diff --git a/modules/4337/scripts/runOp.ts b/modules/4337/scripts/runOp.ts index af0bab80c..c7f846cf6 100644 --- a/modules/4337/scripts/runOp.ts +++ b/modules/4337/scripts/runOp.ts @@ -48,11 +48,18 @@ const runOp = async () => { // All other methods return an error const accountAbstractionProvider = new MultiProvider4337(BUNDLER_URL!, ethers.provider) const entryPoints = await getSupportedEntryPoints(accountAbstractionProvider) - const entryPoint = entryPoints[0] const moduleAddress = MODULE_ADDRESS ?? (await getSafe4337Module().then((module) => module.getAddress())) - const moduleSupportedEntrypoint = await user1.call({ to: moduleAddress, data: INTERFACES.encodeFunctionData('SUPPORTED_ENTRYPOINT') }) + const [moduleSupportedEntrypoint] = ethers.AbiCoder.defaultAbiCoder().decode( + ["address"], + await user1.call({ to: moduleAddress, data: INTERFACES.encodeFunctionData('SUPPORTED_ENTRYPOINT') }), + ); console.log({ moduleAddress, moduleSupportedEntrypoint }) + const entryPoint = entryPoints.find((entry) => entry === moduleSupportedEntrypoint); + if (entryPoint === undefined) { + throw new Error('Module does not support any of the available entry points') + } + const proxyCreationCode = (await callInterface(PROXY_FACTORY_ADDRESS, 'proxyCreationCode'))[0] const globalConfig: GlobalConfig = { @@ -68,10 +75,10 @@ const runOp = async () => { safe.connect(accountAbstractionProvider) - console.log(safe.address) + console.log({ safe: safe.address }) const safeBalance = await ethers.provider.getBalance(safe.address) const minBalance = ethers.parseEther('0.01') - console.log(safeBalance) + console.log({ safeBalance }) if (safeBalance < minBalance) { await (await user1.sendTransaction({ to: safe.address, value: ethers.parseEther('0.01') })).wait() } diff --git a/modules/4337/src/utils/safe.ts b/modules/4337/src/utils/safe.ts index ae0bd55b9..34ab77f6f 100644 --- a/modules/4337/src/utils/safe.ts +++ b/modules/4337/src/utils/safe.ts @@ -3,7 +3,7 @@ import { Provider, Signer, ethers } from 'ethers' // Import from Safe contracts repo once it is upgraded to ethers v6 and can be installed via npm import { MetaTransaction, SafeSignature, SignedSafeTransaction, buildSignatureBytes } from './execution' -import { PackedUserOperation, UserOperation, EIP712_SAFE_OPERATION_TYPE, packGasParameters, unpackUserOperation } from './userOp' +import { PackedUserOperation, UserOperation, EIP712_SAFE_OPERATION_TYPE, packGasParameters, unpackUserOperation, unpackInitCode } from './userOp' export { MultiProvider4337 } @@ -86,7 +86,6 @@ const callInterface = async (provider: Provider, contract: string, method: strin to: contract, data: INTERFACES.encodeFunctionData(method, params), }) - console.log(result) return INTERFACES.decodeFunctionResult(method, result) } @@ -115,6 +114,7 @@ export class Safe4337Operation { { safe: this.safe.address, callData: actionCalldata(this.action), + paymasterAndData: '0x', entryPoint: this.globalConfig.entryPoint, ...this.params, }, @@ -160,12 +160,13 @@ export class Safe4337Operation { { safe: this.safe.address, callData: actionCalldata(this.action), + paymasterAndData: '0x', entryPoint: this.globalConfig.entryPoint, ...this.params, }, ), }) - console.log(this.signatures) + console.log({ signatures: this.signatures }) } static async build( @@ -174,22 +175,25 @@ export class Safe4337Operation { action: MetaTransaction, globalConfig: GlobalConfig, ): Promise { - const initCode = (await safe.isDeployed()) ? '0x' : safe.getInitCode() + const initCode = (await safe.isDeployed()) ? "0x" : safe.getInitCode(); const nonce = (await callInterface(provider, globalConfig.entryPoint, 'getNonce', [safe.address, 0]))[0] const estimateOperation = { sender: safe.address, callData: actionCalldata(action), - paymasterAndData: '0x', nonce: ethers.toBeHex(nonce), - initCode, - signature: '0x'.padEnd(130, 'a'), + ...unpackInitCode({ initCode }), // For some providers we need to set some really high values to allow estimation - preVerificationGas: ethers.toBeHex(1000000), - verificationGasLimit: ethers.toBeHex(1000000), callGasLimit: ethers.toBeHex(10000000), - // To keep the required funds low, the gas fee is set close to the minimum + verificationGasLimit: ethers.toBeHex(1000000), + preVerificationGas: ethers.toBeHex(1000000), + // User arbitrary gas fee values - note that we use lower values in order to reduce the amount + // of gas fees used in tests; when estimating with a real bundler, they will choose these for + // for us anyway. maxFeePerGas: '0x10', maxPriorityFeePerGas: '0x10', + // Use dummy signature that makes ECRECOVER get called in order to have slightly more accurate + // estimates for single signer operations. + signature: `0x${'aa'.repeat(32)}${'bb'.repeat(32)}1b`, } const estimates = await provider.send('eth_estimateUserOperationGas', [ { @@ -197,12 +201,15 @@ export class Safe4337Operation { }, globalConfig.entryPoint, ]) - console.log(estimates) - - const feeData = await provider.getFeeData() + console.log({ estimates }) + const feeData = { ...(await provider.getFeeData()) }; if (!feeData.maxFeePerGas || !feeData.maxPriorityFeePerGas) throw Error('Missing fee data') + // Some bundlers require higher priority fees and use non-standard APIs for this. Instead, just + // bump the priority fee by 20% to ensure that the operation is accepted by the bundler. + feeData.maxPriorityFeePerGas += (feeData.maxPriorityFeePerGas * 20n) / 100n + const params: OperationParams = { nonce, initCode,