diff --git a/mock-verifying-paymaster/src/helpers/schema.ts b/mock-verifying-paymaster/src/helpers/schema.ts index 5bb1406..7e9c7a7 100644 --- a/mock-verifying-paymaster/src/helpers/schema.ts +++ b/mock-verifying-paymaster/src/helpers/schema.ts @@ -1,156 +1,262 @@ -import { type Hex, getAddress } from "viem" -import { type infer as zodInfer, z } from "zod" +import { type Hex, getAddress } from "viem"; +import { type infer as zodInfer, z } from "zod"; export enum ValidationErrors { - InvalidFields = -32602, - InsufficientBalance = -32603, - UnsupportedEntryPoint = -32604 + InvalidFields = -32602, + InsufficientBalance = -32603, + UnsupportedEntryPoint = -32604, } export class InternalBundlerError extends Error { - constructor(msg?: string) { - let message = msg - if (!msg) { - message = "Internal error from bundler" - } - super(message) - } + constructor(msg?: string) { + let message = msg; + if (!msg) { + message = "Internal error from bundler"; + } + super(message); + } } export class RpcError extends Error { - // error codes from: https://eips.ethereum.org/EIPS/eip-1474 - constructor( - msg: string, - readonly code?: number, - readonly data: unknown = undefined - ) { - super(msg) - } + // error codes from: https://eips.ethereum.org/EIPS/eip-1474 + constructor( + msg: string, + readonly code?: number, + readonly data: unknown = undefined, + ) { + super(msg); + } } -const hexDataPattern = /^0x[0-9A-Fa-f]*$/ -const addressPattern = /^0x[0-9,a-f,A-F]{40}$/ +const hexDataPattern = /^0x[0-9A-Fa-f]*$/; +const addressPattern = /^0x[0-9,a-f,A-F]{40}$/; export const addressSchema = z - .string() - .regex(addressPattern, { message: "not a valid hex address" }) - .transform((val) => getAddress(val)) + .string() + .regex(addressPattern, { message: "not a valid hex address" }) + .transform((val) => getAddress(val)); export const hexNumberSchema = z - .string() - .regex(hexDataPattern) - .or(z.number()) - .or(z.bigint()) - .superRefine((data, ctx) => { - // This function is used to refine the input and provide a context where you have access to the path. - try { - BigInt(data) // Attempt to convert to BigInt to validate it can be done - } catch { - ctx.addIssue({ - code: "custom", - message: - "Invalid input, expected a value that can be converted to bigint." - }) - } - }) - .transform((val) => BigInt(val)) + .string() + .regex(hexDataPattern) + .or(z.number()) + .or(z.bigint()) + .superRefine((data, ctx) => { + // This function is used to refine the input and provide a context where you have access to the path. + try { + BigInt(data); // Attempt to convert to BigInt to validate it can be done + } catch { + ctx.addIssue({ + code: "custom", + message: + "Invalid input, expected a value that can be converted to bigint.", + }); + } + }) + .transform((val) => BigInt(val)); export const hexDataSchema = z - .string() - .regex(hexDataPattern, { message: "not valid hex data" }) - .transform((val) => val.toLowerCase() as Hex) + .string() + .regex(hexDataPattern, { message: "not valid hex data" }) + .transform((val) => val.toLowerCase() as Hex); const userOperationSchemaPaymasterV6 = z - .object({ - sender: addressSchema, - nonce: hexNumberSchema, - initCode: hexDataSchema, - callData: hexDataSchema, - callGasLimit: hexNumberSchema.default(1n), - verificationGasLimit: hexNumberSchema.default(1n), - preVerificationGas: hexNumberSchema.default(1n), - maxPriorityFeePerGas: hexNumberSchema, - maxFeePerGas: hexNumberSchema, - paymasterAndData: hexDataSchema - .nullable() - .optional() - .transform((val) => val ?? undefined), - signature: hexDataSchema.optional().transform((val) => { - if (val === undefined) { - return "0x" - } - return val - }) - }) - .strict() - .transform((val) => { - return val - }) + .object({ + sender: addressSchema, + nonce: hexNumberSchema, + initCode: hexDataSchema, + callData: hexDataSchema, + callGasLimit: hexNumberSchema.default(1n), + verificationGasLimit: hexNumberSchema.default(1n), + preVerificationGas: hexNumberSchema.default(1n), + maxPriorityFeePerGas: hexNumberSchema, + maxFeePerGas: hexNumberSchema, + paymasterAndData: hexDataSchema + .nullable() + .optional() + .transform((val) => val ?? undefined), + signature: hexDataSchema.optional().transform((val) => { + if (val === undefined) { + return "0x"; + } + return val; + }), + }) + .strict() + .transform((val) => { + return val; + }); const userOperationSchemaPaymasterV7 = z - .object({ - sender: addressSchema, - nonce: hexNumberSchema, - factory: addressSchema.optional().transform((val) => val ?? undefined), - factoryData: hexDataSchema - .optional() - .transform((val) => val ?? undefined), - callData: hexDataSchema, - callGasLimit: hexNumberSchema.default(1n), - verificationGasLimit: hexNumberSchema.default(1n), - preVerificationGas: hexNumberSchema.default(1n), - maxFeePerGas: hexNumberSchema, - maxPriorityFeePerGas: hexNumberSchema, - paymaster: addressSchema - .nullable() - .optional() - .transform((val) => val ?? undefined), - paymasterVerificationGasLimit: hexNumberSchema - .nullable() - .optional() - .transform((val) => val ?? undefined), - paymasterPostOpGasLimit: hexNumberSchema - .nullable() - .optional() - .transform((val) => val ?? undefined), - paymasterData: hexDataSchema - .nullable() - .optional() - .transform((val) => val ?? undefined), - signature: hexDataSchema.optional().transform((val) => { - if (val === undefined) { - return "0x" - } - return val - }) - }) - .strict() - .transform((val) => { - return val - }) + .object({ + sender: addressSchema, + nonce: hexNumberSchema, + factory: addressSchema.optional().transform((val) => val ?? undefined), + factoryData: hexDataSchema.optional().transform((val) => val ?? undefined), + callData: hexDataSchema, + callGasLimit: hexNumberSchema.default(1n), + verificationGasLimit: hexNumberSchema.default(1n), + preVerificationGas: hexNumberSchema.default(1n), + maxFeePerGas: hexNumberSchema, + maxPriorityFeePerGas: hexNumberSchema, + paymaster: addressSchema + .nullable() + .optional() + .transform((val) => val ?? undefined), + paymasterVerificationGasLimit: hexNumberSchema + .nullable() + .optional() + .transform((val) => val ?? undefined), + paymasterPostOpGasLimit: hexNumberSchema + .nullable() + .optional() + .transform((val) => val ?? undefined), + paymasterData: hexDataSchema + .nullable() + .optional() + .transform((val) => val ?? undefined), + signature: hexDataSchema.optional().transform((val) => { + if (val === undefined) { + return "0x"; + } + return val; + }), + }) + .strict() + .transform((val) => { + return val; + }); export const jsonRpcResultSchema = z - .object({ - jsonrpc: z.literal("2.0"), - id: z.number(), - result: z.unknown() - }) - .strict() + .object({ + jsonrpc: z.literal("2.0"), + id: z.number(), + result: z.unknown(), + }) + .strict(); export const jsonRpcSchema = z - .object({ - jsonrpc: z.literal("2.0"), - id: z.number(), - method: z.string(), - params: z.array(z.unknown()).optional().default([]) - }) - .strict() + .object({ + jsonrpc: z.literal("2.0"), + id: z.number(), + method: z.string(), + params: z.array(z.unknown()).optional().default([]), + }) + .strict(); export const pmSponsorUserOperationParamsSchema = z.tuple([ - z.union([userOperationSchemaPaymasterV6, userOperationSchemaPaymasterV7]), - addressSchema -]) + z.union([userOperationSchemaPaymasterV6, userOperationSchemaPaymasterV7]), + addressSchema, +]); -export type UserOperationV7 = zodInfer -export type UserOperationV6 = zodInfer -export type JsonRpcSchema = zodInfer +const eip7677UserOperationSchemaV6 = z + .object({ + sender: addressSchema, + nonce: hexNumberSchema, + initCode: hexDataSchema, + callData: hexDataSchema, + callGasLimit: hexNumberSchema, + verificationGasLimit: hexNumberSchema, + preVerificationGas: hexNumberSchema, + maxPriorityFeePerGas: hexNumberSchema, + maxFeePerGas: hexNumberSchema, + paymasterAndData: hexDataSchema + .nullable() + .optional() + .transform((_) => { + return "0x" as Hex; + }), + signature: hexDataSchema + .nullable() + .optional() + .transform((_) => { + return "0x" as Hex; + }), + }) + .strict() + .transform((val) => { + return val; + }); + +const eip7677UserOperationSchemaV7 = z + .object({ + sender: addressSchema, + nonce: hexNumberSchema, + factory: addressSchema + .nullable() + .optional() + .transform((val) => val ?? null), + factoryData: hexDataSchema + .nullable() + .optional() + .transform((val) => val ?? null), + callData: hexDataSchema, + callGasLimit: hexNumberSchema, + verificationGasLimit: hexNumberSchema, + preVerificationGas: hexNumberSchema, + maxFeePerGas: hexNumberSchema, + maxPriorityFeePerGas: hexNumberSchema, + paymaster: addressSchema + .nullable() + .optional() + .transform((val) => val ?? null), + paymasterVerificationGasLimit: hexNumberSchema + .nullable() + .optional() + .transform((val) => val ?? null), + paymasterPostOpGasLimit: hexNumberSchema + .nullable() + .optional() + .transform((val) => val ?? null), + paymasterData: hexDataSchema + .nullable() + .optional() + .transform((val) => val ?? null), + signature: hexDataSchema.optional().transform((val) => { + if (val === undefined) { + return "0x"; + } + return val; + }), + }) + .strict() + .transform((val) => { + return val; + }); + +const eip7677UserOperationSchema = z.union([ + eip7677UserOperationSchemaV6, + eip7677UserOperationSchemaV7, +]); + +export const pmGetPaymasterData = z + .union([ + z.tuple([ + eip7677UserOperationSchema, + addressSchema, + hexNumberSchema, + z.union([z.object({}), z.null()]), + ]), + z.tuple([eip7677UserOperationSchema, addressSchema, hexNumberSchema]), + ]) + .transform((val) => { + return [val[0], val[1], val[2], val[3] ?? null] as const; + }); + +export const pmGetPaymasterStubDataParamsSchema = z + .union([ + z.tuple([ + eip7677UserOperationSchema, + addressSchema, + hexNumberSchema, + z.union([z.object({}), z.null()]), + ]), + z.tuple([eip7677UserOperationSchema, addressSchema, hexNumberSchema]), + ]) + .transform((val) => { + return [val[0], val[1], val[2], val[3] ?? null] as const; + }); + +export type UserOperationV7 = zodInfer; +export type UserOperationV6 = zodInfer; +export type JsonRpcSchema = zodInfer; diff --git a/mock-verifying-paymaster/src/helpers/utils.ts b/mock-verifying-paymaster/src/helpers/utils.ts index 2487775..8a2e250 100644 --- a/mock-verifying-paymaster/src/helpers/utils.ts +++ b/mock-verifying-paymaster/src/helpers/utils.ts @@ -1,21 +1,26 @@ -import { http, createWalletClient } from "viem" -import { mnemonicToAccount } from "viem/accounts" -import { foundry } from "viem/chains" +import { http, createWalletClient } from "viem"; +import { mnemonicToAccount } from "viem/accounts"; +import { foundry } from "viem/chains"; + +/// Returns the bigger of two BigInts. +export const maxBigInt = (a: bigint, b: bigint) => { + return a > b ? a : b; +}; export const getAnvilWalletClient = () => { - const account = mnemonicToAccount( - "test test test test test test test test test test test junk", - { - /* avoid nonce error with index 0 when deploying ep contracts. */ - addressIndex: 1 - } - ) + const account = mnemonicToAccount( + "test test test test test test test test test test test junk", + { + /* avoid nonce error with index 0 when deploying ep contracts. */ + addressIndex: 1, + }, + ); - const walletClient = createWalletClient({ - account, - chain: foundry, - transport: http(process.env.ANVIL_RPC) - }) + const walletClient = createWalletClient({ + account, + chain: foundry, + transport: http(process.env.ANVIL_RPC), + }); - return walletClient -} + return walletClient; +}; diff --git a/mock-verifying-paymaster/src/index.ts b/mock-verifying-paymaster/src/index.ts index de16703..954d79a 100644 --- a/mock-verifying-paymaster/src/index.ts +++ b/mock-verifying-paymaster/src/index.ts @@ -1,54 +1,54 @@ -import cors from "@fastify/cors" -import Fastify from "fastify" -import { ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07 } from "permissionless" -import { createPimlicoBundlerClient } from "permissionless/clients/pimlico" -import { http } from "viem" -import { foundry } from "viem/chains" -import { getAnvilWalletClient } from "./helpers/utils" +import cors from "@fastify/cors"; +import Fastify from "fastify"; +import { ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07 } from "permissionless"; +import { createPimlicoBundlerClient } from "permissionless/clients/pimlico"; +import { http } from "viem"; +import { foundry } from "viem/chains"; +import { getAnvilWalletClient } from "./helpers/utils"; import { - setupVerifyingPaymasterV06, - setupVerifyingPaymasterV07 -} from "./helpers/verifyingPaymasters" -import { createRpcHandler } from "./relay" + setupVerifyingPaymasterV06, + setupVerifyingPaymasterV07, +} from "./helpers/verifyingPaymasters"; +import { createRpcHandler } from "./relay"; const main = async () => { - const walletClient = getAnvilWalletClient() - const verifyingPaymasterV07 = await setupVerifyingPaymasterV07(walletClient) - const verifyingPaymasterV06 = await setupVerifyingPaymasterV06(walletClient) - - const altoBundlerV07 = createPimlicoBundlerClient({ - chain: foundry, - transport: http(process.env.ALTO_RPC), - entryPoint: ENTRYPOINT_ADDRESS_V07 - }) - - const altoBundlerV06 = createPimlicoBundlerClient({ - chain: foundry, - transport: http(process.env.ALTO_RPC), - entryPoint: ENTRYPOINT_ADDRESS_V06 - }) - - const app = Fastify({}) - - app.register(cors, { - origin: "*", - methods: ["POST", "GET", "OPTIONS"] - }) - - const rpcHandler = createRpcHandler( - altoBundlerV07, - altoBundlerV06, - verifyingPaymasterV07, - verifyingPaymasterV06, - walletClient - ) - app.post("/", {}, rpcHandler) - - app.get("/ping", async (_request, reply) => { - return reply.code(200).send({ message: "pong" }) - }) - - await app.listen({ host: "0.0.0.0", port: 3000 }) -} - -main() + const walletClient = getAnvilWalletClient(); + const verifyingPaymasterV07 = await setupVerifyingPaymasterV07(walletClient); + const verifyingPaymasterV06 = await setupVerifyingPaymasterV06(walletClient); + + const altoBundlerV07 = createPimlicoBundlerClient({ + chain: foundry, + transport: http(process.env.ALTO_RPC), + entryPoint: ENTRYPOINT_ADDRESS_V07, + }); + + const altoBundlerV06 = createPimlicoBundlerClient({ + chain: foundry, + transport: http(process.env.ALTO_RPC), + entryPoint: ENTRYPOINT_ADDRESS_V06, + }); + + const app = Fastify({}); + + app.register(cors, { + origin: "*", + methods: ["POST", "GET", "OPTIONS"], + }); + + const rpcHandler = createRpcHandler( + altoBundlerV07, + altoBundlerV06, + verifyingPaymasterV07, + verifyingPaymasterV06, + walletClient, + ); + app.post("/", {}, rpcHandler); + + app.get("/ping", async (_request, reply) => { + return reply.code(200).send({ message: "pong" }); + }); + + await app.listen({ host: "0.0.0.0", port: 3000 }); +}; + +main(); diff --git a/mock-verifying-paymaster/src/relay.ts b/mock-verifying-paymaster/src/relay.ts index 849c3dc..fb3d9c1 100644 --- a/mock-verifying-paymaster/src/relay.ts +++ b/mock-verifying-paymaster/src/relay.ts @@ -1,343 +1,461 @@ -import util from "node:util" -import type { FastifyReply, FastifyRequest } from "fastify" +import util from "node:util"; +import type { FastifyReply, FastifyRequest } from "fastify"; import { - ENTRYPOINT_ADDRESS_V07, - type EstimateUserOperationGasReturnType, - getPackedUserOperation -} from "permissionless" -import type { PimlicoBundlerClient } from "permissionless/clients/pimlico" -import type { - ENTRYPOINT_ADDRESS_V06_TYPE, - ENTRYPOINT_ADDRESS_V07_TYPE, - UserOperation -} from "permissionless/types" -import { ENTRYPOINT_ADDRESS_V06 } from "permissionless/utils" + type Account, + BaseError, + type Chain, + type GetContractReturnType, + type Hex, + type PublicClient, + type RpcRequestError, + type Transport, + type WalletClient, + concat, + encodeAbiParameters, + toHex, +} from "viem"; +import { fromZodError } from "zod-validation-error"; import { - type Account, - BaseError, - type Chain, - type GetContractReturnType, - type Hex, - type PublicClient, - type RpcRequestError, - type Transport, - type WalletClient, - concat, - encodeAbiParameters, - toHex -} from "viem" -import { fromZodError } from "zod-validation-error" + ENTRYPOINT_ADDRESS_V07, + type EstimateUserOperationGasReturnType, + getPackedUserOperation, +} from "permissionless"; +import type { PimlicoBundlerClient } from "permissionless/clients/pimlico"; +import type { + ENTRYPOINT_ADDRESS_V06_TYPE, + ENTRYPOINT_ADDRESS_V07_TYPE, + UserOperation, +} from "permissionless/types"; +import { ENTRYPOINT_ADDRESS_V06 } from "permissionless/utils"; import type { - VERIFYING_PAYMASTER_V06_ABI, - VERIFYING_PAYMASTER_V07_ABI -} from "./helpers/abi" + VERIFYING_PAYMASTER_V06_ABI, + VERIFYING_PAYMASTER_V07_ABI, +} from "./helpers/abi"; import { - InternalBundlerError, - type JsonRpcSchema, - RpcError, - ValidationErrors, - jsonRpcSchema, - pmSponsorUserOperationParamsSchema -} from "./helpers/schema" + InternalBundlerError, + type JsonRpcSchema, + RpcError, + ValidationErrors, + jsonRpcSchema, + pmGetPaymasterData, + pmGetPaymasterStubDataParamsSchema, + pmSponsorUserOperationParamsSchema, +} from "./helpers/schema"; +import { maxBigInt } from "./helpers/utils"; const handleMethodV06 = async ( - userOperation: UserOperation<"v0.6">, - altoBundlerV06: PimlicoBundlerClient, - verifyingPaymasterV06: GetContractReturnType< - typeof VERIFYING_PAYMASTER_V06_ABI, - PublicClient - >, - walletClient: WalletClient + userOperation: UserOperation<"v0.6">, + altoBundlerV06: PimlicoBundlerClient, + verifyingPaymasterV06: GetContractReturnType< + typeof VERIFYING_PAYMASTER_V06_ABI, + PublicClient + >, + walletClient: WalletClient, + estimateGas: boolean, ) => { - const opToSimulate = { - ...userOperation, - paymasterAndData: concat([ - verifyingPaymasterV06.address, - "0x000000000000000000000000000000000000000000000000000000006602f66a0000000000000000000000000000000000000000000000000000000000000000dba7a71bd49ae0174b1e4577b28f8b7c262d4085cfa192f1c19b516c85d2d1ef17eadeb549d71caf5d5f24fb6519088c1c13427343843131dd6ec19a3c6a350e1b" - ]) - } - - let gasEstimates: - | EstimateUserOperationGasReturnType - | undefined = undefined - try { - gasEstimates = await altoBundlerV06.estimateUserOperationGas({ - userOperation: opToSimulate - }) - } catch (e: unknown) { - if (!(e instanceof BaseError)) throw new InternalBundlerError() - const err = e.walk() as RpcRequestError - throw err - } - - const op = { - ...opToSimulate, - ...gasEstimates - } - const validAfter = 0 - const validUntil = Math.floor(Date.now() / 1000) + 6000 - op.paymasterAndData = concat([ - verifyingPaymasterV06.address, - encodeAbiParameters( - [ - { name: "validUntil", type: "uint48" }, - { name: "validAfter", type: "uint48" } - ], - [validUntil, validAfter] - ), - toHex(0, { size: 65 }) - ]) - const hash = await verifyingPaymasterV06.read.getHash([ - op, - validUntil, - validAfter - ]) - const sig = await walletClient.signMessage({ - message: { raw: hash } - }) - const paymasterAndData = concat([ - verifyingPaymasterV06.address, - encodeAbiParameters( - [ - { name: "validUntil", type: "uint48" }, - { name: "validAfter", type: "uint48" } - ], - [validUntil, validAfter] - ), - sig - ]) - - const { verificationGasLimit, preVerificationGas, callGasLimit } = - gasEstimates - - const result = { - preVerificationGas: toHex(preVerificationGas), - callGasLimit: toHex(callGasLimit), - verificationGasLimit: toHex(verificationGasLimit || 0), - paymasterAndData - } - - return result -} + let op = { + ...userOperation, + paymasterAndData: concat([ + verifyingPaymasterV06.address, + "0x000000000000000000000000000000000000000000000000000000006602f66a0000000000000000000000000000000000000000000000000000000000000000dba7a71bd49ae0174b1e4577b28f8b7c262d4085cfa192f1c19b516c85d2d1ef17eadeb549d71caf5d5f24fb6519088c1c13427343843131dd6ec19a3c6a350e1b", + ]), + }; + + const callGasLimit = userOperation.callGasLimit; + const verificationGasLimit = userOperation.verificationGasLimit; + const preVerificationGas = userOperation.preVerificationGas; + + if (estimateGas) { + let gasEstimates: + | EstimateUserOperationGasReturnType + | undefined = undefined; + try { + gasEstimates = await altoBundlerV06.estimateUserOperationGas({ + userOperation: op, + }); + } catch (e: unknown) { + if (!(e instanceof BaseError)) throw new InternalBundlerError(); + const err = e.walk() as RpcRequestError; + throw err; + } + + op = { + ...op, + ...gasEstimates, + }; + + op.callGasLimit = maxBigInt(op.callGasLimit, callGasLimit); + op.preVerificationGas = maxBigInt( + op.preVerificationGas, + preVerificationGas, + ); + op.verificationGasLimit = maxBigInt( + op.verificationGasLimit, + verificationGasLimit, + ); + } else if ( + userOperation.preVerificationGas === 1n || + userOperation.verificationGasLimit === 1n || + userOperation.callGasLimit === 1n + ) { + throw new RpcError( + "Gas Limit values (preVerificationGas, verificationGasLimit, callGasLimit) must be set", + ValidationErrors.InvalidFields, + ); + } + + const validAfter = 0; + const validUntil = 0; + op.paymasterAndData = concat([ + verifyingPaymasterV06.address, + encodeAbiParameters( + [ + { name: "validUntil", type: "uint48" }, + { name: "validAfter", type: "uint48" }, + ], + [validUntil, validAfter], + ), + toHex(0, { size: 65 }), + ]); + const hash = await verifyingPaymasterV06.read.getHash([ + op, + validUntil, + validAfter, + ]); + const sig = await walletClient.signMessage({ + message: { raw: hash }, + }); + const paymasterAndData = concat([ + verifyingPaymasterV06.address, + encodeAbiParameters( + [ + { name: "validUntil", type: "uint48" }, + { name: "validAfter", type: "uint48" }, + ], + [validUntil, validAfter], + ), + sig, + ]); + + const result = { + preVerificationGas: toHex(op.preVerificationGas), + callGasLimit: toHex(op.callGasLimit), + verificationGasLimit: toHex(op.verificationGasLimit || 0), + paymasterAndData, + }; + + return result; +}; const handleMethodV07 = async ( - userOperation: UserOperation<"v0.7">, - altoBundlerV07: PimlicoBundlerClient, - verifyingPaymasterV07: GetContractReturnType< - typeof VERIFYING_PAYMASTER_V07_ABI, - PublicClient - >, - walletClient: WalletClient + userOperation: UserOperation<"v0.7">, + altoBundlerV07: PimlicoBundlerClient, + verifyingPaymasterV07: GetContractReturnType< + typeof VERIFYING_PAYMASTER_V07_ABI, + PublicClient + >, + walletClient: WalletClient, + estimateGas: boolean, ) => { - const opToSimulate = { - ...userOperation, - paymaster: verifyingPaymasterV07.address, - paymasterData: - "0x000000000000000000000000000000000000000000000000000000006602f66a0000000000000000000000000000000000000000000000000000000000000000dba7a71bd49ae0174b1e4577b28f8b7c262d4085cfa192f1c19b516c85d2d1ef17eadeb549d71caf5d5f24fb6519088c1c13427343843131dd6ec19a3c6a350e1b" as Hex - } - - let gasEstimates: - | EstimateUserOperationGasReturnType - | undefined = undefined - try { - gasEstimates = await altoBundlerV07.estimateUserOperationGas({ - userOperation: opToSimulate - }) - } catch (e: unknown) { - if (!(e instanceof BaseError)) throw new InternalBundlerError() - const err = e.walk() as RpcRequestError - throw err - } - - const op = { - ...opToSimulate, - ...gasEstimates - } - const validAfter = 0 - const validUntil = Math.floor(Date.now() / 1000) + 6000 - op.paymasterData = concat([ - encodeAbiParameters( - [ - { name: "validUntil", type: "uint48" }, - { name: "validAfter", type: "uint48" } - ], - [validUntil, validAfter] - ), - toHex(0, { size: 65 }) - ]) - op.paymaster = verifyingPaymasterV07.address - const hash = await verifyingPaymasterV07.read.getHash([ - getPackedUserOperation(op), - validUntil, - validAfter - ]) - const sig = await walletClient.signMessage({ - message: { raw: hash } - }) - const paymaster = verifyingPaymasterV07.address - const paymasterData = concat([ - encodeAbiParameters( - [ - { name: "validUntil", type: "uint48" }, - { name: "validAfter", type: "uint48" } - ], - [validUntil, validAfter] - ), - sig - ]) - - const { - paymasterVerificationGasLimit, - verificationGasLimit, - preVerificationGas, - callGasLimit, - paymasterPostOpGasLimit - } = gasEstimates - - const result = { - preVerificationGas: toHex(preVerificationGas), - callGasLimit: toHex(callGasLimit), - paymasterVerificationGasLimit: toHex( - paymasterVerificationGasLimit || 0 - ), - paymasterPostOpGasLimit: toHex(paymasterPostOpGasLimit || 0), - verificationGasLimit: toHex(verificationGasLimit || 0), - paymaster, - paymasterData - } - - return result -} + let op = { + ...userOperation, + paymaster: verifyingPaymasterV07.address, + paymasterData: + "0x000000000000000000000000000000000000000000000000000000006602f66a0000000000000000000000000000000000000000000000000000000000000000dba7a71bd49ae0174b1e4577b28f8b7c262d4085cfa192f1c19b516c85d2d1ef17eadeb549d71caf5d5f24fb6519088c1c13427343843131dd6ec19a3c6a350e1b" as Hex, + }; + + const callGasLimit = userOperation.callGasLimit; + const verificationGasLimit = userOperation.verificationGasLimit; + const preVerificationGas = userOperation.preVerificationGas; + + if (estimateGas) { + let gasEstimates: + | EstimateUserOperationGasReturnType + | undefined = undefined; + try { + gasEstimates = await altoBundlerV07.estimateUserOperationGas({ + userOperation: op, + }); + } catch (e: unknown) { + if (!(e instanceof BaseError)) throw new InternalBundlerError(); + const err = e.walk() as RpcRequestError; + throw err; + } + + op = { + ...op, + ...gasEstimates, + }; + + op.callGasLimit = maxBigInt(op.callGasLimit, callGasLimit); + op.preVerificationGas = maxBigInt( + op.preVerificationGas, + preVerificationGas, + ); + op.verificationGasLimit = maxBigInt( + op.verificationGasLimit, + verificationGasLimit, + ); + } else if ( + userOperation.preVerificationGas === 1n || + userOperation.verificationGasLimit === 1n || + userOperation.callGasLimit === 1n + ) { + throw new RpcError( + "Gas Limit values (preVerificationGas, verificationGasLimit, callGasLimit) must be set", + ValidationErrors.InvalidFields, + ); + } + + const validAfter = 0; + const validUntil = 0; + op.paymasterData = concat([ + encodeAbiParameters( + [ + { name: "validUntil", type: "uint48" }, + { name: "validAfter", type: "uint48" }, + ], + [validUntil, validAfter], + ), + toHex(0, { size: 65 }), + ]); + op.paymaster = verifyingPaymasterV07.address; + const hash = await verifyingPaymasterV07.read.getHash([ + getPackedUserOperation(op), + validUntil, + validAfter, + ]); + const sig = await walletClient.signMessage({ + message: { raw: hash }, + }); + const paymaster = verifyingPaymasterV07.address; + const paymasterData = concat([ + encodeAbiParameters( + [ + { name: "validUntil", type: "uint48" }, + { name: "validAfter", type: "uint48" }, + ], + [validUntil, validAfter], + ), + sig, + ]); + + const result = { + preVerificationGas: toHex(op.preVerificationGas), + callGasLimit: toHex(op.callGasLimit), + paymasterVerificationGasLimit: toHex(op.paymasterVerificationGasLimit || 0), + paymasterPostOpGasLimit: toHex(op.paymasterPostOpGasLimit || 0), + verificationGasLimit: toHex(op.verificationGasLimit || 0), + paymaster, + paymasterData, + }; + + return result; +}; const handleMethod = async ( - altoBundlerV07: PimlicoBundlerClient, - altoBundlerV06: PimlicoBundlerClient, - verifyingPaymasterV07: GetContractReturnType< - typeof VERIFYING_PAYMASTER_V07_ABI, - PublicClient - >, - verifyingPaymasterV06: GetContractReturnType< - typeof VERIFYING_PAYMASTER_V06_ABI, - PublicClient - >, - walletClient: WalletClient, - parsedBody: JsonRpcSchema + altoBundlerV07: PimlicoBundlerClient, + altoBundlerV06: PimlicoBundlerClient, + verifyingPaymasterV07: GetContractReturnType< + typeof VERIFYING_PAYMASTER_V07_ABI, + PublicClient + >, + verifyingPaymasterV06: GetContractReturnType< + typeof VERIFYING_PAYMASTER_V06_ABI, + PublicClient + >, + walletClient: WalletClient, + parsedBody: JsonRpcSchema, ) => { - if (parsedBody.method === "pm_sponsorUserOperation") { - const params = pmSponsorUserOperationParamsSchema.safeParse( - parsedBody.params - ) - - if (!params.success) { - throw new RpcError( - fromZodError(params.error).message, - ValidationErrors.InvalidFields - ) - } - - const [userOperation, entryPoint] = params.data - - if (entryPoint === ENTRYPOINT_ADDRESS_V07) { - return await handleMethodV07( - userOperation as UserOperation<"v0.7">, - altoBundlerV07, - verifyingPaymasterV07, - walletClient - ) - } - - if (entryPoint === ENTRYPOINT_ADDRESS_V06) { - return await handleMethodV06( - userOperation as UserOperation<"v0.6">, - altoBundlerV06, - verifyingPaymasterV06, - walletClient - ) - } - - throw new RpcError( - "EntryPoint not supported", - ValidationErrors.InvalidFields - ) - } - - if (parsedBody.method === "pm_validateSponsorshipPolicies") { - return [ - { - sponsorshipPolicyId: "sp_crazy_kangaroo", - data: { - name: "Free ops for devs", - author: "foo", - icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==", - description: "Free userOps :)" - } - } - ] - } - - throw new RpcError( - "Attempted to call an unknown method", - ValidationErrors.InvalidFields - ) -} + if (parsedBody.method === "pm_sponsorUserOperation") { + const params = pmSponsorUserOperationParamsSchema.safeParse( + parsedBody.params, + ); + + if (!params.success) { + throw new RpcError( + fromZodError(params.error).message, + ValidationErrors.InvalidFields, + ); + } + + const [userOperation, entryPoint] = params.data; + + if (entryPoint === ENTRYPOINT_ADDRESS_V07) { + return await handleMethodV07( + userOperation as UserOperation<"v0.7">, + altoBundlerV07, + verifyingPaymasterV07, + walletClient, + true, + ); + } + + if (entryPoint === ENTRYPOINT_ADDRESS_V06) { + return await handleMethodV06( + userOperation as UserOperation<"v0.6">, + altoBundlerV06, + verifyingPaymasterV06, + walletClient, + true, + ); + } + + throw new RpcError( + "EntryPoint not supported", + ValidationErrors.InvalidFields, + ); + } + + if (parsedBody.method === "pm_getPaymasterStubData") { + const params = pmGetPaymasterStubDataParamsSchema.safeParse( + parsedBody.params, + ); + + if (!params.success) { + throw new RpcError( + fromZodError(params.error).message, + ValidationErrors.InvalidFields, + ); + } + + const [, entryPoint] = params.data; + + if (entryPoint === ENTRYPOINT_ADDRESS_V07) { + return { + paymaster: verifyingPaymasterV07.address, + paymasterData: + "0x00000000000000000000000000000000000000000000000000000101010101010000000000000000000000000000000000000000000000000000000000000000cd91f19f0f19ce862d7bec7b7d9b95457145afc6f639c28fd0360f488937bfa41e6eedcd3a46054fd95fcd0e3ef6b0bc0a615c4d975eef55c8a3517257904d5b1c", + paymasterVerificationGasLimit: toHex(50_000n), + paymasterPostOpGasLimit: toHex(20_000n), + }; + } + + if (entryPoint === ENTRYPOINT_ADDRESS_V06) { + return { + paymasterAndData: `${verifyingPaymasterV06.address}00000000000000000000000000000000000000000000000000000101010101010000000000000000000000000000000000000000000000000000000000000000cd91f19f0f19ce862d7bec7b7d9b95457145afc6f639c28fd0360f488937bfa41e6eedcd3a46054fd95fcd0e3ef6b0bc0a615c4d975eef55c8a3517257904d5b1c`, + }; + } + + throw new RpcError( + "EntryPoint not supported", + ValidationErrors.InvalidFields, + ); + } + + if (parsedBody.method === "pm_getPaymasterData") { + const params = pmGetPaymasterData.safeParse(parsedBody.params); + + if (!params.success) { + throw new RpcError( + fromZodError(params.error).message, + ValidationErrors.InvalidFields, + ); + } + + const [userOperation, entryPoint] = params.data; + + if (entryPoint === ENTRYPOINT_ADDRESS_V07) { + return await handleMethodV07( + userOperation as UserOperation<"v0.7">, + altoBundlerV07, + verifyingPaymasterV07, + walletClient, + false, + ); + } + + if (entryPoint === ENTRYPOINT_ADDRESS_V06) { + return await handleMethodV06( + userOperation as UserOperation<"v0.6">, + altoBundlerV06, + verifyingPaymasterV06, + walletClient, + false, + ); + } + + throw new RpcError( + "EntryPoint not supported", + ValidationErrors.InvalidFields, + ); + } + + if (parsedBody.method === "pm_validateSponsorshipPolicies") { + return [ + { + sponsorshipPolicyId: "sp_crazy_kangaroo", + data: { + name: "Free ops for devs", + author: "foo", + icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==", + description: "Free userOps :)", + }, + }, + ]; + } + + throw new RpcError( + "Attempted to call an unknown method", + ValidationErrors.InvalidFields, + ); +}; export const createRpcHandler = ( - altoBundlerV07: PimlicoBundlerClient, - altoBundlerV06: PimlicoBundlerClient, - verifyingPaymasterV07: GetContractReturnType< - typeof VERIFYING_PAYMASTER_V07_ABI, - PublicClient - >, - verifyingPaymasterV06: GetContractReturnType< - typeof VERIFYING_PAYMASTER_V06_ABI, - PublicClient - >, - walletClient: WalletClient + altoBundlerV07: PimlicoBundlerClient, + altoBundlerV06: PimlicoBundlerClient, + verifyingPaymasterV07: GetContractReturnType< + typeof VERIFYING_PAYMASTER_V07_ABI, + PublicClient + >, + verifyingPaymasterV06: GetContractReturnType< + typeof VERIFYING_PAYMASTER_V06_ABI, + PublicClient + >, + walletClient: WalletClient, ) => { - return async (request: FastifyRequest, _reply: FastifyReply) => { - console.log(`received request: ${JSON.stringify(request.body)}`) - - const body = request.body - const parsedBody = jsonRpcSchema.safeParse(body) - if (!parsedBody.success) { - throw new RpcError( - fromZodError(parsedBody.error).message, - ValidationErrors.InvalidFields - ) - } - - try { - const result = await handleMethod( - altoBundlerV07, - altoBundlerV06, - verifyingPaymasterV07, - verifyingPaymasterV06, - walletClient, - parsedBody.data - ) - - return { - jsonrpc: "2.0", - id: parsedBody.data.id, - result - } - } catch (err: unknown) { - console.log(`JSON.stringify(err): ${util.inspect(err)}`) - - const error = { - // biome-ignore lint/suspicious/noExplicitAny: - message: (err as any).message, - // biome-ignore lint/suspicious/noExplicitAny: - data: (err as any).data, - // biome-ignore lint/suspicious/noExplicitAny: - code: (err as any).code ?? -32603 - } - - return { - jsonrpc: "2.0", - id: parsedBody.data.id, - error - } - } - } -} + return async (request: FastifyRequest, _reply: FastifyReply) => { + const body = request.body; + const parsedBody = jsonRpcSchema.safeParse(body); + if (!parsedBody.success) { + throw new RpcError( + fromZodError(parsedBody.error).message, + ValidationErrors.InvalidFields, + ); + } + + try { + const result = await handleMethod( + altoBundlerV07, + altoBundlerV06, + verifyingPaymasterV07, + verifyingPaymasterV06, + walletClient, + parsedBody.data, + ); + + return { + jsonrpc: "2.0", + id: parsedBody.data.id, + result, + }; + } catch (err: unknown) { + console.log(`JSON.stringify(err): ${util.inspect(err)}`); + + const error = { + // biome-ignore lint/suspicious/noExplicitAny: + message: (err as any).message, + // biome-ignore lint/suspicious/noExplicitAny: + data: (err as any).data, + // biome-ignore lint/suspicious/noExplicitAny: + code: (err as any).code ?? -32603, + }; + + return { + jsonrpc: "2.0", + id: parsedBody.data.id, + error, + }; + } + }; +};