From f8e16249010257ef3ba468586e615b8a25748b3c Mon Sep 17 00:00:00 2001 From: mouseless <97399882+mouseless-eth@users.noreply.github.com> Date: Fri, 18 Oct 2024 22:18:17 +0100 Subject: [PATCH] initial commit --- src/cli/handler.ts | 3 +- src/executor/executor.ts | 109 ++++++++++++++++++++++++++++----------- src/executor/utils.ts | 31 ++++++++--- src/rpc/rpcHandler.ts | 36 +++++++++++++ src/types/mempool.ts | 30 +++++++++-- src/types/schemas.ts | 32 +++++++++++- 6 files changed, 196 insertions(+), 45 deletions(-) diff --git a/src/cli/handler.ts b/src/cli/handler.ts index 1f4d195d..17800109 100644 --- a/src/cli/handler.ts +++ b/src/cli/handler.ts @@ -19,6 +19,7 @@ import { setupServer } from "./setupServer" import { type AltoConfig, createConfig } from "../createConfig" import { parseArgs } from "./parseArgs" import { deploySimulationsContract } from "./deploySimulationsContract" +import { eip7702Actions } from "viem/experimental" const preFlightChecks = async (config: AltoConfig): Promise => { for (const entrypoint of config.entrypoints) { @@ -101,7 +102,7 @@ export async function bundlerHandler(args_: IOptionsInput): Promise { ) }), chain - }) + }).extend(eip7702Actions()) // if flag is set, use utility wallet to deploy the simulations contract if (args.deploySimulationsContract) { diff --git a/src/executor/executor.ts b/src/executor/executor.ts index 20b15db0..35f8df05 100644 --- a/src/executor/executor.ts +++ b/src/executor/executor.ts @@ -21,7 +21,8 @@ import { type UserOperationV06, type UserOperationV07, type UserOperationWithHash, - deriveUserOperation + deriveUserOperation, + isExperimental7702Type } from "@alto/types" import type { Logger, Metrics } from "@alto/utils" import { @@ -55,6 +56,7 @@ import { } from "./utils" import type { SendTransactionErrorType } from "viem" import type { AltoConfig } from "../createConfig" +import { SignedAuthorizationList } from "viem/experimental" export interface GasEstimateResult { preverificationGas: bigint @@ -340,10 +342,11 @@ export class Executor { abi: EntryPointV06Abi, functionName: "handleOps", args: [ - opsToBundle.map( - (opInfo) => - opInfo.mempoolUserOperation as UserOperationV06 - ), + opsToBundle.map(({ mempoolUserOperation }) => { + const op = + deriveUserOperation(mempoolUserOperation) + return op as UserOperationV06 + }), transactionInfo.executor.address ] }) @@ -351,11 +354,15 @@ export class Executor { abi: EntryPointV07Abi, functionName: "handleOps", args: [ - opsToBundle.map((opInfo) => - toPackedUserOperation( - opInfo.mempoolUserOperation as UserOperationV07 + opsToBundle.map((opInfo) => { + const op = deriveUserOperation( + opInfo.mempoolUserOperation ) - ), + + return toPackedUserOperation( + op as UserOperationV07 + ) + }), transactionInfo.executor.address ] }) @@ -521,7 +528,8 @@ export class Executor { account: Account gas: bigint nonce: number - } + }, + authorizationList?: SignedAuthorizationList ) { const request = await this.config.walletClient.prepareTransactionRequest({ @@ -543,8 +551,19 @@ export class Executor { // Try sending the transaction and updating relevant fields if there is an error. while (attempts < maxAttempts) { try { - transactionHash = - await this.config.walletClient.sendTransaction(request) + if (authorizationList) { + transactionHash = + // @ts-ignore: It is complaining about legacy type tx types + await this.config.walletClient.sendTransaction({ + ...request, + authorizationList + }) + } else { + transactionHash = + await this.config.walletClient.sendTransaction({ + ...request + }) + } break } catch (e: unknown) { @@ -592,7 +611,7 @@ export class Executor { return { mempoolUserOperation: op, userOperationHash: getUserOperationHash( - op, + deriveUserOperation(op), entryPoint, this.config.walletClient.chain.id ) @@ -655,7 +674,16 @@ export class Executor { this.config.legacyTransactions, this.config.fixedGasLimitForEstimation, this.reputationManager, - childLogger + childLogger, + opsWithHashes + .map(({ mempoolUserOperation }) => { + if (isExperimental7702Type(mempoolUserOperation)) { + return mempoolUserOperation.authorization + } + + return undefined + }) + .filter((auth) => auth !== undefined) as SignedAuthorizationList ) if (simulatedOps.length === 0) { @@ -756,19 +784,29 @@ export class Executor { ...gasOptions } - const userOps = opsWithHashToBundle.map((owh) => - isUserOpVersion06 - ? owh.mempoolUserOperation - : toPackedUserOperation( - owh.mempoolUserOperation as UserOperationV07 - ) - ) as PackedUserOperation[] + const userOps = opsWithHashToBundle.map((owh) => { + const op = deriveUserOperation(owh.mempoolUserOperation) + return isUserOpVersion06 + ? op + : toPackedUserOperation(op as UserOperationV07) + }) as PackedUserOperation[] + + const authorizationList = opsWithHashToBundle + .map((owh) => { + if ("authorization" in owh.mempoolUserOperation) { + return owh.mempoolUserOperation.authorization + } + + return undefined + }) + .filter((auth) => auth !== undefined) as SignedAuthorizationList transactionHash = await this.sendHandleOpsTransaction( userOps, isUserOpVersion06, entryPoint, - opts + opts, + authorizationList ) opsWithHashToBundle.map(({ userOperationHash }) => { @@ -823,7 +861,7 @@ export class Executor { sentry.captureException(err) childLogger.error( - { error: JSON.stringify(err) }, + { error: JSON.stringify(e) }, "error submitting bundle transaction" ) this.markWalletProcessed(wallet) @@ -865,8 +903,14 @@ export class Executor { functionName: "handleOps", args: [ opsWithHashToBundle.map( - (owh) => - owh.mempoolUserOperation as UserOperationV06 + ({ mempoolUserOperation }) => { + const op = + deriveUserOperation( + mempoolUserOperation + ) + + return op as UserOperationV06 + } ), wallet.address ] @@ -875,10 +919,17 @@ export class Executor { abi: ep.abi, functionName: "handleOps", args: [ - opsWithHashToBundle.map((owh) => - toPackedUserOperation( - owh.mempoolUserOperation as UserOperationV07 - ) + opsWithHashToBundle.map( + ({ mempoolUserOperation }) => { + const op = + deriveUserOperation( + mempoolUserOperation + ) + + return toPackedUserOperation( + op as UserOperationV07 + ) + } ), wallet.address ] diff --git a/src/executor/utils.ts b/src/executor/utils.ts index 824186f3..1f0deca9 100644 --- a/src/executor/utils.ts +++ b/src/executor/utils.ts @@ -41,6 +41,7 @@ import { hexToBytes, numberToHex } from "viem" +import { SignedAuthorizationList } from "viem/experimental" export function simulatedOpsToResults( simulatedOps: { @@ -132,7 +133,8 @@ export async function filterOpsAndEstimateGas( onlyPre1559: boolean, fixedGasLimitForEstimation: bigint | undefined, reputationManager: InterfaceReputationManager, - logger: Logger + logger: Logger, + authorizationList?: SignedAuthorizationList ) { const simulatedOps: { owh: UserOperationWithHash @@ -162,19 +164,31 @@ export async function filterOpsAndEstimateGas( const opsToSend = simulatedOps .filter((op) => op.reason === undefined) - .map((op) => { + .map(({ owh }) => { + const op = deriveUserOperation(owh.mempoolUserOperation) return isUserOpV06 - ? op.owh.mempoolUserOperation - : toPackedUserOperation( - op.owh - .mempoolUserOperation as UserOperationV07 - ) + ? op + : toPackedUserOperation(op as UserOperationV07) }) + logger.info("calling eth_estimateGas", { + args: { + authorizationList, + account: wallet, + nonce: nonce, + blockTag: blockTag, + ...(fixedEstimationGasLimit !== undefined && { + gas: fixedEstimationGasLimit + }), + ...gasOptions + } + }) + gasLimit = await ep.estimateGas.handleOps( // @ts-ignore - ep is set correctly for opsToSend, but typescript doesn't know that [opsToSend, wallet.address], { + authorizationList, account: wallet, nonce: nonce, blockTag: blockTag, @@ -210,6 +224,7 @@ export async function filterOpsAndEstimateGas( return { simulatedOps, gasLimit } } catch (err: unknown) { + logger.error({ err }, "error estimating gas!!") logger.error({ err, blockTag }, "error estimating gas") const e = parseViemError(err) @@ -375,7 +390,7 @@ export async function filterOpsAndEstimateGas( } else { sentry.captureException(err) logger.error( - { error: JSON.stringify(err), blockTag }, + { error: JSON.stringify(e), blockTag }, "error estimating gas" ) return { simulatedOps: [], gasLimit: 0n } diff --git a/src/rpc/rpcHandler.ts b/src/rpc/rpcHandler.ts index 2ee90dd7..04f53d40 100644 --- a/src/rpc/rpcHandler.ts +++ b/src/rpc/rpcHandler.ts @@ -81,6 +81,7 @@ import { import { base, baseSepolia, optimism } from "viem/chains" import type { NonceQueuer } from "./nonceQueuer" import type { AltoConfig } from "../createConfig" +import { SignedAuthorization } from "viem/experimental" export interface IRpcEndpoint { handleMethod( @@ -297,6 +298,14 @@ export class RpcHandler implements IRpcEndpoint { ...request.params ) } + case "pimlico_sendExperimental": + return { + method, + result: await this.pimlico_sendExperimental( + apiVersion, + ...request.params + ) + } } } @@ -1037,6 +1046,33 @@ export class RpcHandler implements IRpcEndpoint { return userOperationReceipt } + async pimlico_sendExperimental( + apiVersion: ApiVersion, + userOperation: UserOperation, + entryPoint: Address, + authorization: SignedAuthorization + ) { + try { + this.logger.info("trying addToMempoolIfValid") + await this.addToMempoolIfValid( + { + userOperation, + authorization + }, + entryPoint, + apiVersion + ) + } catch (e) { + this.logger.error(e) + } + + return getUserOperationHash( + userOperation, + entryPoint, + this.config.publicClient.chain.id + ) + } + async pimlico_sendCompressedUserOperation( apiVersion: ApiVersion, compressedCalldata: Hex, diff --git a/src/types/mempool.ts b/src/types/mempool.ts index 3b77df47..08244411 100644 --- a/src/types/mempool.ts +++ b/src/types/mempool.ts @@ -1,6 +1,11 @@ import type { Address, Chain, Hex } from "viem" import type { Account } from "viem/accounts" -import type { CompressedUserOperation, HexData32, UserOperation } from "." +import type { + CompressedUserOperation, + Experimental7702UserOperation, + HexData32, + UserOperation +} from "." export interface ReferencedCodeHashes { // addresses accessed during this user operation @@ -13,16 +18,31 @@ export interface ReferencedCodeHashes { export const deriveUserOperation = ( op: MempoolUserOperation ): UserOperation => { - return isCompressedType(op) - ? (op as CompressedUserOperation).inflatedOp - : (op as UserOperation) + if (isCompressedType(op)) { + return (op as CompressedUserOperation).inflatedOp + } + if (isExperimental7702Type(op)) { + return (op as Experimental7702UserOperation).userOperation + } + + // default case + return op as UserOperation } export const isCompressedType = (op: MempoolUserOperation): boolean => { return "compressedCalldata" in op } -export type MempoolUserOperation = UserOperation | CompressedUserOperation +export const isExperimental7702Type = ( + op: MempoolUserOperation +): op is Experimental7702UserOperation => { + return "authorization" in op +} + +export type MempoolUserOperation = + | UserOperation + | CompressedUserOperation + | Experimental7702UserOperation export type TransactionInfo = { transactionType: "default" | "compressed" diff --git a/src/types/schemas.ts b/src/types/schemas.ts index c8baec47..d0c1fb9a 100644 --- a/src/types/schemas.ts +++ b/src/types/schemas.ts @@ -1,6 +1,7 @@ import { type Hash, type Hex, getAddress, maxUint256 } from "viem" import { z } from "zod" import type { MempoolUserOperation } from "./mempool" +import { SignedAuthorization } from "viem/experimental" const hexDataPattern = /^0x[0-9A-Fa-f]*$/ const addressPattern = /^0x[0-9,a-f,A-F]{40}$/ @@ -166,6 +167,16 @@ const packerUserOperationSchema = z .strict() .transform((val) => val) +const authorizationSchema = z.object({ + contractAddress: addressSchema, + chainId: hexNumberSchema.transform((val) => Number(val)), + nonce: hexNumberSchema.transform((val) => Number(val)), + r: hexData32Schema.transform((val) => val as Hex), + s: hexData32Schema.transform((val) => val as Hex), + v: hexNumberSchema, + yParity: hexNumberSchema.transform((val) => Number(val)) +}) + const partialUserOperationSchema = z.union([ partialUserOperationV06Schema, partialUserOperationV07Schema @@ -182,6 +193,11 @@ export type PackedUserOperation = z.infer export type UserOperation = z.infer +export type Experimental7702UserOperation = { + userOperation: UserOperation + authorization: SignedAuthorization +} + export type CompressedUserOperation = { compressedCalldata: Hex inflatedOp: UserOperation @@ -348,6 +364,11 @@ const pimlicoSendUserOperationNowRequestSchema = z.object({ params: z.tuple([userOperationSchema, addressSchema]) }) +const pimlicoSendExperimental = z.object({ + method: z.literal("pimlico_sendExperimental"), + params: z.tuple([userOperationSchema, addressSchema, authorizationSchema]) +}) + export const altoVersions = z.enum(["v1", "v2"]) export type AltoVersions = z.infer @@ -369,7 +390,8 @@ const bundlerRequestSchema = z.discriminatedUnion("method", [ pimlicoGetUserOperationStatusRequestSchema, pimlicoGetUserOperationGasPriceRequestSchema, pimlicoSendCompressedUserOperationRequestSchema, - pimlicoSendUserOperationNowRequestSchema + pimlicoSendUserOperationNowRequestSchema, + pimlicoSendExperimental ]) const chainIdResponseSchema = z.object({ @@ -583,6 +605,11 @@ const pimlicoSendUserOperationNowResponseSchema = z.object({ result: userOperationReceiptSchema }) +const pimlicoSendExperimentalResponseSchema = z.object({ + method: z.literal("pimlico_sendExperimental"), + result: hexData32Schema +}) + const bundlerResponseSchema = z.discriminatedUnion("method", [ chainIdResponseSchema, supportedEntryPointsResponseSchema, @@ -601,7 +628,8 @@ const bundlerResponseSchema = z.discriminatedUnion("method", [ pimlicoGetUserOperationStatusResponseSchema, pimlicoGetUserOperationGasPriceResponseSchema, pimlicoSendCompressedUserOperationResponseSchema, - pimlicoSendUserOperationNowResponseSchema + pimlicoSendUserOperationNowResponseSchema, + pimlicoSendExperimentalResponseSchema ]) export type BundlingMode = z.infer<