diff --git a/backend/indexer/src/index.ts b/backend/indexer/src/index.ts index 4da4d33..faac19e 100644 --- a/backend/indexer/src/index.ts +++ b/backend/indexer/src/index.ts @@ -1,10 +1,9 @@ import { ponder } from "@/generated"; -import { BigNumber } from "ethers"; ponder.on("EtherspotPaymaster:SponsorSuccessful", async ({ event, context }) => { const { db, network } = context; const { args, block, transaction, log } = event; - const time = Number(BigNumber.from(block.timestamp).toString()); + const time = Number(BigInt(block.timestamp).toString()); const timestamp = new Date(time * 1000); try { await db.PaymasterEvent.create({ diff --git a/backend/package.json b/backend/package.json index b7e6c1d..ab89647 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "arka", - "version": "1.2.9", + "version": "1.2.10", "description": "ARKA - (Albanian for Cashier's case) is the first open source Paymaster as a service software", "type": "module", "directories": { diff --git a/backend/src/abi/ChainlinkOracleAbi.ts b/backend/src/abi/ChainlinkOracleAbi.ts index d178ee2..a232868 100644 --- a/backend/src/abi/ChainlinkOracleAbi.ts +++ b/backend/src/abi/ChainlinkOracleAbi.ts @@ -506,4 +506,4 @@ export default [ "stateMutability": "view", "type": "function" } -] \ No newline at end of file +] as const \ No newline at end of file diff --git a/backend/src/abi/EtherspotChainlinkOracleAbi.ts b/backend/src/abi/EtherspotChainlinkOracleAbi.ts index a53d6dd..ff10521 100644 --- a/backend/src/abi/EtherspotChainlinkOracleAbi.ts +++ b/backend/src/abi/EtherspotChainlinkOracleAbi.ts @@ -360,4 +360,4 @@ export default [ "stateMutability": "nonpayable", "type": "function" } -] \ No newline at end of file +] as const; \ No newline at end of file diff --git a/backend/src/abi/PimlicoAbi.ts b/backend/src/abi/PimlicoAbi.ts index b3ffc9e..5cedf40 100644 --- a/backend/src/abi/PimlicoAbi.ts +++ b/backend/src/abi/PimlicoAbi.ts @@ -484,4 +484,4 @@ export default [ stateMutability: "nonpayable", type: "function" } -] \ No newline at end of file +] as const; \ No newline at end of file diff --git a/backend/src/abi/PythOracleAbi.ts b/backend/src/abi/PythOracleAbi.ts index 18a5868..01d8b90 100644 --- a/backend/src/abi/PythOracleAbi.ts +++ b/backend/src/abi/PythOracleAbi.ts @@ -517,4 +517,4 @@ export default [ "stateMutability": "payable", "type": "function" } -] \ No newline at end of file +] as const; \ No newline at end of file diff --git a/backend/src/constants/Pimlico.ts b/backend/src/constants/Pimlico.ts index a226ede..528333d 100644 --- a/backend/src/constants/Pimlico.ts +++ b/backend/src/constants/Pimlico.ts @@ -1,4 +1,6 @@ -export const TOKEN_ADDRESS: Record> = { +import { Hex } from "viem" + +export const TOKEN_ADDRESS: Record> = { 1: { USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", }, @@ -49,7 +51,7 @@ export const NATIVE_ASSET: Record = { 84531: "ETH" } -export const ORACLE_ADDRESS: Record> = { +export const ORACLE_ADDRESS: Record> = { 1: { ETH: "0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419", DAI: "0xaed0c38402a5d19df6e4c03f4e2dced6e29c1ee9", @@ -99,7 +101,7 @@ export const ORACLE_ADDRESS: Record> = { /** * Example Structure for adding deployed erc20 paymasters on AWS secrets manager */ -export const CustomDeployedPaymasters: Record> = { +export const CustomDeployedPaymasters: Record> = { "137": { "USDT": "0xFB8A7D1786E01f31Fc6466A48243Ca9FF0820cCB" }, @@ -140,7 +142,7 @@ export const CustomDeployedPaymasters: Record> = } } -export const PAYMASTER_ADDRESS: Record> = { +export const PAYMASTER_ADDRESS: Record> = { 1: { USDC: "0x0000000000fABFA8079AB313D1D14Dcf4D15582a" }, @@ -181,7 +183,7 @@ export const PAYMASTER_ADDRESS: Record> = { /** * Example Structure for adding deployed multi-token paymasters on AWS secrets manager */ -export const MULTI_TOKEN_PAYMASTERS: Record> = { +export const MULTI_TOKEN_PAYMASTERS: Record> = { "80001": { "0x453478E2E0c846c069e544405d5877086960BEf2": "0xe85649152D15825F2226B2d9C49c07b1cd2b36C7" }, @@ -193,7 +195,7 @@ export const MULTI_TOKEN_PAYMASTERS: Record> = { /** * Example Structure for adding deployed multi-token oracles on AWS secrets manager */ -export const MULTI_TOKEN_ORACLES: Record> = { +export const MULTI_TOKEN_ORACLES: Record> = { "80001": { "0x453478E2E0c846c069e544405d5877086960BEf2": "0x75bDb3803d671ac3051112E8A7745fF88eEd6d94" }, diff --git a/backend/src/paymaster/index.ts b/backend/src/paymaster/index.ts index 5d5a15c..c53274a 100644 --- a/backend/src/paymaster/index.ts +++ b/backend/src/paymaster/index.ts @@ -1,6 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { providers, Wallet, ethers, Contract, BigNumber, BigNumberish } from 'ethers'; -import { arrayify, BytesLike, defaultAbiCoder, hexConcat, hexZeroPad } from 'ethers/lib/utils.js'; import { FastifyBaseLogger } from 'fastify'; import EtherspotAbiV06 from '../abi/EtherspotAbi.js'; import EtherspotAbiV07 from "../abi/EtherspotVerifyingSignerAbi.js"; @@ -11,63 +9,93 @@ import { getEtherscanFee } from '../utils/common.js'; import MultiTokenPaymasterAbi from '../abi/MultiTokenPaymasterAbi.js'; import OrochiOracleAbi from '../abi/OrochiOracleAbi.js'; import ChainlinkOracleAbi from '../abi/ChainlinkOracleAbi.js'; +import { + pad, + toHex, + concat, + parseUnits, + PrivateKeyAccount, + encodeAbiParameters, + http, + GetContractReturnType, + createPublicClient, + getContract, + createWalletClient, + parseEther, + encodeFunctionData, + SendTransactionReturnType, + erc20Abi, + Hex, + formatEther, + PublicClient, + stringToBytes, + formatUnits, + zeroAddress, + Chain +} from "viem"; +import { privateKeyToAccount } from "viem/accounts"; export class Paymaster { - feeMarkUp: BigNumber; - multiTokenMarkUp: number; + feeMarkUp: bigint; + multiTokenMarkUp: Number; constructor(feeMarkUp: string, multiTokenMarkUp: string) { - this.feeMarkUp = ethers.utils.parseUnits(feeMarkUp, 'gwei'); + this.feeMarkUp = parseUnits(feeMarkUp, 9); if (isNaN(Number(multiTokenMarkUp))) this.multiTokenMarkUp = 1150000 // 15% more of the actual cost. Can be anything between 1e6 to 2e6 else this.multiTokenMarkUp = Number(multiTokenMarkUp); } - packUint (high128: BigNumberish, low128: BigNumberish): string { - return hexZeroPad(BigNumber.from(high128).shl(128).add(low128).toHexString(), 32) + packUint (high128: bigint, low128: bigint): Hex { + return pad((toHex((high128 << BigInt(128)) + low128)), {size: 32}); } - packPaymasterData (paymaster: string, paymasterVerificationGasLimit: BigNumberish, postOpGasLimit: BigNumberish, paymasterData?: BytesLike): BytesLike { - return ethers.utils.hexConcat([ + packPaymasterData (paymaster: Hex, paymasterVerificationGasLimit: bigint, postOpGasLimit: bigint, paymasterData?: Hex): Hex { + return concat([ paymaster, this.packUint(paymasterVerificationGasLimit, postOpGasLimit), paymasterData ?? '0x' ]) } - async getPaymasterData(userOp: any, validUntil: string, validAfter: string, paymasterContract: Contract, signer: Wallet) { + async getPaymasterData(userOp: any, validUntil: number, validAfter: number, paymasterContract: GetContractReturnType, signer: PrivateKeyAccount) { // actual signing... - const hash = await paymasterContract.getHash( - userOp, - validUntil, - validAfter - ); + const hash = await paymasterContract.read.getHash([userOp, validUntil, validAfter]); - const sig = await signer.signMessage(arrayify(hash)); + const sig = await signer.signMessage({message: {raw: hash}}); - const paymasterData = hexConcat([ - defaultAbiCoder.encode( - ['uint48', 'uint48'], - [validUntil, validAfter] + const paymasterData = concat([ + encodeAbiParameters( + [{type: 'uint48'}, {type: 'uint48'}], + [validUntil, validAfter] ), - sig, + sig ]); return paymasterData; } - async signV07(userOp: any, validUntil: string, validAfter: string, entryPoint: string, paymasterAddress: string, - bundlerRpc: string, signer: Wallet, estimate: boolean, log?: FastifyBaseLogger) { + async signV07(userOp: any, validUntil: string, validAfter: string, entryPoint: Hex, paymasterAddress: Hex, + bundlerRpc: string, signer: PrivateKeyAccount, estimate: boolean, log?: FastifyBaseLogger) { try { - const provider = new providers.JsonRpcProvider(bundlerRpc); - const paymasterContract = new ethers.Contract(paymasterAddress, EtherspotAbiV07, provider); + const client = createPublicClient({ + transport: http(bundlerRpc) + }); + const paymasterContract = getContract({ + abi: EtherspotAbiV07, + address: paymasterAddress, + client + }) if (!userOp.signature) userOp.signature = '0x'; - if (userOp.factory && userOp.factoryData) userOp.initCode = hexConcat([userOp.factory, userOp.factoryData ?? '']) + if (userOp.factory && userOp.factoryData) userOp.initCode = concat([userOp.factory, userOp.factoryData ?? '']) if (!userOp.initCode) userOp.initCode = "0x"; if (estimate) { - const response = await provider.send('eth_estimateUserOperationGas', [userOp, entryPoint]); - userOp.verificationGasLimit = response.verificationGasLimit; - userOp.callGasLimit = response.callGasLimit; - userOp.preVerificationGas = response.preVerificationGas; + const response: {verificationGasLimit: string, preVerificationGas: string, callGasLimit: string} = await client.request({ + method: "eth_estimateUserOperationGas" as any, + params: [userOp, entryPoint] + }) + userOp.verificationGasLimit = BigInt(response.verificationGasLimit); + userOp.callGasLimit = BigInt(response.callGasLimit); + userOp.preVerificationGas = BigInt(response.preVerificationGas); } const accountGasLimits = this.packUint(userOp.verificationGasLimit, userOp.callGasLimit) const gasFees = this.packUint(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas); @@ -79,11 +107,11 @@ export class Paymaster { accountGasLimits: accountGasLimits, preVerificationGas: userOp.preVerificationGas, gasFees: gasFees, - paymasterAndData: this.packPaymasterData(paymasterAddress, BigNumber.from(30000), "0x1"), + paymasterAndData: this.packPaymasterData(paymasterAddress, BigInt(30000), BigInt("0x1")), signature: userOp.signature } - const paymasterData = await this.getPaymasterData(packedUserOp, validUntil, validAfter, paymasterContract, signer); + let paymasterData = await this.getPaymasterData(packedUserOp, +validUntil, +validAfter, paymasterContract, signer); let returnValue; if (estimate) { returnValue = { @@ -92,8 +120,8 @@ export class Paymaster { preVerificationGas: packedUserOp.preVerificationGas.toString(), verificationGasLimit: userOp.verificationGasLimit, callGasLimit: userOp.callGasLimit, - paymasterVerificationGasLimit: BigNumber.from(30000).toString(), - paymasterPostOpGasLimit: "0x1" + paymasterVerificationGasLimit: BigInt(30000).toString(), + paymasterPostOpGasLimit: BigInt("0x1").toString() } } else { returnValue = { @@ -109,20 +137,20 @@ export class Paymaster { } } - async getPaymasterAndData(userOp: any, validUntil: string, validAfter: string, paymasterContract: Contract, signer: Wallet) { + async getPaymasterAndData(userOp: any, validUntil: number, validAfter: number, paymasterContract: GetContractReturnType, signer: PrivateKeyAccount) { // actual signing... - const hash = await paymasterContract.getHash( + const hash = await paymasterContract.read.getHash([ userOp, validUntil, validAfter - ); + ]); - const sig = await signer.signMessage(arrayify(hash)); + const sig = await signer.signMessage({message: {raw: hash}}); - const paymasterAndData = hexConcat([ + const paymasterAndData = concat([ paymasterContract.address, - defaultAbiCoder.encode( - ['uint48', 'uint48'], + encodeAbiParameters( + [{type: 'uint48'}, {type: 'uint48'}], [validUntil, validAfter] ), sig, @@ -131,27 +159,36 @@ export class Paymaster { return paymasterAndData; } - async signV06(userOp: any, validUntil: string, validAfter: string, entryPoint: string, paymasterAddress: string, - bundlerRpc: string, signer: Wallet, estimate: boolean, log?: FastifyBaseLogger) { + async signV06(userOp: any, validUntil: string, validAfter: string, entryPoint: Hex, paymasterAddress: Hex, + bundlerRpc: string, signer: PrivateKeyAccount, estimate: boolean, log?: FastifyBaseLogger) { try { - const provider = new providers.JsonRpcProvider(bundlerRpc); - const paymasterContract = new ethers.Contract(paymasterAddress, EtherspotAbiV06, provider); - userOp.paymasterAndData = await this.getPaymasterAndData(userOp, validUntil, validAfter, paymasterContract, signer); + const client = createPublicClient({ + transport: http(bundlerRpc) + }); + const paymasterContract = getContract({ + abi: EtherspotAbiV06, + address: paymasterAddress, + client + }); + userOp.paymasterAndData = await this.getPaymasterAndData(userOp, +validUntil, +validAfter, paymasterContract, signer); if (!userOp.signature) userOp.signature = '0x'; if (estimate) { - const response = await provider.send('eth_estimateUserOperationGas', [userOp, entryPoint]); - userOp.verificationGasLimit = response.verificationGasLimit; - userOp.preVerificationGas = response.preVerificationGas; - userOp.callGasLimit = response.callGasLimit; + const response: {verificationGasLimit: string, preVerificationGas: string, callGasLimit: string} = await client.request({ + method: "eth_estimateUserOperationGas" as any, + params: [userOp, entryPoint] + }) + userOp.verificationGasLimit = BigInt(response.verificationGasLimit); + userOp.preVerificationGas = BigInt(response.preVerificationGas); + userOp.callGasLimit = BigInt(response.callGasLimit); } - const paymasterAndData = await this.getPaymasterAndData(userOp, validUntil, validAfter, paymasterContract, signer); + const paymasterAndData = await this.getPaymasterAndData(userOp, +validUntil, +validAfter, paymasterContract, signer); let returnValue; if (estimate) { returnValue = { paymasterAndData, - verificationGasLimit: userOp.verificationGasLimit, - preVerificationGas: userOp.preVerificationGas, - callGasLimit: userOp.callGasLimit, + verificationGasLimit: userOp.verificationGasLimit.toString(), + preVerificationGas: userOp.preVerificationGas.toString(), + callGasLimit: userOp.callGasLimit.toString(), } } else { returnValue = { @@ -166,63 +203,80 @@ export class Paymaster { } } - async getPaymasterAndDataForMultiTokenPaymaster(userOp: any, validUntil: string, validAfter: string, feeToken: string, - ethPrice: string, paymasterContract: Contract, signer: Wallet) { - const exchangeRate = 1000000; // This is for setting min tokens required for the txn that gets validated on estimate - const rate = ethers.BigNumber.from(exchangeRate).mul(ethPrice); - const priceMarkup = this.multiTokenMarkUp; + async getPaymasterAndDataForMultiTokenPaymaster(userOp: any, validUntil: number, validAfter: number, feeToken: Hex, + ethPrice: string, paymasterContract: GetContractReturnType, signer: PrivateKeyAccount) { + const exchangeRate = 1000000n; // This is for setting min tokens required for the txn that gets validated on estimate + const rate = exchangeRate * BigInt(ethPrice); + const priceMarkup = this.multiTokenMarkUp as number; // actual signing... // priceSource inputs available 0 - for using external exchange price and 1 - for oracle based price - const hash = await paymasterContract.getHash( + const hash = await paymasterContract.read.getHash([ userOp, 0, validUntil, validAfter, feeToken, - ethers.constants.AddressZero, - rate.toNumber().toFixed(0), + zeroAddress, + rate, priceMarkup, - ); + ]); - const sig = await signer.signMessage(arrayify(hash)); + const sig = await signer.signMessage({message: {raw: hash}}); - const paymasterAndData = hexConcat([ + const paymasterAndData = concat([ paymasterContract.address, '0x00', - defaultAbiCoder.encode( - ['uint48', 'uint48', 'address', 'address', 'uint256', 'uint32'], - [validUntil, validAfter, feeToken, ethers.constants.AddressZero, rate.toNumber().toFixed(0), priceMarkup] + encodeAbiParameters( + [{type: 'uint48'}, {type: 'uint48'}, {type: 'address'}, {type: 'address'}, {type: 'uint256'}, {type: 'uint32'}], + [validUntil, validAfter, feeToken, zeroAddress, rate, priceMarkup] ), - sig, + sig ]); return paymasterAndData; } - async signMultiTokenPaymaster(userOp: any, validUntil: string, validAfter: string, entryPoint: string, paymasterAddress: string, - feeToken: string, oracleAggregator: string, bundlerRpc: string, signer: Wallet, oracleName: string, log?: FastifyBaseLogger) { + async signMultiTokenPaymaster(userOp: any, validUntil: string, validAfter: string, entryPoint: Hex, paymasterAddress: Hex, + feeToken: Hex, oracleAggregator: Hex, bundlerRpc: string, signer: PrivateKeyAccount, oracleName: string, log?: FastifyBaseLogger) { try { - const provider = new providers.JsonRpcProvider(bundlerRpc); - const paymasterContract = new ethers.Contract(paymasterAddress, MultiTokenPaymasterAbi, provider); + const client = createPublicClient({ + transport: http(bundlerRpc) + }); + const paymasterContract = getContract({ + abi: MultiTokenPaymasterAbi, + address: paymasterAddress, + client + }); let ethPrice = ""; if (oracleName === "orochi") { - const oracleContract = new ethers.Contract(oracleAggregator, OrochiOracleAbi, provider); - const result = await oracleContract.getLatestData(1, ethers.utils.hexlify(ethers.utils.toUtf8Bytes('ETH')).padEnd(42, '0')) - ethPrice = Number(ethers.utils.formatEther(result)).toFixed(0); + const oracleContract = getContract({ + abi: OrochiOracleAbi, + address: oracleAggregator, + client + }); + const result = await oracleContract.read.getLatestData([1, toHex(stringToBytes('ETH')).padEnd(42, '0') as Hex]); + ethPrice = Number(formatEther(BigInt(result))).toFixed(0); } else { - const chainlinkContract = new ethers.Contract(oracleAggregator, ChainlinkOracleAbi, provider); - const decimals = await chainlinkContract.decimals(); - const result = await chainlinkContract.latestAnswer(); - ethPrice = Number(ethers.utils.formatUnits(result, decimals)).toFixed(0); + const chainlinkContract = getContract({ + abi: ChainlinkOracleAbi, + address: oracleAggregator, + client + }); + const decimals = await chainlinkContract.read.decimals(); + const result = await chainlinkContract.read.latestAnswer(); + ethPrice = Number(formatUnits(result, decimals)).toFixed(0); } - userOp.paymasterAndData = await this.getPaymasterAndDataForMultiTokenPaymaster(userOp, validUntil, validAfter, feeToken, ethPrice, paymasterContract, signer); + userOp.paymasterAndData = await this.getPaymasterAndDataForMultiTokenPaymaster(userOp, +validUntil, +validAfter, feeToken, ethPrice, paymasterContract, signer); if (!userOp.signature) userOp.signature = '0x'; - const response = await provider.send('eth_estimateUserOperationGas', [userOp, entryPoint]); - userOp.verificationGasLimit = response.verificationGasLimit; - userOp.preVerificationGas = response.preVerificationGas; - userOp.callGasLimit = response.callGasLimit; - const paymasterAndData = await this.getPaymasterAndDataForMultiTokenPaymaster(userOp, validUntil, validAfter, feeToken, ethPrice, paymasterContract, signer); + const response: {verificationGasLimit: string, preVerificationGas: string, callGasLimit: string} = await client.request({ + method: "eth_estimateUserOperationGas" as any, + params: [userOp, entryPoint] + }) + userOp.verificationGasLimit = BigInt(response.verificationGasLimit); + userOp.preVerificationGas = BigInt(response.preVerificationGas); + userOp.callGasLimit = BigInt(response.callGasLimit); + const paymasterAndData = await this.getPaymasterAndDataForMultiTokenPaymaster(userOp, +validUntil, +validAfter, feeToken, ethPrice, paymasterContract, signer); const returnValue = { paymasterAndData, @@ -238,46 +292,39 @@ export class Paymaster { } } - async pimlico(userOp: any, bundlerRpc: string, entryPoint: string, PaymasterAddress: string, log?: FastifyBaseLogger) { + async pimlico(userOp: any, bundlerRpc: string, entryPoint: Hex, PaymasterAddress: Hex, log?: FastifyBaseLogger) { try { - const provider = new providers.JsonRpcProvider(bundlerRpc); - const erc20Paymaster = new PimlicoPaymaster(PaymasterAddress, provider) + const client = createPublicClient({ + transport: http(bundlerRpc) + }); + const erc20Paymaster = new PimlicoPaymaster(PaymasterAddress, client) if (!userOp.signature) userOp.signature = '0x'; - - // The minimum ABI to get the ERC20 Token balance - const minABI = [ - // balanceOf - { - constant: true, - - inputs: [{ name: '_owner', type: 'address' }], - - name: 'balanceOf', - - outputs: [{ name: 'balance', type: 'uint256' }], - - type: 'function', - }, - ] const tokenAmountRequired = await erc20Paymaster.calculateTokenAmount(userOp); - const tokenContract = new Contract(await erc20Paymaster.tokenAddress, minABI, provider) - const tokenBalance = await tokenContract.balanceOf(userOp.sender); - - if (tokenAmountRequired.gte(tokenBalance)) + const tokenContract = getContract({ + abi: erc20Abi, + address: (await erc20Paymaster.tokenAddress), + client + }); + const tokenBalance = await tokenContract.read.balanceOf([userOp.sender]); + + if (tokenAmountRequired >= tokenBalance) throw new Error(`The required token amount ${tokenAmountRequired.toString()} is more than what the sender has ${tokenBalance}`) let paymasterAndData = await erc20Paymaster.generatePaymasterAndDataForTokenAmount(userOp, tokenAmountRequired) userOp.paymasterAndData = paymasterAndData; - const response = await provider.send('eth_estimateUserOperationGas', [userOp, entryPoint]); - userOp.verificationGasLimit = ethers.BigNumber.from(response.verificationGasLimit).add(100000).toString(); - userOp.preVerificationGas = response.preVerificationGas; - userOp.callGasLimit = response.callGasLimit; + const response: {verficationGasLimit: string, preVerificationGas: string, callGasLimit: string} = await client.request({ + method: "eth_estimateUserOperationGas" as any, + params: [userOp, entryPoint] + }) + userOp.verificationGasLimit = (BigInt(response.verficationGasLimit) + BigInt(100000)) + userOp.preVerificationGas = BigInt(response.preVerificationGas) + userOp.callGasLimit = BigInt(response.callGasLimit) paymasterAndData = await erc20Paymaster.generatePaymasterAndData(userOp); return { paymasterAndData, - verificationGasLimit: userOp.verificationGasLimit, + verificationGasLimit: userOp.verificationGasLimit.toString(), preVerificationGas: response.preVerificationGas, callGasLimit: response.callGasLimit, }; @@ -299,51 +346,66 @@ export class Paymaster { } } - async whitelistAddresses(address: string[], paymasterAddress: string, bundlerRpc: string, relayerKey: string, chainId: number, log?: FastifyBaseLogger) { + async whitelistAddresses(address: Hex[], paymasterAddress: Hex, bundlerRpc: string, relayerKey: Hex, chainId: number, chain: Chain, log?: FastifyBaseLogger) { try { - const provider = new providers.JsonRpcProvider(bundlerRpc); - const paymasterContract = new ethers.Contract(paymasterAddress, EtherspotAbiV06, provider); - const signer = new Wallet(relayerKey, provider) + const client = createPublicClient({ + transport: http(bundlerRpc), + chain + }); + const paymasterContract = getContract({ + abi: EtherspotAbiV06, + address: paymasterAddress, + client + }); + const signer = createWalletClient({ + transport: http(bundlerRpc), + account: privateKeyToAccount(relayerKey), + chain + }); for (let i = 0; i < address.length; i++) { - const isAdded = await paymasterContract.check(signer.address, address[i]); + const isAdded = await paymasterContract.read.check([signer.account.address, address[i]]); if (isAdded) { throw new Error(`${address[i]} already whitelisted`) } } - const encodedData = paymasterContract.interface.encodeFunctionData('addBatchToWhitelist', [address]); + const encodedData = encodeFunctionData({ + abi: EtherspotAbiV06, + args: [address], + functionName: 'addBatchToWhitelist' + }) const etherscanFeeData = await getEtherscanFee(chainId); let feeData; if (etherscanFeeData) { feeData = etherscanFeeData; } else { - feeData = await provider.getFeeData(); - feeData.gasPrice = feeData.gasPrice ? feeData.gasPrice.add(this.feeMarkUp) : null; - feeData.maxFeePerGas = feeData.maxFeePerGas ? feeData.maxFeePerGas.add(this.feeMarkUp) : null; - feeData.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ? feeData.maxPriorityFeePerGas.add(this.feeMarkUp) : null; + feeData = await client.estimateFeesPerGas(); + feeData.gasPrice = feeData.gasPrice ? feeData.gasPrice + this.feeMarkUp : undefined; + feeData.maxFeePerGas = feeData.maxFeePerGas ? feeData.maxFeePerGas + this.feeMarkUp : undefined; + feeData.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ? feeData.maxPriorityFeePerGas + this.feeMarkUp : undefined; } - let tx: providers.TransactionResponse; + let tx: SendTransactionReturnType; if (!feeData.maxFeePerGas) { tx = await signer.sendTransaction({ to: paymasterAddress, data: encodedData, - gasPrice: feeData.gasPrice ?? undefined, - }) + gasPrice: feeData.gasPrice ?? undefined + }); } else { tx = await signer.sendTransaction({ to: paymasterAddress, data: encodedData, maxFeePerGas: feeData.maxFeePerGas ?? undefined, maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, - type: 2, + type: "eip1559" }); } // commented the below line to avoid timeouts for long delays in transaction confirmation. // await tx.wait(); return { - message: `Successfully whitelisted with transaction Hash ${tx.hash}` + message: `Successfully whitelisted with transaction Hash ${tx}` }; } catch (err: any) { if (err.message.includes('already whitelisted')) throw new Error(err.message); @@ -352,31 +414,45 @@ export class Paymaster { } } - async removeWhitelistAddress(address: string[], paymasterAddress: string, bundlerRpc: string, relayerKey: string, chainId: number, log?: FastifyBaseLogger) { + async removeWhitelistAddress(address: Hex[], paymasterAddress: Hex, bundlerRpc: string, relayerKey: Hex, chainId: number, chain: Chain, log?: FastifyBaseLogger) { try { - const provider = new providers.JsonRpcProvider(bundlerRpc); - const paymasterContract = new ethers.Contract(paymasterAddress, EtherspotAbiV06, provider); - const signer = new Wallet(relayerKey, provider) + const client = createPublicClient({ + transport: http(bundlerRpc), + chain + }); + const paymasterContract = getContract({ + abi: EtherspotAbiV06, + address: paymasterAddress, + client + }); + const signer = createWalletClient({ + transport: http(bundlerRpc), + account: privateKeyToAccount(relayerKey), + chain + }); for (let i = 0; i < address.length; i++) { - const isAdded = await paymasterContract.check(signer.address, address[i]); + const isAdded = await paymasterContract.read.check([signer.account.address, address[i]]); if (!isAdded) { throw new Error(`${address[i]} is not whitelisted`) } } - - const encodedData = paymasterContract.interface.encodeFunctionData('removeBatchFromWhitelist', [address]); + const encodedData = encodeFunctionData({ + abi: EtherspotAbiV06, + args: [address], + functionName: 'removeBatchFromWhitelist' + }) const etherscanFeeData = await getEtherscanFee(chainId); let feeData; if (etherscanFeeData) { feeData = etherscanFeeData; } else { - feeData = await provider.getFeeData(); - feeData.gasPrice = feeData.gasPrice ? feeData.gasPrice.add(this.feeMarkUp) : null; - feeData.maxFeePerGas = feeData.maxFeePerGas ? feeData.maxFeePerGas.add(this.feeMarkUp) : null; - feeData.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ? feeData.maxPriorityFeePerGas.add(this.feeMarkUp) : null; + feeData = await client.estimateFeesPerGas(); + feeData.gasPrice = feeData.gasPrice ? feeData.gasPrice + this.feeMarkUp : undefined; + feeData.maxFeePerGas = feeData.maxFeePerGas ? feeData.maxFeePerGas + this.feeMarkUp : undefined; + feeData.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ? feeData.maxPriorityFeePerGas + this.feeMarkUp : undefined; } - let tx: providers.TransactionResponse; + let tx: SendTransactionReturnType; if (!feeData.maxFeePerGas) { tx = await signer.sendTransaction({ to: paymasterAddress, @@ -389,14 +465,14 @@ export class Paymaster { data: encodedData, maxFeePerGas: feeData.maxFeePerGas ?? undefined, maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, - type: 2, + type: "eip1559", }); } // commented the below line to avoid timeouts for long delays in transaction confirmation. // await tx.wait(); return { - message: `Successfully removed whitelisted addresses with transaction Hash ${tx.hash}` + message: `Successfully removed whitelisted addresses with transaction Hash ${tx}` }; } catch (err: any) { if (err.message.includes('is not whitelisted')) throw new Error(err.message); @@ -405,64 +481,86 @@ export class Paymaster { } } - async checkWhitelistAddress(accountAddress: string, paymasterAddress: string, bundlerRpc: string, relayerKey: string, log?: FastifyBaseLogger) { + async checkWhitelistAddress(accountAddress: Hex, paymasterAddress: Hex, bundlerRpc: string, relayerKey: Hex, log?: FastifyBaseLogger) { try { - const provider = new providers.JsonRpcProvider(bundlerRpc); - const signer = new Wallet(relayerKey, provider) - const paymasterContract = new ethers.Contract(paymasterAddress, EtherspotAbiV06, provider); - return paymasterContract.check(signer.address, accountAddress); + const client = createPublicClient({ + transport: http(bundlerRpc) + }); + const account = privateKeyToAccount(relayerKey); + const paymasterContract = getContract({ + abi: EtherspotAbiV06, + address: paymasterAddress, + client + }); + return paymasterContract.read.check([account.address, accountAddress]); } catch (err) { if (log) log.error(err, 'checkWhitelistAddress'); throw new Error(ErrorMessage.RPC_ERROR); } } - async deposit(amount: string, paymasterAddress: string, bundlerRpc: string, relayerKey: string, chainId: number, isEpv06: boolean, log?: FastifyBaseLogger) { + async deposit(amount: string, paymasterAddress: Hex, bundlerRpc: string, relayerKey: Hex, chainId: number, isEpv06: boolean, chain: Chain, log?: FastifyBaseLogger) { try { - const provider = new providers.JsonRpcProvider(bundlerRpc); - const paymasterContract = new ethers.Contract(paymasterAddress, isEpv06 ? EtherspotAbiV06 : EtherspotAbiV07, provider); - const signer = new Wallet(relayerKey, provider) - const balance = await signer.getBalance(); - const amountInWei = ethers.utils.parseEther(amount.toString()); - if (amountInWei.gte(balance)) - throw new Error(`${signer.address} Balance is less than the amount to be deposited`) - - const encodedData = paymasterContract.interface.encodeFunctionData(isEpv06 ? 'depositFunds': 'deposit', []); + const client = createPublicClient({ + transport: http(bundlerRpc), + chain + }); + const abi = isEpv06 ? EtherspotAbiV06 : EtherspotAbiV07; + const signer = createWalletClient({ + account: privateKeyToAccount(relayerKey), + transport: http(bundlerRpc), + chain + }); + const balance = await client.getBalance({ + address: signer.account.address + }); + const amountInWei = parseEther(amount); + if (amountInWei >= balance) + throw new Error(`${signer.account.address} Balance is less than the amount to be deposited`) + + const encodedData = encodeFunctionData({ + abi, + args: [], + functionName: isEpv06 ? 'depositFunds' : 'deposit' + }); const etherscanFeeData = await getEtherscanFee(chainId); let feeData; if (etherscanFeeData) { feeData = etherscanFeeData; } else { - feeData = await provider.getFeeData(); - feeData.gasPrice = feeData.gasPrice ? feeData.gasPrice.add(this.feeMarkUp) : null; - feeData.maxFeePerGas = feeData.maxFeePerGas ? feeData.maxFeePerGas.add(this.feeMarkUp) : null; - feeData.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ? feeData.maxPriorityFeePerGas.add(this.feeMarkUp) : null; + feeData = await client.estimateFeesPerGas({ + type: 'eip1559' + }) + feeData.gasPrice = feeData.gasPrice ? feeData.gasPrice + this.feeMarkUp : undefined; + feeData.maxFeePerGas = feeData.maxFeePerGas ? feeData.maxFeePerGas + this.feeMarkUp : undefined; + feeData.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ? feeData.maxPriorityFeePerGas + this.feeMarkUp : undefined; } - let tx: providers.TransactionResponse; + let tx: SendTransactionReturnType; if (!feeData.maxFeePerGas) { tx = await signer.sendTransaction({ to: paymasterAddress, data: encodedData, value: amountInWei, - gasPrice: feeData.gasPrice ?? undefined, + gasPrice: feeData.gasPrice }) } else { tx = await signer.sendTransaction({ to: paymasterAddress, data: encodedData, value: amountInWei, - maxFeePerGas: feeData.maxFeePerGas ?? undefined, + maxFeePerGas: feeData.maxFeePerGas ? feeData.maxFeePerGas : undefined, maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, - type: 2, + type: "eip1559", + chain }); } // commented the below line to avoid timeouts for long delays in transaction confirmation. // await tx.wait(); return { - message: `Successfully deposited with transaction Hash ${tx.hash}` + message: `Successfully deposited with transaction Hash ${tx}` }; } catch (err: any) { if (log) log.error(err, 'deposit'); diff --git a/backend/src/paymaster/pimlico.ts b/backend/src/paymaster/pimlico.ts index e1ce5f7..b90efd4 100644 --- a/backend/src/paymaster/pimlico.ts +++ b/backend/src/paymaster/pimlico.ts @@ -1,28 +1,31 @@ -import { Contract, BigNumber, providers, utils, Signer, ethers } from "ethers"; -import { UserOperationStruct } from "@account-abstraction/contracts" -import { NotPromise } from "@account-abstraction/utils" +import { UserOperationStruct } from "../types/entrypoint" import abi from "../abi/PimlicoAbi.js"; import { NATIVE_ASSET, ORACLE_ADDRESS, TOKEN_ADDRESS, bytecode } from "../constants/Pimlico.js"; +import { encodeDeployData, getContract, getContractAddress, Hex, keccak256, pad, parseUnits, PublicClient, toHex, WalletClient, concat } from "viem"; export interface ERC20PaymasterBuildOptions { - entrypoint?: string + entrypoint?: Hex nativeAsset?: string - nativeAssetOracle?: string - tokenAddress?: string - tokenOracle?: string - owner?: string - deployer?: Signer + nativeAssetOracle?: Hex + tokenAddress?: Hex + tokenOracle?: Hex + owner?: Hex + deployer?: WalletClient } export class PimlicoPaymaster { - private contract: Contract; - tokenAddress: Promise; - paymasterAddress: string; + private contract; + tokenAddress: Promise; + paymasterAddress: Hex; - constructor(address: string, provider: providers.Provider) { + constructor(address: Hex, client: PublicClient) { this.paymasterAddress = address; - this.contract = new Contract(address, abi, provider) - this.tokenAddress = this.contract.token(); + this.contract = getContract({ + abi: abi, + address: address, + client + }); + this.tokenAddress = this.contract.read.token() as Promise; } /** @@ -32,31 +35,36 @@ export class PimlicoPaymaster { * @param userOp the user operation to calculate the token amount for (with gas limits already set) * @returns the recommend token price to set during paymaster execution */ - async calculateTokenAmount(userOp: NotPromise): Promise { - const priceMarkup = await this.contract.priceMarkup() - const cachedPrice = await this.contract.previousPrice() - const tokenDecimals = await this.contract.tokenDecimals(); - if (cachedPrice.eq(0)) { - throw new Error("ERC20Paymaster: no previous price set") + async calculateTokenAmount(userOp: UserOperationStruct): Promise { + const priceMarkup = await this.contract.read.priceMarkup(); + const cachedPrice = await this.contract.read.previousPrice(); + const tokenDecimals = await this.contract.read.tokenDecimals(); + if (cachedPrice == BigInt(0)) { + throw new Error("ERC20Paymaster: no previous price set"); } - const requiredPreFund = BigNumber.from(userOp.preVerificationGas) - .add(BigNumber.from(userOp.verificationGasLimit).mul(3)) // 3 is for buffer when using paymaster - .add(BigNumber.from(userOp.callGasLimit)) - .mul(BigNumber.from(userOp.maxFeePerGas).mul(2)) - let tokenAmount = requiredPreFund - .add(BigNumber.from(userOp.maxFeePerGas).mul(40000)) // 40000 is the REFUND_POSTOP_COST constant - .mul(priceMarkup) - .mul(cachedPrice) - .div(1e6) // 1e6 is the priceDenominator constant + const requiredPreFund = ( + userOp.preVerificationGas + + (userOp.verificationGasLimit * BigInt(3)) + // 3 is for buffer when using paymaster + userOp.callGasLimit + ) * (userOp.maxFeePerGas * BigInt(2)); + + let tokenAmount = ( + ( + ( + requiredPreFund + + (userOp.maxFeePerGas * BigInt(40000)) // 40000 is the REFUND_POSTOP_COST constant + ) * BigInt(priceMarkup) + ) * BigInt(cachedPrice) + ) / BigInt(1e6); // 1e6 is the priceDenominator constant /** * Don't know why but the below calculation is for tokens with 6 decimals such as USDC, USDT * Pimlico default paymasters uses only USDC * After long testing the below code is neglected for tokens with 18 decimals */ - if (ethers.utils.parseUnits('1', 6).eq(tokenDecimals)) { - tokenAmount = tokenAmount.div(BigNumber.from(10).pow(18)); + if (parseUnits('1', 6) === tokenDecimals) { + tokenAmount = tokenAmount / BigInt(10 ** 18) } return tokenAmount; } @@ -67,12 +75,12 @@ export class PimlicoPaymaster { * @param userOp the UserOperation to generate the paymasterAndData for (with gas limits already set) * @returns the paymasterAndData to be filled in */ - async generatePaymasterAndData(userOp: NotPromise): Promise { + async generatePaymasterAndData(userOp: UserOperationStruct): Promise { const tokenAmount = await this.calculateTokenAmount(userOp) - const paymasterAndData = utils.hexlify( - utils.concat([this.contract.address, utils.hexZeroPad(utils.hexlify(tokenAmount), 32)]) - ) - return paymasterAndData + const paymasterAndData = toHex( + concat([this.contract.address, pad(toHex(tokenAmount), {size: 32})]) + ); + return paymasterAndData; } /** @@ -82,16 +90,16 @@ export class PimlicoPaymaster { * @param requiredPreFund the required token amount if already calculated * @returns the paymasterAndData to be filled in */ - async generatePaymasterAndDataForTokenAmount(userOp: NotPromise, tokenAmount: BigNumber): Promise { - const paymasterAndData = utils.hexlify( - utils.concat([this.contract.address, utils.hexZeroPad(utils.hexlify(tokenAmount), 32)]) - ) - return paymasterAndData + async generatePaymasterAndDataForTokenAmount(userOp: UserOperationStruct, tokenAmount: bigint): Promise { + const paymasterAndData = toHex( + concat([this.contract.address, pad(toHex(tokenAmount), {size: 32})]) + ); + return paymasterAndData; } } async function validatePaymasterOptions( - provider: providers.Provider, + client: PublicClient, erc20: string, options?: ERC20PaymasterBuildOptions ): Promise>> { @@ -105,8 +113,7 @@ async function validatePaymasterOptions( if (parsedOptions.deployer === undefined) { throw new Error("Deployer must be provided") } - - const chainId = (await provider.getNetwork()).chainId + const chainId = await client.getChainId(); const nativeAsset = options?.nativeAsset ?? NATIVE_ASSET[chainId] if (!nativeAsset) { throw new Error(`Native asset not found - chainId ${chainId} not supported`) @@ -116,8 +123,8 @@ async function validatePaymasterOptions( if (!nativeAssetOracle) { throw new Error(`Native asset oracle not found - chainId ${chainId} not supported`) } - await provider.getCode(nativeAssetOracle).then((code) => { - if (code === "0x") { + await client.getBytecode({address: nativeAssetOracle}).then((code) => { + if(code === "0x") { throw new Error(`Oracle for ${nativeAsset} on chainId ${chainId} is not deployed`) } }) @@ -126,8 +133,8 @@ async function validatePaymasterOptions( if (!tokenAddress) { throw new Error(`Token ${erc20} not supported on chainId ${chainId}`) } - await provider.getCode(tokenAddress).then((code) => { - if (code === "0x") { + await client.getBytecode({address: tokenAddress}).then((code) => { + if(code === "0x") { throw new Error(`Token ${erc20} on ${chainId} is not deployed`) } }) @@ -136,8 +143,8 @@ async function validatePaymasterOptions( if (!tokenOracle) { throw new Error(`Oracle for ${erc20} not found, not supported on chainId ${chainId}`) } - await provider.getCode(tokenOracle).then((code) => { - if (code === "0x") { + await client.getBytecode({address: tokenOracle}).then((code) => { + if(code === "0x") { throw new Error(`Oracle for ${erc20} on ${chainId} is not deployed`) } }) @@ -154,28 +161,33 @@ async function validatePaymasterOptions( export function getPaymasterConstructor( options: Required, "deployer">> -): string { +): Hex { const constructorArgs = [ options.tokenAddress, options.entrypoint, options.tokenOracle, options.nativeAssetOracle, options.owner - ] - const paymasterConstructor = new utils.Interface(abi).encodeDeploy(constructorArgs) - return utils.hexlify(utils.concat([bytecode, paymasterConstructor])) + ] as const; + const paymasterConstructor = encodeDeployData({ + abi, + bytecode, + args: constructorArgs + }) + return paymasterConstructor; } export async function calculateERC20PaymasterAddress( options: Required, "deployer">> -): Promise { - const address = utils.getCreate2Address( - "0x4e59b44847b379578588920cA78FbF26c0B4956C", - "0x0000000000000000000000000000000000000000000000000000000000000000", - utils.keccak256(getPaymasterConstructor(options)) - ) - - return address +): Promise { + const address = getContractAddress({ + from: "0x4e59b44847b379578588920cA78FbF26c0B4956C", + salt: "0x0000000000000000000000000000000000000000000000000000000000000000" as Hex, + bytecodeHash: keccak256(getPaymasterConstructor(options)), + opcode: "CREATE2" + }) + + return address; } /** @@ -186,13 +198,13 @@ export async function calculateERC20PaymasterAddress( * @returns the ERC20Paymaster object */ export async function getERC20Paymaster( - provider: providers.Provider, + client: PublicClient, erc20: string, - entryPoint: string, + entryPoint: Hex, options?: Omit, "deployer"> ): Promise { let parsedOptions: Required, "deployer">> - const chainId = (await provider.getNetwork()).chainId + const chainId = await client.getChainId(); if (options === undefined) { parsedOptions = { entrypoint: entryPoint, @@ -202,11 +214,13 @@ export async function getERC20Paymaster( owner: "0x4337000c2828F5260d8921fD25829F606b9E8680" // pimlico address } } else { - parsedOptions = await validatePaymasterOptions(provider, erc20, options) - } - const address = await calculateERC20PaymasterAddress(parsedOptions) - if ((await provider.getCode(address)).length <= 2) { - throw new Error(`ERC20Paymaster not deployed at ${address}`) + parsedOptions = await validatePaymasterOptions(client, erc20, options) } - return new PimlicoPaymaster(address, provider) + const address = await calculateERC20PaymasterAddress(parsedOptions); + await client.getBytecode({address}).then((code) => { + if(!code || code.length <= 2) { + throw new Error(`ERC20Paymaster not deployed at ${address}`); + } + }); + return new PimlicoPaymaster(address, client); } diff --git a/backend/src/repository/sponsorship-policy-repository.ts b/backend/src/repository/sponsorship-policy-repository.ts index 10159b0..bb35dc8 100644 --- a/backend/src/repository/sponsorship-policy-repository.ts +++ b/backend/src/repository/sponsorship-policy-repository.ts @@ -1,7 +1,8 @@ import { Sequelize, Op } from 'sequelize'; import { SponsorshipPolicy } from '../models/sponsorship-policy.js'; import { EPVersions, SponsorshipPolicyDto, getEPVersionString } from '../types/sponsorship-policy-dto.js'; -import { ethers } from 'ethers'; +import { isAddress } from 'viem'; +import { server } from 'server.js'; export class SponsorshipPolicyRepository { private sequelize: Sequelize; @@ -662,7 +663,7 @@ export class SponsorshipPolicyRepository { const invalidAddresses: string[] = []; sponsorshipPolicy.addressAllowList.forEach(address => { - if (!address || !ethers.utils.isAddress(address)) { + if (!address || !isAddress(address)) { invalidAddresses.push(address); } }); @@ -676,7 +677,7 @@ export class SponsorshipPolicyRepository { const invalidAddresses: string[] = []; sponsorshipPolicy.addressBlockList.forEach(address => { - if (!address || !ethers.utils.isAddress(address)) { + if (!address || !isAddress(address)) { invalidAddresses.push(address); } }); diff --git a/backend/src/routes/admin-routes.ts b/backend/src/routes/admin-routes.ts index 9a742ea..e0254c1 100644 --- a/backend/src/routes/admin-routes.ts +++ b/backend/src/routes/admin-routes.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { FastifyPluginAsync } from "fastify"; import { CronTime } from 'cron'; -import { ethers } from "ethers"; import ErrorMessage from "../constants/ErrorMessage.js"; import ReturnCode from "../constants/ReturnCode.js"; import { encode, decode } from "../utils/crypto.js"; @@ -9,6 +8,8 @@ import SupportedNetworks from "../../config.json" assert { type: "json" }; import { APIKey } from "../models/api-key.js"; import { ArkaConfigUpdateData } from "../types/arka-config-dto.js"; import { ApiKeyDto } from "../types/apikey-dto.js"; +import { getAddress } from 'viem'; +import { privateKeyToAccount } from "viem/accounts"; const adminRoutes: FastifyPluginAsync = async (server) => { server.post('/adminLogin', async function (request, reply) { @@ -19,7 +20,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { const body: any = JSON.parse(request.body as string); if (!body) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.EMPTY_BODY }); if (!body.walletAddress) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); - if (ethers.utils.getAddress(body.walletAddress) === ethers.utils.getAddress(server.config.ADMIN_WALLET_ADDRESS)) return reply.code(ReturnCode.SUCCESS).send({ error: null, message: "Successfully Logged in" }); + if (getAddress(body.walletAddress) === getAddress(server.config.ADMIN_WALLET_ADDRESS)) return reply.code(ReturnCode.SUCCESS).send({ error: null, message: "Successfully Logged in" }); return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_USER }); } catch (err: any) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_USER }); @@ -87,8 +88,8 @@ const adminRoutes: FastifyPluginAsync = async (server) => { if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*-_&])[A-Za-z\d@$!%*-_&]{8,}$/.test(body.apiKey)) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.API_KEY_VALIDATION_FAILED }) - const wallet = new ethers.Wallet(body.privateKey); - const publicAddress = await wallet.getAddress(); + const wallet = privateKeyToAccount(body.privateKey); + const publicAddress = getAddress(wallet.address); // Use Sequelize to find the API key const result = await server.apiKeyRepository.findOneByWalletAddress(publicAddress); diff --git a/backend/src/routes/deposit-route.ts b/backend/src/routes/deposit-route.ts index 1acbcec..63af623 100644 --- a/backend/src/routes/deposit-route.ts +++ b/backend/src/routes/deposit-route.ts @@ -7,8 +7,9 @@ import SupportedNetworks from "../../config.json" assert { type: "json" }; import ErrorMessage from "../constants/ErrorMessage.js"; import ReturnCode from "../constants/ReturnCode.js"; import { decode } from "../utils/crypto.js"; -import { printRequest, getNetworkConfig } from "../utils/common.js"; +import { printRequest, getNetworkConfig, getViemChain } from "../utils/common.js"; import { APIKey } from "../models/api-key.js"; +import { Hex } from "viem"; const SUPPORTED_ENTRYPOINTS = { 'EPV_06': "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", @@ -53,7 +54,7 @@ const depositRoutes: FastifyPluginAsync = async (server) => { const api_key = query['apiKey'] ?? body.params[2]; if (!api_key) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - let privateKey = ''; + let privateKey: Hex; let supportedNetworks; if (!unsafeMode) { const AWSresponse = await client.send( @@ -78,12 +79,16 @@ const depositRoutes: FastifyPluginAsync = async (server) => { ) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); } - if (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) { + const chain = getViemChain(Number(chainId)); + if ( + (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) || + !chain + ) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); } const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_06); if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); - return await paymaster.deposit(amount, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey, chainId, true, server.log); + return await paymaster.deposit(amount, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey, chainId, true, chain, server.log); } catch (err: any) { request.log.error(err); if (err.name == "ResourceNotFoundException") @@ -105,7 +110,7 @@ const depositRoutes: FastifyPluginAsync = async (server) => { const api_key = query['apiKey'] ?? body.params[2]; if (!api_key) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - let privateKey = ''; + let privateKey: Hex; let supportedNetworks; if (!unsafeMode) { const AWSresponse = await client.send( @@ -130,12 +135,16 @@ const depositRoutes: FastifyPluginAsync = async (server) => { ) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); } - if (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) { + const chain = getViemChain(Number(chainId)); + if ( + (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) || + !chain + ) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); } const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_07); if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); - return await paymaster.deposit(amount, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey, chainId, false, server.log); + return await paymaster.deposit(amount, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey, chainId, false, chain, server.log); } catch (err: any) { request.log.error(err); if (err.name == "ResourceNotFoundException") diff --git a/backend/src/routes/metadata-routes.ts b/backend/src/routes/metadata-routes.ts index cefa106..a82e5e8 100644 --- a/backend/src/routes/metadata-routes.ts +++ b/backend/src/routes/metadata-routes.ts @@ -1,6 +1,5 @@ import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager"; import { FastifyPluginAsync } from "fastify"; -import { Contract, Wallet, providers } from "ethers"; import SupportedNetworks from "../../config.json" assert { type: "json" }; import { getNetworkConfig, printRequest } from "../utils/common.js"; import ReturnCode from "../constants/ReturnCode.js"; @@ -9,6 +8,8 @@ import { decode } from "../utils/crypto.js"; import { PAYMASTER_ADDRESS } from "../constants/Pimlico.js"; import { APIKey } from "../models/api-key.js"; import * as EtherspotAbi from "../abi/EtherspotAbi.js"; +import { createPublicClient, getAddress, getContract, Hex, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; const metadataRoutes: FastifyPluginAsync = async (server) => { @@ -35,7 +36,7 @@ const metadataRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.INVALID_DATA}) let customPaymasters = []; let multiTokenPaymasters = []; - let privateKey = ''; + let privateKey: Hex = '0x'; let supportedNetworks; let sponsorName = '', sponsorImage = ''; if (!unsafeMode) { @@ -85,14 +86,20 @@ const metadataRoutes: FastifyPluginAsync = async (server) => { } const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); - const provider = new providers.JsonRpcProvider(networkConfig.bundler); - const signer = new Wallet(privateKey, provider) - const sponsorWalletBalance = await signer.getBalance(); - const sponsorAddress = await signer.getAddress(); + const provider = createPublicClient({ + transport: http(networkConfig.bundler) + }); + const signer = privateKeyToAccount(privateKey); + const sponsorWalletBalance = await provider.getBalance({address: signer.address}); + const sponsorAddress = getAddress(signer.address); //get native balance of the sponsor in the EtherSpotPaymaster-contract - const paymasterContract = new Contract(networkConfig.contracts.etherspotPaymasterAddress, EtherspotAbi.default, provider); - const sponsorBalance = await paymasterContract.getSponsorBalance(sponsorAddress); + const paymasterContract = getContract({ + abi: EtherspotAbi.default, + address: networkConfig.contracts.etherspotPaymasterAddress, + client: provider + }) + const sponsorBalance = await paymasterContract.read.getSponsorBalance([sponsorAddress]); const chainsSupported: {chainId: number, entryPoint: string}[] = []; if (supportedNetworks) { diff --git a/backend/src/routes/paymaster-routes.ts b/backend/src/routes/paymaster-routes.ts index 5667ffc..880f226 100644 --- a/backend/src/routes/paymaster-routes.ts +++ b/backend/src/routes/paymaster-routes.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { FastifyPluginAsync } from "fastify"; -import { BigNumber, Wallet, ethers, providers } from "ethers"; import { gql, request as GLRequest } from "graphql-request"; import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager"; import { Paymaster } from "../paymaster/index.js"; @@ -12,6 +11,8 @@ import { decode } from "../utils/crypto.js"; import { printRequest, getNetworkConfig } from "../utils/common.js"; import { SponsorshipPolicy } from "../models/sponsorship-policy.js"; import { DEFAULT_EP_VERSION, EPVersions, getEPVersion } from "../types/sponsorship-policy-dto.js"; +import { createPublicClient, getAddress, Hex, http, isAddress } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; const SUPPORTED_ENTRYPOINTS = { 'EPV_06': "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", @@ -56,7 +57,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server) => { } // eslint-disable-next-line no-fallthrough case 'pm_getPaymasterStubData': { - chainId = BigNumber.from(body.params[2]).toNumber(); + chainId = Number(BigInt(body.params[2])) context = body.params[3]; gasToken = context?.token ? context.token : null; mode = context?.mode ? String(context.mode) : "sponsor"; @@ -81,7 +82,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server) => { let customPaymasters = []; let multiTokenPaymasters = []; let multiTokenOracles = []; - let privateKey = ''; + let privateKey: Hex = '0x'; let supportedNetworks; let noOfTxns; let txnMode; @@ -175,7 +176,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server) => { !(customPaymasters[chainId] && customPaymasters[chainId][gasToken]) ) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK_TOKEN }) - if (gasToken && ethers.utils.isAddress(gasToken)) gasToken = ethers.utils.getAddress(gasToken) + if (gasToken && isAddress(gasToken)) gasToken = getAddress(gasToken) if (mode.toLowerCase() == 'multitoken' && !(multiTokenPaymasters[chainId] && multiTokenPaymasters[chainId][gasToken]) && @@ -189,11 +190,13 @@ const paymasterRoutes: FastifyPluginAsync = async (server) => { switch (mode.toLowerCase()) { case 'sponsor': { const date = new Date(); - const provider = new providers.JsonRpcProvider(networkConfig.bundler); - const signer = new Wallet(privateKey, provider) + const provider = createPublicClient({ + transport: http(networkConfig.bundler) + }); + const signer = privateKeyToAccount(privateKey); // get chainid from provider - const chainId = await provider.getNetwork(); + const chainId = await provider.getChainId(); // get wallet_address from api_key const apiKeyData = await server.apiKeyRepository.findOneByApiKey(api_key); @@ -202,24 +205,24 @@ const paymasterRoutes: FastifyPluginAsync = async (server) => { // get sponsorshipPolicy for the user from walletAddress and entrypoint version const sponsorshipPolicy: SponsorshipPolicy | null = await server.sponsorshipPolicyRepository.findOneByWalletAddressAndSupportedEPVersion(apiKeyData?.walletAddress, getEPVersion(epVersion)); if (!sponsorshipPolicy) { - const errorMessage: string = generateErrorMessage(ErrorMessage.ACTIVE_SPONSORSHIP_POLICY_NOT_FOUND, { walletAddress: apiKeyData?.walletAddress, epVersion: epVersion, chainId: chainId.chainId }); + const errorMessage: string = generateErrorMessage(ErrorMessage.ACTIVE_SPONSORSHIP_POLICY_NOT_FOUND, { walletAddress: apiKeyData?.walletAddress, epVersion: epVersion, chainId }); return reply.code(ReturnCode.FAILURE).send({ error: errorMessage }); } if (!Object.assign(new SponsorshipPolicy(), sponsorshipPolicy).isApplicable) { - const errorMessage: string = generateErrorMessage(ErrorMessage.NO_ACTIVE_SPONSORSHIP_POLICY_FOR_CURRENT_TIME, { walletAddress: apiKeyData?.walletAddress, epVersion: epVersion, chainId: chainId.chainId }); + const errorMessage: string = generateErrorMessage(ErrorMessage.NO_ACTIVE_SPONSORSHIP_POLICY_FOR_CURRENT_TIME, { walletAddress: apiKeyData?.walletAddress, epVersion: epVersion, chainId }); return reply.code(ReturnCode.FAILURE).send({ error: errorMessage }); } // get supported networks from sponsorshipPolicy const supportedNetworks: number[] | undefined | null = sponsorshipPolicy.enabledChains; - if (!supportedNetworks || !supportedNetworks.includes(chainId.chainId)) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); + if (!supportedNetworks || !supportedNetworks.includes(chainId)) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); // is chainId exists in supportedNetworks - if (!supportedNetworks.includes(chainId.chainId)) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); + if (!supportedNetworks.includes(chainId)) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); if (txnMode) { - const signerAddress = await signer.getAddress(); + const signerAddress = getAddress(signer.address); const IndexerData = await getIndexerData(signerAddress, userOp.sender, date.getMonth(), date.getFullYear(), noOfTxns, indexerEndpoint); if (IndexerData.length >= noOfTxns) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.QUOTA_EXCEEDED }) } @@ -252,7 +255,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server) => { case 'erc20': { if (entryPoint !== SUPPORTED_ENTRYPOINTS.EPV_06) throw new Error('Currently only 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 entryPoint address is supported') - let paymasterAddress: string; + let paymasterAddress: Hex; if (customPaymasters[chainId] && customPaymasters[chainId][gasToken]) paymasterAddress = customPaymasters[chainId][gasToken]; else paymasterAddress = PAYMASTER_ADDRESS[chainId][gasToken] result = await paymaster.pimlico(userOp, networkConfig.bundler, entryPoint, paymasterAddress, server.log); @@ -262,8 +265,10 @@ const paymasterRoutes: FastifyPluginAsync = async (server) => { if (entryPoint !== SUPPORTED_ENTRYPOINTS.EPV_06) throw new Error('Currently only 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 entryPoint address is supported') const date = new Date(); - const provider = new providers.JsonRpcProvider(networkConfig.bundler); - const signer = new Wallet(privateKey, provider) + const provider = createPublicClient({ + transport: http(networkConfig.bundler) + }); + const signer = privateKeyToAccount(privateKey); const validUntil = context.validUntil ? new Date(context.validUntil) : date; const validAfter = context.validAfter ? new Date(context.validAfter) : date; const hex = (Number((validUntil.valueOf() / 1000).toFixed(0)) + 600).toString(16); diff --git a/backend/src/routes/whitelist-routes.ts b/backend/src/routes/whitelist-routes.ts index beaf42d..dbb30db 100644 --- a/backend/src/routes/whitelist-routes.ts +++ b/backend/src/routes/whitelist-routes.ts @@ -1,14 +1,15 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { FastifyPluginAsync } from "fastify"; -import { ethers, Wallet } from "ethers"; import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager"; import { Paymaster } from "../paymaster/index.js"; import SupportedNetworks from "../../config.json" assert { type: "json" }; import ErrorMessage from "../constants/ErrorMessage.js"; import ReturnCode from "../constants/ReturnCode.js"; import { decode } from "../utils/crypto.js"; -import { printRequest, getNetworkConfig } from "../utils/common.js"; +import { printRequest, getNetworkConfig, getViemChain } from "../utils/common.js"; import { APIKey } from "../models/api-key.js"; +import { Hex, isAddress } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; const SUPPORTED_ENTRYPOINTS = { 'EPV_06': "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", @@ -39,7 +40,7 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { const api_key = query['apiKey'] ?? body.params[2]; if (!api_key) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - let privateKey = ''; + let privateKey: Hex; let supportedNetworks; if (!unsafeMode) { const AWSresponse = await client.send( @@ -66,14 +67,18 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { ) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); } - if (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) { + const chain = getViemChain(Number(chainId)); + if ( + (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) || + !chain + ) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); } const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_06); if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); - const validAddresses = address.every(ethers.utils.isAddress); + const validAddresses = address.every(isAddress); if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); - const result = await paymaster.whitelistAddresses(address, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey, chainId, server.log); + const result = await paymaster.whitelistAddresses(address, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey, chainId, chain, server.log); server.log.info(result, 'Response sent: '); if (body.jsonrpc) return reply.code(ReturnCode.SUCCESS).send({ jsonrpc: body.jsonrpc, id: body.id, result, error: null }) @@ -97,7 +102,7 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { const api_key = query['apiKey'] ?? body.params[2]; if (!api_key) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - let privateKey = ''; + let privateKey: Hex let supportedNetworks; if (!unsafeMode) { const AWSresponse = await client.send( @@ -124,14 +129,18 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { ) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); } - if (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) { + const chain = getViemChain(Number(chainId)); + if ( + (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) || + !chain + ) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); } const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_06); if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); - const validAddresses = address.every(ethers.utils.isAddress); + const validAddresses = address.every(isAddress); if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); - const result = await paymaster.removeWhitelistAddress(address, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey, chainId, server.log); + const result = await paymaster.removeWhitelistAddress(address, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey, chainId, chain, server.log); if (body.jsonrpc) return reply.code(ReturnCode.SUCCESS).send({ jsonrpc: body.jsonrpc, id: body.id, result, error: null }) return reply.code(ReturnCode.SUCCESS).send(result); @@ -154,7 +163,7 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { const api_key = query['apiKey'] ?? body.params[2]; if (!api_key) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - let privateKey = ''; + let privateKey: Hex; let supportedNetworks; if (!unsafeMode) { const AWSresponse = await client.send( @@ -175,7 +184,7 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if ( !accountAddress || - !ethers.utils.isAddress(accountAddress) || + !isAddress(accountAddress) || !chainId || isNaN(chainId) ) { @@ -244,7 +253,7 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { } const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_07); if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); - const validAddresses = address.every(ethers.utils.isAddress); + const validAddresses = address.every(isAddress); if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); const existingWhitelistRecord = await server.whitelistRepository.findOneByApiKeyAndPolicyId(api_key, policyId); @@ -311,7 +320,7 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if ( !accountAddress || - !ethers.utils.isAddress(accountAddress) || + !isAddress(accountAddress) || !chainId || isNaN(chainId) ) { @@ -353,7 +362,7 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { const api_key = query['apiKey'] ?? body.params[3]; if (!api_key) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - let privateKey = ''; + let privateKey: Hex; let supportedNetworks; if (!unsafeMode) { const AWSresponse = await client.send( @@ -385,9 +394,9 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { } const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_07); if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); - const validAddresses = address.every(ethers.utils.isAddress); + const validAddresses = address.every(isAddress); if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); - const signer = new Wallet(privateKey) + const signer = privateKeyToAccount(privateKey); if (policyId) { const policyRecord = await server.sponsorshipPolicyRepository.findOneById(policyId); if (!policyRecord || (policyRecord?.walletAddress !== signer.address)) return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.INVALID_SPONSORSHIP_POLICY_ID }) diff --git a/backend/src/server.ts b/backend/src/server.ts index f532946..c9a5c60 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -3,7 +3,6 @@ import fastify, { FastifyInstance } from 'fastify'; import fastifyHealthcheck from 'fastify-healthcheck'; import cors from '@fastify/cors'; import fastifyCron from 'fastify-cron'; -import { providers, ethers } from 'ethers'; import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; import fetch from 'node-fetch'; import sequelizePlugin from './plugins/sequelizePlugin.js'; @@ -11,7 +10,7 @@ import config from './plugins/config.js'; import EtherspotChainlinkOracleAbi from './abi/EtherspotChainlinkOracleAbi.js'; import PimlicoAbi from './abi/PimlicoAbi.js'; import PythOracleAbi from './abi/PythOracleAbi.js'; -import { getNetworkConfig } from './utils/common.js'; +import { getNetworkConfig, getViemChain } from './utils/common.js'; import { checkDeposit } from './utils/monitorTokenPaymaster.js'; import { APIKey } from './models/api-key.js'; import { APIKeyRepository } from './repository/api-key-repository.js'; @@ -24,6 +23,8 @@ import paymasterRoutes from './routes/paymaster-routes.js'; import pimlicoRoutes from './routes/pimlico-routes.js'; import whitelistRoutes from './routes/whitelist-routes.js'; import sponsorshipPolicyRoutes from './routes/sponsorship-policy-routes.js'; +import { createPublicClient, createWalletClient, encodeFunctionData, getContract, Hex, http, parseUnits } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; let server: FastifyInstance; @@ -100,33 +101,54 @@ const initializeServer = async (): Promise => { Object.keys(DEPLOYED_ERC20_PAYMASTERS).forEach(async (chain) => { //EP-v6 entrypoint address const networkConfig = getNetworkConfig(chain, '', "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); + const chainDef = getViemChain(+chain)!; if (networkConfig) { - const deployedPaymasters: string[] = DEPLOYED_ERC20_PAYMASTERS[chain]; - const provider = new providers.JsonRpcProvider(networkConfig.bundler); - const signer = new ethers.Wallet(process.env.CRON_PRIVATE_KEY ?? '', provider); + const deployedPaymasters: Hex[] = DEPLOYED_ERC20_PAYMASTERS[chain]; + const client = createPublicClient({ + transport: http(networkConfig.bundler), + chain: chainDef + }) + const account = privateKeyToAccount(process.env.CRON_PRIVATE_KEY as Hex ?? '0x') + const signer = createWalletClient({ + transport: http(networkConfig.bundler), + account, + chain: chainDef + }) deployedPaymasters.forEach(async (deployedPaymaster) => { - const paymasterContract = new ethers.Contract(deployedPaymaster, PimlicoAbi, signer) + const paymasterContract = getContract({ + abi: PimlicoAbi, + address: deployedPaymaster, + client + }) const pythMainnetChains = configData?.pythMainnetChainIds?.split(',') ?? []; const pythTestnetChains = configData?.pythTestnetChainIds?.split(',') ?? []; if (pythMainnetChains?.includes(chain) || pythTestnetChains?.includes(chain)) { try { - const oracleAddress = await paymasterContract.tokenOracle(); - const oracleContract = new ethers.Contract(oracleAddress, PythOracleAbi, provider) - const priceId = await oracleContract.priceLocator(); + const oracleAddress = await paymasterContract.read.tokenOracle(); + const oracleContract = getContract({ + abi: PythOracleAbi, + address: oracleAddress, + client + }); + const priceId = await oracleContract.read.priceLocator(); const TESTNET_API_URL = configData?.pythTestnetUrl; const MAINNET_API_URL = configData?.pythMainnetUrl; const requestURL = `${chain === '5000' ? MAINNET_API_URL : TESTNET_API_URL}${priceId}`; const response = await fetch(requestURL); const vaa: any = await response.json(); - const priceData = '0x' + Buffer.from(vaa[0], 'base64').toString('hex'); - const updateFee = await oracleContract.getUpdateFee([priceData]); - const data = oracleContract.interface.encodeFunctionData('updatePrice', [[priceData]]) + const priceData: Hex = `0x${Buffer.from(vaa[0], 'base64').toString('hex')}`; + const updateFee = await oracleContract.read.getUpdateFee([[priceData]]); + const data = encodeFunctionData({ + abi: PythOracleAbi, + functionName: 'updatePrice', + args: [[priceData]] + }); const tx = await signer.sendTransaction({ to: oracleAddress, data: data, value: updateFee }); - await tx.wait(); + await client.waitForTransactionReceipt({hash: tx}); } catch (err) { server.log.error(err); } @@ -143,16 +165,19 @@ const initializeServer = async (): Promise => { if (customChainlinkDeployments.includes(deployedPaymaster)) { const coingeckoId = coingeckoIds[chain][customChainlinkDeployments.indexOf(deployedPaymaster)] const response: any = await (await fetch(`${configData.coingeckoApiUrl}${coingeckoId}`)).json(); - const price = ethers.utils.parseUnits(response[coingeckoId].usd.toString(), 8); + const price = parseUnits(response[coingeckoId].usd.toString(), 8); if (price) { - const oracleAddress = await paymasterContract.tokenOracle(); - const oracleContract = new ethers.Contract(oracleAddress, EtherspotChainlinkOracleAbi, provider) - const data = oracleContract.interface.encodeFunctionData('fulfillPriceData', [price]) + const oracleAddress = await paymasterContract.read.tokenOracle(); + const data = encodeFunctionData({ + abi: EtherspotChainlinkOracleAbi, + functionName: 'fulfillPriceData', + args: [price] + }) const tx = await signer.sendTransaction({ to: oracleAddress, data: data, }); - await tx.wait(); + await client.waitForTransactionReceipt({hash: tx}); } } } catch (err) { @@ -160,7 +185,7 @@ const initializeServer = async (): Promise => { } } try { - await paymasterContract.updatePrice(); + await paymasterContract.write.updatePrice({account}); server.log.info('Price Updated for ' + chain); } catch (err) { server.log.error('Err on updating Price on paymaster' + err); diff --git a/backend/src/types/entrypoint.ts b/backend/src/types/entrypoint.ts new file mode 100644 index 0000000..f4b43ec --- /dev/null +++ b/backend/src/types/entrypoint.ts @@ -0,0 +1,15 @@ +import { Hex } from "viem"; + +export interface UserOperationStruct { + sender: Hex; + nonce: bigint; + initCode: Hex; + callData: Hex; + callGasLimit: bigint; + verificationGasLimit: bigint; + preVerificationGas: bigint; + maxFeePerGas: bigint; + maxPriorityFeePerGas: bigint; + paymasterAndData: Hex; + signature: Hex; +} \ No newline at end of file diff --git a/backend/src/utils/common.ts b/backend/src/utils/common.ts index 5a529f9..8d0eacc 100644 --- a/backend/src/utils/common.ts +++ b/backend/src/utils/common.ts @@ -1,7 +1,8 @@ import { FastifyBaseLogger, FastifyRequest } from "fastify"; -import { BigNumber, ethers } from "ethers"; import SupportedNetworks from "../../config.json" assert { type: "json" }; import { EtherscanResponse, getEtherscanFeeResponse } from "./interface.js"; +import { parseUnits } from "viem"; +import * as chains from "viem/chains"; export function printRequest(methodName: string, request: FastifyRequest, log: FastifyBaseLogger) { log.info(methodName, "called: "); @@ -41,16 +42,16 @@ export async function getEtherscanFee(chainId: number, log?: FastifyBaseLogger): const response: EtherscanResponse = await data.json(); if (response.result && typeof response.result === "object" && response.status === "1") { if(log) log.info('setting maxFeePerGas and maxPriorityFeePerGas as received') - const maxFeePerGas = ethers.utils.parseUnits(response.result.suggestBaseFee, 'gwei') - const fastGasPrice = ethers.utils.parseUnits(response.result.FastGasPrice, 'gwei') + const maxFeePerGas = parseUnits(response.result.suggestBaseFee, 9); + const fastGasPrice = parseUnits(response.result.FastGasPrice, 9); return { - maxPriorityFeePerGas: fastGasPrice.sub(maxFeePerGas), + maxPriorityFeePerGas: fastGasPrice - maxFeePerGas, maxFeePerGas, gasPrice: maxFeePerGas, } } if (response.result && typeof response.result === "string" && response.jsonrpc) { - const gasPrice = BigNumber.from(response.result) + const gasPrice = BigInt(response.result) if(log) log.info('setting gas price as received') return { maxFeePerGas: gasPrice, @@ -72,3 +73,11 @@ export async function getEtherscanFee(chainId: number, log?: FastifyBaseLogger): } } +export function getViemChain(chainId: number): chains.Chain | undefined { + for(const chain of Object.values(chains)) { + if (chain.id === chainId) { + return chain; + } + } + return undefined; +} diff --git a/backend/src/utils/interface.ts b/backend/src/utils/interface.ts index 274efa4..d19c54f 100644 --- a/backend/src/utils/interface.ts +++ b/backend/src/utils/interface.ts @@ -1,5 +1,3 @@ -import { BigNumber } from "ethers"; - export interface EtherscanResponse { jsonrpc?: string; id?: string; @@ -16,7 +14,7 @@ export interface EtherscanResponse { } export interface getEtherscanFeeResponse { - maxFeePerGas: BigNumber; - maxPriorityFeePerGas: BigNumber; - gasPrice: BigNumber; + maxFeePerGas: bigint; + maxPriorityFeePerGas: bigint; + gasPrice: bigint; } \ No newline at end of file diff --git a/backend/src/utils/monitorTokenPaymaster.ts b/backend/src/utils/monitorTokenPaymaster.ts index b7c4af7..8efb7cd 100644 --- a/backend/src/utils/monitorTokenPaymaster.ts +++ b/backend/src/utils/monitorTokenPaymaster.ts @@ -1,15 +1,21 @@ import { FastifyBaseLogger } from "fastify"; -import { ethers, providers } from "ethers"; import fetch from 'node-fetch'; import EtherspotAbi from "../abi/EtherspotAbi.js"; +import { createPublicClient, formatEther, getContract, Hex, http, parseEther } from "viem"; -export async function checkDeposit(paymasterAddress: string, bundlerUrl: string, webhookUrl: string, thresholdValue: string, chainId: number, log: FastifyBaseLogger) { +export async function checkDeposit(paymasterAddress: Hex, bundlerUrl: string, webhookUrl: string, thresholdValue: string, chainId: number, log: FastifyBaseLogger) { try { - const provider = new providers.JsonRpcProvider(bundlerUrl); - const contract = new ethers.Contract(paymasterAddress, EtherspotAbi, provider); - const currentDeposit = await contract.getDeposit(); - if (ethers.utils.parseEther(thresholdValue).gte(currentDeposit)) { - const body = { message: `Balance below threshold. Please deposit on tokenPaymasterAddress: ${paymasterAddress} chainId: ${chainId}`, currentDeposit: ethers.utils.formatEther(currentDeposit) } + const client = createPublicClient({ + transport: http(bundlerUrl) + }) + const contract = getContract({ + abi: EtherspotAbi, + address: paymasterAddress, + client + }) + const currentDeposit = await contract.read.getDeposit(); + if (parseEther(thresholdValue) >= currentDeposit) { + const body = { message: `Balance below threshold. Please deposit on tokenPaymasterAddress: ${paymasterAddress} chainId: ${chainId}`, currentDeposit: formatEther(currentDeposit) } await fetch(webhookUrl, { method: 'POST', body: JSON.stringify(body)