diff --git a/examples/4337-gas-metering/package.json b/examples/4337-gas-metering/package.json index 8c500411..3f01e6c2 100644 --- a/examples/4337-gas-metering/package.json +++ b/examples/4337-gas-metering/package.json @@ -57,7 +57,7 @@ "dotenv": "16.4.5", "ethers": "^6.12.1", "permissionless": "0.1.29", - "viem": "2.10.8" + "viem": "2.11.0" }, "devDependencies": { "@types/node": "20.12.12", diff --git a/examples/4337-gas-metering/pimlico/README.md b/examples/4337-gas-metering/pimlico/README.md index 8c8fe260..448aa81e 100644 --- a/examples/4337-gas-metering/pimlico/README.md +++ b/examples/4337-gas-metering/pimlico/README.md @@ -36,7 +36,6 @@ Gas Used (Account or Paymaster): 423757 Gas Used (Transaction): 409872 ``` - ## Safe Deployment with Pimlico Paymaster (Own Sponsorship) ``` diff --git a/examples/4337-gas-metering/pimlico/pimlico.ts b/examples/4337-gas-metering/pimlico/pimlico.ts index 3a247ee8..287bbff5 100644 --- a/examples/4337-gas-metering/pimlico/pimlico.ts +++ b/examples/4337-gas-metering/pimlico/pimlico.ts @@ -9,7 +9,7 @@ import { baseSepolia, sepolia } from 'viem/chains' import { getAccountAddress, getAccountInitCode } from '../utils/safe' import { SAFE_ADDRESSES_MAP } from '../utils/address' import { UserOperation, submitUserOperationPimlico, signUserOperation, txTypes, createCallData } from '../utils/userOps' -import { transferETH } from '../utils/nativeTransfer' +import { getERC20Decimals, getERC20Balance, transferERC20Token } from '../utils/erc20' dotenv.config() // For Paymaster Identification. @@ -171,7 +171,7 @@ const txCallData: `0x${string}` = await createCallData( ) // Create User Operation Object. -const userOp: UserOperation = { +const sponsoredUserOperation: UserOperation = { sender: senderAddress, nonce: newNonce, factory: contractCode ? undefined : chainAddresses.SAFE_PROXY_FACTORY_ADDRESS, @@ -190,68 +190,70 @@ const userOp: UserOperation = { } // Sign the User Operation. -userOp.signature = await signUserOperation(userOp, signer, chainID, ENTRYPOINT_ADDRESS_V07, chainAddresses.SAFE_4337_MODULE_ADDRESS) +sponsoredUserOperation.signature = await signUserOperation( + sponsoredUserOperation, + signer, + chainID, + ENTRYPOINT_ADDRESS_V07, + chainAddresses.SAFE_4337_MODULE_ADDRESS, +) -// Fetch Max Gas Price from Bundler. +// Estimate gas and gas price for the User Operation. +const gasEstimate = await bundlerClient.estimateUserOperationGas({ + userOperation: sponsoredUserOperation, +}) const maxGasPriceResult = await bundlerClient.getUserOperationGasPrice() -userOp.maxFeePerGas = maxGasPriceResult.fast.maxFeePerGas -userOp.maxPriorityFeePerGas = maxGasPriceResult.fast.maxPriorityFeePerGas +sponsoredUserOperation.maxFeePerGas = maxGasPriceResult.fast.maxFeePerGas +sponsoredUserOperation.maxPriorityFeePerGas = maxGasPriceResult.fast.maxPriorityFeePerGas + +sponsoredUserOperation.callGasLimit = gasEstimate.callGasLimit +sponsoredUserOperation.verificationGasLimit = gasEstimate.verificationGasLimit +sponsoredUserOperation.preVerificationGas = gasEstimate.preVerificationGas +sponsoredUserOperation.paymasterVerificationGasLimit = gasEstimate.paymasterVerificationGasLimit +sponsoredUserOperation.paymasterPostOpGasLimit = gasEstimate.paymasterPostOpGasLimit // If Paymaster is used, then sponsor the User Operation. if (usePaymaster) { const sponsorResult = await pimlicoPaymasterClient.sponsorUserOperation({ userOperation: { - sender: userOp.sender, - nonce: userOp.nonce, - factory: userOp.factory, - factoryData: userOp.factoryData, - callData: userOp.callData, - maxFeePerGas: userOp.maxFeePerGas, - maxPriorityFeePerGas: userOp.maxPriorityFeePerGas, - signature: userOp.signature, + sender: sponsoredUserOperation.sender, + nonce: sponsoredUserOperation.nonce, + factory: sponsoredUserOperation.factory, + factoryData: sponsoredUserOperation.factoryData, + callData: sponsoredUserOperation.callData, + maxFeePerGas: sponsoredUserOperation.maxFeePerGas, + maxPriorityFeePerGas: sponsoredUserOperation.maxPriorityFeePerGas, + signature: sponsoredUserOperation.signature, }, sponsorshipPolicyId: policyID, }) - userOp.callGasLimit = sponsorResult.callGasLimit - userOp.verificationGasLimit = sponsorResult.verificationGasLimit - userOp.preVerificationGas = sponsorResult.preVerificationGas - userOp.paymasterData = sponsorResult.paymasterData - userOp.paymasterVerificationGasLimit = sponsorResult.paymasterVerificationGasLimit - userOp.paymasterPostOpGasLimit = sponsorResult.paymasterPostOpGasLimit + sponsoredUserOperation.callGasLimit = sponsorResult.callGasLimit + sponsoredUserOperation.verificationGasLimit = sponsorResult.verificationGasLimit + sponsoredUserOperation.preVerificationGas = sponsorResult.preVerificationGas + sponsoredUserOperation.paymasterData = sponsorResult.paymasterData + sponsoredUserOperation.paymasterVerificationGasLimit = sponsorResult.paymasterVerificationGasLimit + sponsoredUserOperation.paymasterPostOpGasLimit = sponsorResult.paymasterPostOpGasLimit } else { - userOp.paymaster = undefined - - // Estimate Gas for the User Operation. - const gasEstimate = await bundlerClient.estimateUserOperationGas({ - userOperation: userOp, - }) - - userOp.callGasLimit = gasEstimate.callGasLimit - userOp.verificationGasLimit = gasEstimate.verificationGasLimit - userOp.preVerificationGas = gasEstimate.preVerificationGas - - // Check Sender ETH Balance. - let senderETHBalance = await publicClient.getBalance({ address: senderAddress }) - console.log('\nSender ETH Balance:', ethers.formatEther(senderETHBalance)) - - // Checking required preFund. - const requiredPrefund = getRequiredPrefund({ userOperation: userOp, entryPoint: ENTRYPOINT_ADDRESS_V07 }) - console.log('\nRequired Prefund:', ethers.formatEther(requiredPrefund)) - - const requiredBalance = requiredPrefund + (txType == 'native-transfer' ? parseEther('0.000001') : 0n) - - if (senderETHBalance < requiredBalance) { - await transferETH(publicClient, signer, senderAddress, requiredBalance - senderETHBalance, chain, paymaster) - while (senderETHBalance < requiredBalance) { + // Fetch USDC balance of sender + const usdcDecimals = BigInt(await getERC20Decimals(usdcTokenAddress, publicClient)) + const usdcAmount = 10n ** usdcDecimals + let senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress) + console.log('\nSafe Wallet USDC Balance:', Number(senderUSDCBalance / usdcAmount)) + + if (senderUSDCBalance < BigInt(1) * usdcAmount) { + console.log('\nTransferring 1 USDC Token for paying the Paymaster from Sender to Safe.') + await transferERC20Token(usdcTokenAddress, publicClient, signer, senderAddress, BigInt(1) * usdcAmount, chain, paymaster) + while (senderUSDCBalance < BigInt(1) * usdcAmount) { await setTimeout(15000) - senderETHBalance = await publicClient.getBalance({ address: senderAddress }) + senderUSDCBalance = await getERC20Balance(usdcTokenAddress, publicClient, senderAddress) } + console.log('\nUpdated Safe Wallet USDC Balance:', Number(senderUSDCBalance / usdcAmount)) } } // Sign the User Operation. -userOp.signature = await signUserOperation(userOp, signer, chainID, ENTRYPOINT_ADDRESS_V07, chainAddresses.SAFE_4337_MODULE_ADDRESS) +sponsoredUserOperation.signature = await signUserOperation(sponsoredUserOperation, signer, chainID, ENTRYPOINT_ADDRESS_V07, chainAddresses.SAFE_4337_MODULE_ADDRESS) // Submit the User Operation. -await submitUserOperationPimlico(userOp, bundlerClient, ENTRYPOINT_ADDRESS_V07, chain) +await submitUserOperationPimlico(sponsoredUserOperation, bundlerClient, ENTRYPOINT_ADDRESS_V07, chain) diff --git a/examples/4337-gas-metering/utils/erc20.ts b/examples/4337-gas-metering/utils/erc20.ts index f05939c5..fa7bbff8 100644 --- a/examples/4337-gas-metering/utils/erc20.ts +++ b/examples/4337-gas-metering/utils/erc20.ts @@ -1,5 +1,5 @@ import dotenv from 'dotenv' -import { http, Address, encodeFunctionData, createWalletClient, PrivateKeyAccount, PublicClient } from 'viem' +import { HttpTransport, http, Address, encodeFunctionData, createWalletClient, PrivateKeyAccount, PublicClient } from 'viem' import { baseSepolia, goerli, polygonMumbai, sepolia } from 'viem/chains' import { ERC20_TOKEN_APPROVE_ABI, @@ -32,7 +32,10 @@ export const generateTransferCallData = (to: Address, value: bigint) => { return transferData } -export const getERC20Decimals = async (erc20TokenAddress: Address, publicClient: PublicClient): Promise => { +export const getERC20Decimals = async ( + erc20TokenAddress: Address, + publicClient: PublicClient, +): Promise => { const erc20Decimals = (await publicClient.readContract({ abi: ERC20_TOKEN_DECIMALS_ABI, address: erc20TokenAddress, @@ -42,7 +45,11 @@ export const getERC20Decimals = async (erc20TokenAddress: Address, publicClient: return erc20Decimals } -export const getERC20Balance = async (erc20TokenAddress: Address, publicClient: PublicClient, owner: Address): Promise => { +export const getERC20Balance = async ( + erc20TokenAddress: Address, + publicClient: PublicClient, + owner: Address, +): Promise => { const senderERC20Balance = (await publicClient.readContract({ abi: ERC20_TOKEN_BALANCE_OF_ABI, address: erc20TokenAddress, @@ -55,7 +62,7 @@ export const getERC20Balance = async (erc20TokenAddress: Address, publicClient: export const mintERC20Token = async ( erc20TokenAddress: Address, - publicClient: PublicClient, + publicClient: PublicClient, signer: PrivateKeyAccount, to: Address, amount: bigint, @@ -132,7 +139,7 @@ export const mintERC20Token = async ( export const transferERC20Token = async ( erc20TokenAddress: Address, - publicClient: PublicClient, + publicClient: PublicClient, signer: PrivateKeyAccount, to: Address, amount: bigint, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d82567d..79459cde 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,10 +70,10 @@ importers: version: 6.12.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) permissionless: specifier: 0.1.29 - version: 0.1.29(viem@2.10.8(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)) + version: 0.1.29(viem@2.11.0(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)) viem: - specifier: 2.10.8 - version: 2.10.8(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) + specifier: 2.11.0 + version: 2.11.0(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) devDependencies: '@types/node': specifier: 20.12.12 @@ -4827,8 +4827,8 @@ packages: typescript: optional: true - viem@2.10.8: - resolution: {integrity: sha512-ttCXlDmjjcZ8M/eJezXFzDtHj+RFOjEQ3elmXnCC7suXo/y8CuIM1LrIoyUFk7LKIE5E+bzmWUErS4u/MQBtpQ==} + viem@2.11.0: + resolution: {integrity: sha512-JwGxcpr3pUlquXWkRdXAWTOaY3RDPftrkxksiIWlPEgLWRYOLuFkJyxmvLTE6ZY3njww8vmZP97UqBt0R+B/xw==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: @@ -9584,9 +9584,9 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 - permissionless@0.1.29(viem@2.10.8(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)): + permissionless@0.1.29(viem@2.11.0(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)): dependencies: - viem: 2.10.8(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.11.0(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) picocolors@1.0.1: {} @@ -10541,7 +10541,7 @@ snapshots: - utf-8-validate - zod - viem@2.10.8(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8): + viem@2.11.0(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8): dependencies: '@adraffy/ens-normalize': 1.10.0 '@noble/curves': 1.2.0