diff --git a/packages/entrykit/src/EntryKitConfigProvider.tsx b/packages/entrykit/src/EntryKitConfigProvider.tsx index 546016364b..7937295ca9 100644 --- a/packages/entrykit/src/EntryKitConfigProvider.tsx +++ b/packages/entrykit/src/EntryKitConfigProvider.tsx @@ -3,7 +3,7 @@ import { createContext, useContext, type ReactNode } from "react"; import { RainbowKitProvider, midnightTheme } from "@rainbow-me/rainbowkit"; import { EntryKitConfig } from "./config/output"; import { Chain } from "viem"; -import { useChains } from "wagmi"; +import { useChains, useClient } from "wagmi"; import { getBundlerTransport } from "./getBundlerTransport"; type ContextValue = EntryKitConfig & { @@ -29,7 +29,9 @@ export function EntryKitConfigProvider({ config, children }: Props) { if (!chain) throw new Error(`Could not find configured chain for chain ID ${config.chainId}.`); // This throws when no we can't find a bundler to talk to, so use it to validate the chain. - getBundlerTransport(chain); + const client = useClient({ chainId: config.chainId }); + if (!client) throw new Error(`Could not get client for chain ID ${config.chainId}.`); + getBundlerTransport(client); return ( wiresaw(transport)); return createConfig({ connectors, chains: config.chains, - transports: config.transports, + transports, pollingInterval: config.pollingInterval, }) as never; } diff --git a/packages/entrykit/src/getBundlerTransport.ts b/packages/entrykit/src/getBundlerTransport.ts index d639af73f3..6b3dc4b1b9 100644 --- a/packages/entrykit/src/getBundlerTransport.ts +++ b/packages/entrykit/src/getBundlerTransport.ts @@ -1,25 +1,62 @@ import { transactionQueue } from "@latticexyz/common/actions"; -import { Chain, createClient, fallback, http, keccak256, stringToHex, webSocket } from "viem"; +import { Chain, Client, Transport, createClient, http, keccak256, stringToHex } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { userOpExecutor } from "./quarry/transports/userOpExecutor"; +import { wiresaw } from "./quarry/transports/wiresaw"; -export function getBundlerTransport(chain: Chain) { - const bundlerHttpUrl = chain.rpcUrls.bundler?.http[0]; +export function getBundlerTransport(client: Client) { + const bundlerHttpUrl = client.chain.rpcUrls.bundler?.http[0]; // TODO: bundler websocket + // TODO: do chain checks/conditionals inside the transports const bundlerTransport = bundlerHttpUrl - ? http(bundlerHttpUrl) - : chain.id === 31337 + ? client.chain.id === 17420 + ? wiresaw(http(bundlerHttpUrl)) + : client.chain.id === 690 || client.chain.id === 17069 + ? alto(http(bundlerHttpUrl)) + : http(bundlerHttpUrl) + : client.chain.id === 31337 ? userOpExecutor({ executor: createClient({ - chain, - transport: fallback([webSocket(), http()]), + chain: client.chain, + transport: () => ({ config: client.transport, request: client.request }), account: privateKeyToAccount(keccak256(stringToHex("local user op executor"))), pollingInterval: 10, }).extend(transactionQueue()), }) : null; if (!bundlerTransport) { - throw new Error(`Chain ${chain.id} config did not include a bundler RPC URL.`); + throw new Error(`Chain ${client.chain.id} config did not include a bundler RPC URL.`); } return bundlerTransport; } + +function alto(createTransport: transport): transport { + return ((opts) => { + const transport = createTransport(opts); + const request: typeof transport.request = async (req) => { + if (req.method === "eth_sendUserOperation") { + const { transactionHash, userOpHash } = await transport.request({ + ...req, + method: "pimlico_sendUserOperationNow", + }); + return userOpHash; + } + + if (req.method === "eth_estimateUserOperationGas") { + try { + return await transport.request(req); + } catch (error) { + // fill in missing error data from alto so viem can parse the error + error.data ??= error.details; + throw error; + } + } + + return transport.request(req); + }; + return { + ...transport, + request, + }; + }) as transport; +} diff --git a/packages/entrykit/src/getSessionClient.ts b/packages/entrykit/src/getSessionClient.ts index cfd186ebe8..ef196e7eb3 100644 --- a/packages/entrykit/src/getSessionClient.ts +++ b/packages/entrykit/src/getSessionClient.ts @@ -21,7 +21,7 @@ export async function getSessionClient({ } const bundlerClient = createBundlerClient({ - transport: getBundlerTransport(client.chain), + transport: getBundlerTransport(client), client, account: sessionAccount, }); diff --git a/packages/entrykit/src/quarry/transports/methods/estimateUserOperationGas.ts b/packages/entrykit/src/quarry/transports/methods/estimateUserOperationGas.ts index 4e00139763..00555e83c9 100644 --- a/packages/entrykit/src/quarry/transports/methods/estimateUserOperationGas.ts +++ b/packages/entrykit/src/quarry/transports/methods/estimateUserOperationGas.ts @@ -1,20 +1,2901 @@ -import { BundlerRpcSchema } from "viem"; -import { formatUserOperationRequest } from "viem/account-abstraction"; +import { BundlerRpcSchema, RpcUserOperation } from "viem"; +import { + entryPoint07Abi, + entryPoint07Address, + formatUserOperation, + formatUserOperationRequest, + toPackedUserOperation, +} from "viem/account-abstraction"; import { getRpcMethod } from "../common"; +import { ConnectedClient } from "../../../common"; +import { estimateContractGas, getCode, simulateContract } from "viem/actions"; +import { getAction } from "viem/utils"; // TODO: revisit after demo (don't hardcode gas) +const userOpGas = { + callGasLimit: 20_000_000n, + preVerificationGas: 200_000n, + verificationGasLimit: 2_000_000n, + paymasterVerificationGasLimit: 200_000n, + paymasterPostOpGasLimit: 200_000n, +} as const; type rpcMethod = getRpcMethod; -export async function estimateUserOperationGas( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _params: rpcMethod["Parameters"], -): Promise { - return formatUserOperationRequest({ - callGasLimit: 20_000_000n, - preVerificationGas: 200_000n, - verificationGasLimit: 2_000_000n, - paymasterVerificationGasLimit: 200_000n, - paymasterPostOpGasLimit: 200_000n, - }); +export async function estimateUserOperationGas({ + executor, + rpcUserOp, +}: { + executor: ConnectedClient; + rpcUserOp: RpcUserOperation<"0.7">; +}): Promise { + const userOp = formatUserOperation(rpcUserOp); + // console.log("user op", userOp); + const packedUserOp = toPackedUserOperation(userOp); + + // TODO: figure out how to decode `handleOps` errors + // try { + // await getAction( + // executor, + // estimateContractGas, + // "estimateContractGas", + // )({ + // abi: entryPoint07Abi, + // address: entryPoint07Address, + // // address: "0xBbe8A301FbDb2a4CD58c4A37c262ecef8f889c47", + // functionName: "handleOps", + // args: [[packedUserOp], executor.account.address], + // account: executor.account, + // }); + // } catch (error) { + // console.log("estimate user op gas error", { error }); + // } + + // try { + // await getAction( + // executor, + // simulateContract, + // "simulateContract", + // )({ + // abi: entryPoint07Abi, + // address: entryPoint07Address, + // functionName: "handleOps", + // args: [[packedUserOp], executor.account.address], + // account: executor.account, + // }); + // } catch (error) { + // console.log("simulate user op error", { error }); + // } + + // try { + // await getAction( + // executor, + // simulateContract, + // "simulateContract", + // )({ + // abi: entrypointSimulationsAbi, + // address: "0xBbe8A301FbDb2a4CD58c4A37c262ecef8f889c47", + // functionName: "simulateHandleOp", + // args: [packedUserOp], + // account: executor.account, + // }); + // } catch (error) { + // console.log("simulate user op error", { error }); + // } + + return formatUserOperationRequest(userOpGas); } + +const entrypointSimulationsAbi = [ + { + type: "constructor", + inputs: [], + stateMutability: "nonpayable", + }, + { + type: "receive", + stateMutability: "payable", + }, + { + type: "function", + name: "_accountValidation", + inputs: [ + { + name: "opIndex", + type: "uint256", + internalType: "uint256", + }, + { + name: "userOp", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "outOpInfo", + type: "tuple", + internalType: "struct EntryPoint.UserOpInfo", + components: [ + { + name: "mUserOp", + type: "tuple", + internalType: "struct EntryPoint.MemoryUserOp", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "verificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "callGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterPostOpGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymaster", + type: "address", + internalType: "address", + }, + { + name: "maxFeePerGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "maxPriorityFeePerGas", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "userOpHash", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "prefund", + type: "uint256", + internalType: "uint256", + }, + { + name: "contextOffset", + type: "uint256", + internalType: "uint256", + }, + { + name: "preOpGas", + type: "uint256", + internalType: "uint256", + }, + ], + }, + ], + outputs: [ + { + name: "validationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "_paymasterValidation", + inputs: [ + { + name: "opIndex", + type: "uint256", + internalType: "uint256", + }, + { + name: "userOp", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "outOpInfo", + type: "tuple", + internalType: "struct EntryPoint.UserOpInfo", + components: [ + { + name: "mUserOp", + type: "tuple", + internalType: "struct EntryPoint.MemoryUserOp", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "verificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "callGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterPostOpGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymaster", + type: "address", + internalType: "address", + }, + { + name: "maxFeePerGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "maxPriorityFeePerGas", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "userOpHash", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "prefund", + type: "uint256", + internalType: "uint256", + }, + { + name: "contextOffset", + type: "uint256", + internalType: "uint256", + }, + { + name: "preOpGas", + type: "uint256", + internalType: "uint256", + }, + ], + }, + ], + outputs: [ + { + name: "validationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "_validatePrepayment", + inputs: [ + { + name: "opIndex", + type: "uint256", + internalType: "uint256", + }, + { + name: "userOp", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "outOpInfo", + type: "tuple", + internalType: "struct EntryPoint.UserOpInfo", + components: [ + { + name: "mUserOp", + type: "tuple", + internalType: "struct EntryPoint.MemoryUserOp", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "verificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "callGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterPostOpGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymaster", + type: "address", + internalType: "address", + }, + { + name: "maxFeePerGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "maxPriorityFeePerGas", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "userOpHash", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "prefund", + type: "uint256", + internalType: "uint256", + }, + { + name: "contextOffset", + type: "uint256", + internalType: "uint256", + }, + { + name: "preOpGas", + type: "uint256", + internalType: "uint256", + }, + ], + }, + ], + outputs: [ + { + name: "validationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "addStake", + inputs: [ + { + name: "unstakeDelaySec", + type: "uint32", + internalType: "uint32", + }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "balanceOf", + inputs: [ + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "binarySearchCallGasLimit", + inputs: [ + { + name: "queuedUserOps", + type: "tuple[]", + internalType: "struct SimulationArgs[]", + components: [ + { + name: "op", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "target", + type: "address", + internalType: "address", + }, + { + name: "targetCallData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "targetUserOp", + type: "tuple", + internalType: "struct SimulationArgs", + components: [ + { + name: "op", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "target", + type: "address", + internalType: "address", + }, + { + name: "targetCallData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "entryPoint", + type: "address", + internalType: "address", + }, + { + name: "initialMinGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "toleranceDelta", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasAllowance", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct IEntryPointSimulations.TargetCallResult", + components: [ + { + name: "gasUsed", + type: "uint256", + internalType: "uint256", + }, + { + name: "success", + type: "bool", + internalType: "bool", + }, + { + name: "returnData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "binarySearchPaymasterVerificationGasLimit", + inputs: [ + { + name: "queuedUserOps", + type: "tuple[]", + internalType: "struct SimulationArgs[]", + components: [ + { + name: "op", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "target", + type: "address", + internalType: "address", + }, + { + name: "targetCallData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "targetUserOp", + type: "tuple", + internalType: "struct SimulationArgs", + components: [ + { + name: "op", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "target", + type: "address", + internalType: "address", + }, + { + name: "targetCallData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "entryPoint", + type: "address", + internalType: "address", + }, + { + name: "initialMinGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "toleranceDelta", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasAllowance", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct IEntryPointSimulations.TargetCallResult", + components: [ + { + name: "gasUsed", + type: "uint256", + internalType: "uint256", + }, + { + name: "success", + type: "bool", + internalType: "bool", + }, + { + name: "returnData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "binarySearchVerificationGasLimit", + inputs: [ + { + name: "queuedUserOps", + type: "tuple[]", + internalType: "struct SimulationArgs[]", + components: [ + { + name: "op", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "target", + type: "address", + internalType: "address", + }, + { + name: "targetCallData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "targetUserOp", + type: "tuple", + internalType: "struct SimulationArgs", + components: [ + { + name: "op", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "target", + type: "address", + internalType: "address", + }, + { + name: "targetCallData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "entryPoint", + type: "address", + internalType: "address", + }, + { + name: "initialMinGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "toleranceDelta", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasAllowance", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct IEntryPointSimulations.TargetCallResult", + components: [ + { + name: "gasUsed", + type: "uint256", + internalType: "uint256", + }, + { + name: "success", + type: "bool", + internalType: "bool", + }, + { + name: "returnData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "depositTo", + inputs: [ + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "deposits", + inputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "deposit", + type: "uint256", + internalType: "uint256", + }, + { + name: "staked", + type: "bool", + internalType: "bool", + }, + { + name: "stake", + type: "uint112", + internalType: "uint112", + }, + { + name: "unstakeDelaySec", + type: "uint32", + internalType: "uint32", + }, + { + name: "withdrawTime", + type: "uint48", + internalType: "uint48", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getDepositInfo", + inputs: [ + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "info", + type: "tuple", + internalType: "struct IStakeManager.DepositInfo", + components: [ + { + name: "deposit", + type: "uint256", + internalType: "uint256", + }, + { + name: "staked", + type: "bool", + internalType: "bool", + }, + { + name: "stake", + type: "uint112", + internalType: "uint112", + }, + { + name: "unstakeDelaySec", + type: "uint32", + internalType: "uint32", + }, + { + name: "withdrawTime", + type: "uint48", + internalType: "uint48", + }, + ], + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getNonce", + inputs: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "key", + type: "uint192", + internalType: "uint192", + }, + ], + outputs: [ + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getUserOpHash", + inputs: [ + { + name: "userOp", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "incrementNonce", + inputs: [ + { + name: "key", + type: "uint192", + internalType: "uint192", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "innerHandleOp", + inputs: [ + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "opInfo", + type: "tuple", + internalType: "struct EntryPoint.UserOpInfo", + components: [ + { + name: "mUserOp", + type: "tuple", + internalType: "struct EntryPoint.MemoryUserOp", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "verificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "callGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterPostOpGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymaster", + type: "address", + internalType: "address", + }, + { + name: "maxFeePerGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "maxPriorityFeePerGas", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "userOpHash", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "prefund", + type: "uint256", + internalType: "uint256", + }, + { + name: "contextOffset", + type: "uint256", + internalType: "uint256", + }, + { + name: "preOpGas", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "context", + type: "bytes", + internalType: "bytes", + }, + { + name: "preGas", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "actualGasCost", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterPostOpGasLimit", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "nonceSequenceNumber", + inputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + { + name: "", + type: "uint192", + internalType: "uint192", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "simulateCall", + inputs: [ + { + name: "entryPoint", + type: "address", + internalType: "address", + }, + { + name: "payload", + type: "bytes", + internalType: "bytes", + }, + { + name: "gas", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "success", + type: "bool", + internalType: "bool", + }, + { + name: "result", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "simulateCallAndRevert", + inputs: [ + { + name: "target", + type: "address", + internalType: "address", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + { + name: "gas", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "simulateHandleOp", + inputs: [ + { + name: "op", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct IEntryPointSimulations.ExecutionResult", + components: [ + { + name: "preOpGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "paid", + type: "uint256", + internalType: "uint256", + }, + { + name: "accountValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterPostOpGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "targetSuccess", + type: "bool", + internalType: "bool", + }, + { + name: "targetResult", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "simulateHandleOpBulk", + inputs: [ + { + name: "ops", + type: "tuple[]", + internalType: "struct PackedUserOperation[]", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "", + type: "tuple[]", + internalType: "struct IEntryPointSimulations.ExecutionResult[]", + components: [ + { + name: "preOpGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "paid", + type: "uint256", + internalType: "uint256", + }, + { + name: "accountValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterPostOpGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "targetSuccess", + type: "bool", + internalType: "bool", + }, + { + name: "targetResult", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "simulateHandleOpLast", + inputs: [ + { + name: "ops", + type: "tuple[]", + internalType: "struct PackedUserOperation[]", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct IEntryPointSimulations.ExecutionResult", + components: [ + { + name: "preOpGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "paid", + type: "uint256", + internalType: "uint256", + }, + { + name: "accountValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterVerificationGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterPostOpGasLimit", + type: "uint256", + internalType: "uint256", + }, + { + name: "targetSuccess", + type: "bool", + internalType: "bool", + }, + { + name: "targetResult", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "simulateValidation", + inputs: [ + { + name: "userOp", + type: "tuple", + internalType: "struct PackedUserOperation", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct IEntryPointSimulations.ValidationResult", + components: [ + { + name: "returnInfo", + type: "tuple", + internalType: "struct IEntryPoint.ReturnInfo", + components: [ + { + name: "preOpGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "prefund", + type: "uint256", + internalType: "uint256", + }, + { + name: "accountValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterContext", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "senderInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "factoryInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "paymasterInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "aggregatorInfo", + type: "tuple", + internalType: "struct IEntryPoint.AggregatorStakeInfo", + components: [ + { + name: "aggregator", + type: "address", + internalType: "address", + }, + { + name: "stakeInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + ], + }, + ], + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "simulateValidationBulk", + inputs: [ + { + name: "userOps", + type: "tuple[]", + internalType: "struct PackedUserOperation[]", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "", + type: "tuple[]", + internalType: "struct IEntryPointSimulations.ValidationResult[]", + components: [ + { + name: "returnInfo", + type: "tuple", + internalType: "struct IEntryPoint.ReturnInfo", + components: [ + { + name: "preOpGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "prefund", + type: "uint256", + internalType: "uint256", + }, + { + name: "accountValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterContext", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "senderInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "factoryInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "paymasterInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "aggregatorInfo", + type: "tuple", + internalType: "struct IEntryPoint.AggregatorStakeInfo", + components: [ + { + name: "aggregator", + type: "address", + internalType: "address", + }, + { + name: "stakeInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + ], + }, + ], + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "simulateValidationLast", + inputs: [ + { + name: "userOps", + type: "tuple[]", + internalType: "struct PackedUserOperation[]", + components: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + internalType: "uint256", + }, + { + name: "initCode", + type: "bytes", + internalType: "bytes", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "accountGasLimits", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "preVerificationGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "gasFees", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "paymasterAndData", + type: "bytes", + internalType: "bytes", + }, + { + name: "signature", + type: "bytes", + internalType: "bytes", + }, + ], + }, + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct IEntryPointSimulations.ValidationResult", + components: [ + { + name: "returnInfo", + type: "tuple", + internalType: "struct IEntryPoint.ReturnInfo", + components: [ + { + name: "preOpGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "prefund", + type: "uint256", + internalType: "uint256", + }, + { + name: "accountValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterValidationData", + type: "uint256", + internalType: "uint256", + }, + { + name: "paymasterContext", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + name: "senderInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "factoryInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "paymasterInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + name: "aggregatorInfo", + type: "tuple", + internalType: "struct IEntryPoint.AggregatorStakeInfo", + components: [ + { + name: "aggregator", + type: "address", + internalType: "address", + }, + { + name: "stakeInfo", + type: "tuple", + internalType: "struct IStakeManager.StakeInfo", + components: [ + { + name: "stake", + type: "uint256", + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + internalType: "uint256", + }, + ], + }, + ], + }, + ], + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "unlockStake", + inputs: [], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "withdrawStake", + inputs: [ + { + name: "withdrawAddress", + type: "address", + internalType: "address payable", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "withdrawTo", + inputs: [ + { + name: "withdrawAddress", + type: "address", + internalType: "address payable", + }, + { + name: "withdrawAmount", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "event", + name: "AccountDeployed", + inputs: [ + { + name: "userOpHash", + type: "bytes32", + indexed: true, + internalType: "bytes32", + }, + { + name: "sender", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "factory", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "paymaster", + type: "address", + indexed: false, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "BeforeExecution", + inputs: [], + anonymous: false, + }, + { + type: "event", + name: "Deposited", + inputs: [ + { + name: "account", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "totalDeposit", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "PostOpRevertReason", + inputs: [ + { + name: "userOpHash", + type: "bytes32", + indexed: true, + internalType: "bytes32", + }, + { + name: "sender", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "revertReason", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "SignatureAggregatorChanged", + inputs: [ + { + name: "aggregator", + type: "address", + indexed: true, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "StakeLocked", + inputs: [ + { + name: "account", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "totalStaked", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "unstakeDelaySec", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "StakeUnlocked", + inputs: [ + { + name: "account", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "withdrawTime", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "StakeWithdrawn", + inputs: [ + { + name: "account", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "withdrawAddress", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "UserOperationEvent", + inputs: [ + { + name: "userOpHash", + type: "bytes32", + indexed: true, + internalType: "bytes32", + }, + { + name: "sender", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "paymaster", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "success", + type: "bool", + indexed: false, + internalType: "bool", + }, + { + name: "actualGasCost", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "actualGasUsed", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "UserOperationPrefundTooLow", + inputs: [ + { + name: "userOpHash", + type: "bytes32", + indexed: true, + internalType: "bytes32", + }, + { + name: "sender", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "UserOperationRevertReason", + inputs: [ + { + name: "userOpHash", + type: "bytes32", + indexed: true, + internalType: "bytes32", + }, + { + name: "sender", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "nonce", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "revertReason", + type: "bytes", + indexed: false, + internalType: "bytes", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "Withdrawn", + inputs: [ + { + name: "account", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "withdrawAddress", + type: "address", + indexed: false, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "error", + name: "FailedOp", + inputs: [ + { + name: "opIndex", + type: "uint256", + internalType: "uint256", + }, + { + name: "reason", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "FailedOpWithRevert", + inputs: [ + { + name: "opIndex", + type: "uint256", + internalType: "uint256", + }, + { + name: "reason", + type: "string", + internalType: "string", + }, + { + name: "inner", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + type: "error", + name: "PostOpReverted", + inputs: [ + { + name: "returnData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + type: "error", + name: "ReentrancyGuardReentrantCall", + inputs: [], + }, + { + type: "error", + name: "SenderAddressResult", + inputs: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "SignatureValidationFailed", + inputs: [ + { + name: "aggregator", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "SimulationOutOfGas", + inputs: [ + { + name: "optimalGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "minGas", + type: "uint256", + internalType: "uint256", + }, + { + name: "maxGas", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "innerCallResult", + inputs: [ + { + name: "remainingGas", + type: "uint256", + internalType: "uint256", + }, + ], + }, +] as const; diff --git a/packages/entrykit/src/quarry/transports/methods/sendUserOperation.ts b/packages/entrykit/src/quarry/transports/methods/sendUserOperation.ts index 45b0f0a152..ae87a65ac7 100644 --- a/packages/entrykit/src/quarry/transports/methods/sendUserOperation.ts +++ b/packages/entrykit/src/quarry/transports/methods/sendUserOperation.ts @@ -12,19 +12,15 @@ import { ConnectedClient } from "../../../common"; // TODO: move to common package? -// TODO: move this into a generic to support other versions? -const entryPointVersion = "0.7"; -type entryPointVersion = typeof entryPointVersion; - export async function sendUserOperation({ executor, rpcUserOp, }: { executor: ConnectedClient; - rpcUserOp: RpcUserOperation; + rpcUserOp: RpcUserOperation<"0.7">; }): Promise< - Pick, "success" | "userOpHash"> & { - receipt: Pick["receipt"], "transactionHash">; + Pick, "success" | "userOpHash"> & { + receipt: Pick["receipt"], "transactionHash">; } > { const userOp = formatUserOperation(rpcUserOp); diff --git a/packages/entrykit/src/quarry/transports/userOpExecutor.ts b/packages/entrykit/src/quarry/transports/userOpExecutor.ts index 040da0b02f..43f43837d0 100644 --- a/packages/entrykit/src/quarry/transports/userOpExecutor.ts +++ b/packages/entrykit/src/quarry/transports/userOpExecutor.ts @@ -48,11 +48,12 @@ export function userOpExecutor({ executor }: { executor: ConnectedClient }): Tra if (method === "eth_sendUserOperation") { const [rpcUserOp, entrypoint] = params; - if (entrypoint === entryPoint07Address) { - const result = await sendUserOperation({ executor, rpcUserOp }); - receipts.set(result.userOpHash, result as RpcUserOperationReceipt<"0.7">); - return result.userOpHash; + if (entrypoint !== entryPoint07Address) { + throw new Error(`Entrypoint "${entrypoint}" not supported.`); } + const result = await sendUserOperation({ executor, rpcUserOp }); + receipts.set(result.userOpHash, result as RpcUserOperationReceipt<"0.7">); + return result.userOpHash; } if (method === "eth_getUserOperationReceipt") { @@ -61,7 +62,11 @@ export function userOpExecutor({ executor }: { executor: ConnectedClient }): Tra } if (method === "eth_estimateUserOperationGas") { - return await estimateUserOperationGas(params); + const [rpcUserOp, entrypoint] = params; + if (entrypoint !== entryPoint07Address) { + throw new Error(`Entrypoint "${entrypoint}" not supported.`); + } + return await estimateUserOperationGas({ executor, rpcUserOp }); } throw new Error("Method not implemented."); diff --git a/packages/entrykit/src/quarry/transports/wiresaw.ts b/packages/entrykit/src/quarry/transports/wiresaw.ts new file mode 100644 index 0000000000..dc4be20f8f --- /dev/null +++ b/packages/entrykit/src/quarry/transports/wiresaw.ts @@ -0,0 +1,161 @@ +import { + BundlerRpcSchema, + Hash, + Hex, + PublicRpcSchema, + RpcTransactionReceipt, + RpcUserOperationReceipt, + Transport, + fallback, + http, + webSocket, +} from "viem"; +import { getRpcMethod, getRpcSchema, TransportRequestFn, TransportRequestFnMapped } from "./common"; + +export type WiresawRpcSchema = [ + { + Method: "wiresaw_call"; + Parameters: getRpcMethod["Parameters"]; + ReturnType: getRpcMethod["ReturnType"]; + }, + { + Method: "wiresaw_getTransactionReceipt"; + Parameters: getRpcMethod["Parameters"]; + ReturnType: getRpcMethod["ReturnType"]; + }, + { + Method: "wiresaw_sendUserOperation"; + Parameters: getRpcMethod["Parameters"]; + ReturnType: { + userOpHash: Hex; + txHash: Hex; + }; + }, +]; + +export type OverriddenMethods = [ + ...getRpcSchema, + ...getRpcSchema, +]; + +type ChainCache = { + receipts: Map; + /** + * user op hash to transaction hash + */ + userOps: Map; +}; + +const cache = new Map(); + +function getCache(chainId: Hex) { + const cached = cache.get(chainId) ?? { + receipts: new Map(), + userOps: new Map(), + }; + if (!cache.has(chainId)) cache.set(chainId, cached); + return cached; +} + +export function wiresaw(getTransport: transport): transport { + return ((args) => { + const getWiresawTransport = + args.chain?.rpcUrls && "wiresaw" in args.chain.rpcUrls + ? args.chain.rpcUrls.wiresaw.webSocket + ? fallback([webSocket(args.chain.rpcUrls.wiresaw.webSocket[0]), http(args.chain.rpcUrls.wiresaw.http[0])]) + : http(args.chain.rpcUrls.wiresaw.http[0]) + : undefined; + if (!getWiresawTransport) return getTransport(args); + + const transport = getTransport(args) as { + request: TransportRequestFn; + }; + const wiresawTransport = getWiresawTransport(args) as { + request: TransportRequestFn; + }; + + let chainId: Hex | null = null; + async function getChainId() { + return (chainId ??= await transport.request({ method: "eth_chainId" })); + } + + async function getReceipt(hash: Hex, receipts: ChainCache["receipts"]) { + if (receipts.has(hash)) return receipts.get(hash)!; + const [wiresawReceipt, chainReceipt] = await Promise.all([ + wiresawTransport.request({ + method: "wiresaw_getTransactionReceipt", + params: [hash], + }), + transport.request({ + method: "eth_getTransactionReceipt", + params: [hash], + }), + ]); + const receipt = wiresawReceipt ?? chainReceipt; + if (receipt != null) receipts.set(hash, receipt); + return receipt; + } + + const request: TransportRequestFnMapped = async ({ method, params }, opts) => { + if (method === "eth_chainId") { + return await getChainId(); + } + + // TODO: only route to wiresaw if using pending block tag + if (method === "eth_call") { + return wiresawTransport.request({ method: "wiresaw_call", params }); + } + + // We intentionally don't reroute `eth_sendRawTransaction` because Wiresaw + // already handles this method within the RPC spec, where it returns the + // tx hash, which can be fetched immediately with `eth_getTransactionReceipt`. + + if (method === "eth_getTransactionReceipt") { + const { receipts } = getCache(await getChainId()); + const [hash] = params; + return await getReceipt(hash, receipts); + } + + if (method === "eth_sendUserOperation") { + const { userOps } = getCache(await getChainId()); + const result = await wiresawTransport.request({ + method: "wiresaw_sendUserOperation", + params, + }); + userOps.set(result.userOpHash, result.txHash); + return result.userOpHash; + } + + if (method === "eth_getUserOperationReceipt") { + const { userOps } = getCache(await getChainId()); + const [userOpHash] = params; + const transactionHash = userOps.get(userOpHash); + + if (!transactionHash) { + // TODO: look up user op logs + // https://github.com/latticexyz/alto/blob/206dd8fc0d672a3d49e06d4bdd2eff4d519bdea3/src/executor/executorManager.ts#L617-L625 + throw new Error(`Could not find transaction hash for user op hash "${userOpHash}".`); + } + + // return instant/partial receipt for now until we can find a good way + // to opt-in to this when using things like permissionless actions, which + // call out to `waitForUserOperationReceipt` + return { + success: true, + userOpHash, + receipt: { + transactionHash, + }, + } as RpcUserOperationReceipt; + + // const receipt = await getReceipt(transactionHash, receipts); + // if (!receipt) return null; + // return getUserOperationReceipt(userOpHash, receipt); + } + + return await transport.request({ method, params }, opts); + }; + + return { ...transport, request }; + }) as transport; +} diff --git a/packages/store-sync/src/common.ts b/packages/store-sync/src/common.ts index d05ca7bf60..94507bc973 100644 --- a/packages/store-sync/src/common.ts +++ b/packages/store-sync/src/common.ts @@ -1,4 +1,4 @@ -import { Address, Block, Hex, Log, TransactionReceipt } from "viem"; +import { Address, Block, Hex, Log, OneOf, TransactionReceipt } from "viem"; import { StoreEventsAbiItem, StoreEventsAbi } from "@latticexyz/store"; import { Observable } from "rxjs"; import { UnionPick } from "@latticexyz/common/type-utils"; @@ -118,7 +118,9 @@ export type SyncResult = { latestBlock$: Observable; latestBlockNumber$: Observable; storedBlockLogs$: Observable; - waitForTransaction: (tx: Hex) => Promise; + waitForTransaction: ( + tx: Hex | OneOf<{ userOperationHash: Hex } | { transactionHash: Hex }>, + ) => Promise; }; export type SyncAdapter = (opts: SyncOptions) => Promise; diff --git a/packages/store-sync/src/createStoreSync.ts b/packages/store-sync/src/createStoreSync.ts index b6b8bf41dc..4d65851f1a 100644 --- a/packages/store-sync/src/createStoreSync.ts +++ b/packages/store-sync/src/createStoreSync.ts @@ -1,7 +1,7 @@ import { MUDChain } from "@latticexyz/common/chains"; import { storeEventsAbi } from "@latticexyz/store"; -import { GetTransactionReceiptErrorType, Hex, parseEventLogs } from "viem"; -import { entryPoint07Abi } from "viem/account-abstraction"; +import { GetTransactionReceiptErrorType, Hex, OneOf, parseEventLogs } from "viem"; +import { GetUserOperationReceiptErrorType, entryPoint07Abi, getUserOperationReceipt } from "viem/account-abstraction"; import { StorageAdapter, StorageAdapterBlock, @@ -335,7 +335,9 @@ export async function createStoreSync({ ); // TODO: move to its own file so we can test it, have its own debug instance, etc. - async function waitForTransaction(tx: Hex): Promise { + async function waitForTransaction( + tx: Hex | OneOf<{ userOperationHash: Hex } | { transactionHash: Hex }>, + ): Promise { debug("waiting for tx", tx); // This currently blocks for async call on each block processed @@ -344,17 +346,28 @@ export async function createStoreSync({ // We use `mergeMap` instead of `concatMap` here to send the fetch request immediately when a new block range appears, // instead of sending the next request only when the previous one completed. mergeMap(async (storedBlocks) => { - for (const storedBlock of storedBlocks) { - // If stored block had Store event logs associated with tx, it must have succeeded. - if (storedBlock.logs.some((log) => log.transactionHash === tx)) { - return { blockNumber: storedBlock.blockNumber, status: "success", transactionHash: tx } as const; + try { + const hash = await (async (): Promise => { + if (typeof tx === "string") return tx; + if (tx.transactionHash) return tx.transactionHash; + const userOpReceipt = await getAction( + publicClient, + getUserOperationReceipt, + "getUserOperationReceipt", + )({ hash: tx.userOperationHash }); + return userOpReceipt.receipt.transactionHash; + })(); + + for (const storedBlock of storedBlocks) { + // If stored block had Store event logs associated with tx, it must have succeeded. + if (storedBlock.logs.some((log) => log.transactionHash === hash)) { + return { blockNumber: storedBlock.blockNumber, status: "success", transactionHash: hash } as const; + } } - } - try { const lastStoredBlock = storedBlocks[0]; debug("fetching tx receipt for block", lastStoredBlock.blockNumber); - const receipt = await getAction(publicClient, getTransactionReceipt, "getTransactionReceipt")({ hash: tx }); + const receipt = await getAction(publicClient, getTransactionReceipt, "getTransactionReceipt")({ hash }); // Skip if sync hasn't caught up to this transaction's block. if (lastStoredBlock.blockNumber < receipt.blockNumber) return; @@ -367,6 +380,8 @@ export async function createStoreSync({ }); if (userOpEvents.length) { debug("tx receipt appears to be a user op, using user op status instead"); + // TODO: remove this check since non-wiresaw bundlers will likely have multiple user ops + // TODO: cache and look up specific user op hash(es) for this tx if (userOpEvents.length > 1) { const issueLink = `https://github.com/latticexyz/mud/issues/new${new URLSearchParams({ title: "waitForTransaction found more than one user op", @@ -378,6 +393,10 @@ export async function createStoreSync({ ); } + // console.log(userOpEvents[0]); + // console.log(hexToString(userOpEvents[0].data)); + // console.log(decodeErrorResult({ abi: worldAbi, data: userOpEvents[0].data })); + return { status: userOpEvents[0].args.success ? "success" : "reverted", blockNumber: receipt.blockNumber, @@ -391,8 +410,8 @@ export async function createStoreSync({ transactionHash: receipt.transactionHash, }; } catch (e) { - const error = e as GetTransactionReceiptErrorType; - if (error.name === "TransactionReceiptNotFoundError") { + const error = e as GetTransactionReceiptErrorType | GetUserOperationReceiptErrorType; + if (error.name === "TransactionReceiptNotFoundError" || error.name === "UserOperationReceiptNotFoundError") { return; } throw error;