diff --git a/aa-sdk/core/src/client/types.ts b/aa-sdk/core/src/client/types.ts index e245bd902d..bf482e45c0 100644 --- a/aa-sdk/core/src/client/types.ts +++ b/aa-sdk/core/src/client/types.ts @@ -3,10 +3,7 @@ import type { Hash, Hex } from "viem"; import type { z } from "zod"; import type { UserOperationContext } from "../actions/smartAccount/types.js"; import type { EntryPointVersion } from "../entrypoint/types.js"; -import type { - ClientMiddleware, - ClientMiddlewareFn, -} from "../middleware/types.js"; +import type { ClientMiddleware } from "../middleware/types.js"; import type { UserOperationRequest } from "../types.js"; import type { ConnectionConfigSchema } from "./schema.js"; @@ -37,15 +34,5 @@ export type ClientMiddlewareConfig< TContext extends UserOperationContext | undefined = | UserOperationContext | undefined -> = Omit< - Partial>, - "dummyPaymasterAndData" | "paymasterAndData" -> & { - paymasterAndData?: { - dummyPaymasterAndData: () => - | UserOperationRequest<"0.6.0">["paymasterAndData"] - | Pick, "paymaster" | "paymasterData">; - paymasterAndData: ClientMiddlewareFn; - }; -}; +> = Partial>; // [!endregion ClientMiddlewareConfig] diff --git a/aa-sdk/core/src/index.ts b/aa-sdk/core/src/index.ts index aa8254ab76..29f9280d13 100644 --- a/aa-sdk/core/src/index.ts +++ b/aa-sdk/core/src/index.ts @@ -94,6 +94,8 @@ export { defaultFeeEstimator } from "./middleware/defaults/feeEstimator.js"; export { defaultGasEstimator } from "./middleware/defaults/gasEstimator.js"; export { defaultPaymasterAndData } from "./middleware/defaults/paymasterAndData.js"; export { defaultUserOpSigner } from "./middleware/defaults/userOpSigner.js"; +export type * from "./middleware/erc7677middleware.js"; +export { erc7677Middleware } from "./middleware/erc7677middleware.js"; export { noopMiddleware } from "./middleware/noopMiddleware.js"; export type * from "./middleware/types.js"; export { LocalAccountSigner } from "./signer/local-account.js"; diff --git a/aa-sdk/core/src/middleware/actions.ts b/aa-sdk/core/src/middleware/actions.ts index 0e48af44a9..80433b1be5 100644 --- a/aa-sdk/core/src/middleware/actions.ts +++ b/aa-sdk/core/src/middleware/actions.ts @@ -1,5 +1,4 @@ import { - isHex, type Chain, type Client, type PublicActions, @@ -12,10 +11,6 @@ import type { BundlerRpcSchema, } from "../client/decorators/bundlerClient.js"; import type { ClientMiddlewareConfig } from "../client/types.js"; -import { - concatPaymasterAndData, - parsePaymasterAndData, -} from "../utils/userop.js"; import { defaultFeeEstimator } from "./defaults/feeEstimator.js"; import { defaultGasEstimator } from "./defaults/gasEstimator.js"; import { defaultPaymasterAndData } from "./defaults/paymasterAndData.js"; @@ -64,27 +59,11 @@ export const middlewareActions = ): { middleware: ClientMiddleware } => ({ middleware: { customMiddleware: overrides.customMiddleware ?? noopMiddleware, - dummyPaymasterAndData: overrides.paymasterAndData?.dummyPaymasterAndData - ? async (struct, { account }) => { - const data = overrides.paymasterAndData!.dummyPaymasterAndData(); - const paymasterOverrides = - account.getEntryPoint().version === "0.7.0" - ? isHex(data) - ? parsePaymasterAndData(data) - : data - : { - paymasterAndData: isHex(data) - ? data - : concatPaymasterAndData(data), - }; - return { ...struct, ...paymasterOverrides }; - } - : defaultPaymasterAndData, - + dummyPaymasterAndData: + overrides.dummyPaymasterAndData ?? defaultPaymasterAndData, feeEstimator: overrides.feeEstimator ?? defaultFeeEstimator(client), gasEstimator: overrides.gasEstimator ?? defaultGasEstimator(client), - paymasterAndData: - overrides.paymasterAndData?.paymasterAndData ?? defaultPaymasterAndData, + paymasterAndData: overrides.paymasterAndData ?? defaultPaymasterAndData, userOperationSimulator: overrides.userOperationSimulator ?? noopMiddleware, signUserOperation: overrides.signUserOperation ?? defaultUserOpSigner, diff --git a/aa-sdk/core/src/middleware/erc7677middleware.ts b/aa-sdk/core/src/middleware/erc7677middleware.ts new file mode 100644 index 0000000000..ece7bb578b --- /dev/null +++ b/aa-sdk/core/src/middleware/erc7677middleware.ts @@ -0,0 +1,187 @@ +import { + toHex, + type Address, + type Chain, + type Client, + type Hex, + type Transport, +} from "viem"; +import type { ClientMiddlewareConfig } from "../client/types"; +import type { EntryPointVersion } from "../entrypoint/types"; +import { ChainNotFoundError } from "../errors/client.js"; +import type { + UserOperationFeeOptions, + UserOperationOverrides, + UserOperationRequest, + UserOperationStruct, +} from "../types"; +import { + deepHexlify, + resolveProperties, + type Deferrable, +} from "../utils/index.js"; +import type { ClientMiddlewareFn } from "./types"; + +export type Erc7677RpcSchema = [ + { + Method: "pm_getPaymasterStubData"; + Parameters: [UserOperationRequest, Address, Hex, Record]; + ReturnType: { + sponsor?: { name: string; icon?: string }; // Sponsor info + paymaster?: Address; // Paymaster address (entrypoint v0.7) + paymasterData?: Hex; // Paymaster data (entrypoint v0.7) + paymasterVerificationGasLimit?: Hex; // Paymaster validation gas (entrypoint v0.7) + paymasterPostOpGasLimit?: Hex; // Paymaster post-op gas (entrypoint v0.7) + paymasterAndData?: Hex; // Paymaster and data (entrypoint v0.6) + isFinal?: boolean; // Indicates that the caller does not need to call pm_getPaymasterData + }; + }, + { + Method: "pm_getPaymasterData"; + Parameters: [UserOperationRequest, Address, Hex, Record]; + ReturnType: { + paymaster?: Address; // Paymaster address (entrypoint v0.7) + paymasterData?: Hex; // Paymaster data (entrypoint v0.7) + paymasterAndData?: Hex; // Paymaster and data (entrypoint v0.6) + }; + } +]; + +export type Erc7677Client = Client< + T, + Chain, + undefined, + Erc7677RpcSchema +>; + +export type Erc7677MiddlewareParams< + TContext extends Record | undefined = + | Record + | undefined, + TEntryPointVersion extends EntryPointVersion = EntryPointVersion +> = { + context?: + | (( + struct: Deferrable>, + args: { + overrides?: UserOperationOverrides; + feeOptions?: UserOperationFeeOptions; + } + ) => Promise) + | TContext; +}; + +/** + * Middleware function for interacting with ERC-7677 enabled clients. It supports resolving paymaster and data fields for user operations. + * This middleware assumes that your RPC provider supports the ERC-7677 methods (pm_getPaymasterStubData and pm_getPaymasterData). + * + * @example + * ```ts + * import { createSmartAccountClient, erc7677Middleware } from "@aa-sdk/core"; + * import { http } from "viem"; + * import { sepolia } from "viem/chains"; + * + * const client = createSmartAccountClient({ + * transport: http("rpc-url"), + * chain: sepolia, + * // this assumes that your RPC provider supports the ERC-7677 methods AND takes no context + * ...erc7677Middleware(), + * }) + * ``` + * + * @param {Erc7677MiddlewareParams} params Middleware parameters including context function or object. Context can be resolved dynamically by passing in a function which takes in the context at the time of sending a user op + * @returns {Pick} An object containing middleware functions `dummyPaymasterAndData` and `paymasterAndData` for processing user operations with the paymaster data + */ +export function erc7677Middleware< + TContext extends Record | undefined = + | Record + | undefined +>( + params?: Erc7677MiddlewareParams +): Pick { + const dummyPaymasterAndData: ClientMiddlewareFn = async ( + uo, + { client, account, feeOptions, overrides } + ) => { + const userOp = deepHexlify(await resolveProperties(uo)); + const context = + (typeof params?.context === "function" + ? await params?.context(userOp, { overrides, feeOptions }) + : params?.context) ?? {}; + + if (!client.chain) { + throw new ChainNotFoundError(); + } + + const erc7677client = client as Erc7677Client; + const entrypoint = account.getEntryPoint(); + // TODO: probably need to handle the sponsor and isFinal fields + const { + paymaster, + paymasterAndData, + paymasterData, + paymasterPostOpGasLimit, + paymasterVerificationGasLimit, + } = await erc7677client.request({ + method: "pm_getPaymasterStubData", + params: [userOp, entrypoint.address, toHex(client.chain.id), context], + }); + + if (entrypoint.version === "0.6.0") { + return { + ...uo, + paymasterAndData, + }; + } + + return { + ...uo, + paymaster, + paymasterData, + paymasterPostOpGasLimit, + paymasterVerificationGasLimit, + }; + }; + + const paymasterAndData: ClientMiddlewareFn = async ( + uo, + { client, account, feeOptions, overrides } + ) => { + const userOp = deepHexlify(await resolveProperties(uo)); + const context = + (typeof params?.context === "function" + ? await params?.context(userOp, { overrides, feeOptions }) + : params?.context) ?? {}; + + if (!client.chain) { + throw new ChainNotFoundError(); + } + + const erc7677client = client as Erc7677Client; + + const entrypoint = account.getEntryPoint(); + const { paymaster, paymasterAndData, paymasterData } = + await erc7677client.request({ + method: "pm_getPaymasterData", + params: [userOp, entrypoint.address, toHex(client.chain.id), context], + }); + + if (entrypoint.version === "0.6.0") { + return { + ...uo, + paymasterAndData, + }; + } + + return { + ...uo, + paymaster, + paymasterData, + }; + }; + + return { + dummyPaymasterAndData, + paymasterAndData, + }; +} diff --git a/aa-sdk/core/src/middleware/noopMiddleware.ts b/aa-sdk/core/src/middleware/noopMiddleware.ts index d3e7fcd257..9cab009578 100644 --- a/aa-sdk/core/src/middleware/noopMiddleware.ts +++ b/aa-sdk/core/src/middleware/noopMiddleware.ts @@ -1,3 +1,4 @@ +import type { UserOperationContext } from "../actions/smartAccount/types"; import type { ClientMiddlewareFn } from "./types"; /** @@ -6,6 +7,8 @@ import type { ClientMiddlewareFn } from "./types"; * @param {Deferrable>} args the client middleware arguments passed to the middleware * @returns {Promise>>} the arguments passed to the middleware and returned as is without modification */ -export const noopMiddleware: ClientMiddlewareFn = async (args) => { +export const noopMiddleware: ClientMiddlewareFn< + UserOperationContext | undefined +> = async (args) => { return args; }; diff --git a/account-kit/infra/e2e-tests/constants.ts b/account-kit/infra/e2e-tests/constants.ts index c8daf509d5..c82fcd13b4 100644 --- a/account-kit/infra/e2e-tests/constants.ts +++ b/account-kit/infra/e2e-tests/constants.ts @@ -1,2 +1,4 @@ export const API_KEY = process.env.API_KEY!; export const PAYMASTER_POLICY_ID = process.env.PAYMASTER_POLICY_ID!; +export const LIGHT_ACCOUNT_OWNER_MNEMONIC = + process.env.LIGHT_ACCOUNT_OWNER_MNEMONIC!; diff --git a/account-kit/infra/e2e-tests/simple-account-v7.e2e.test.ts b/account-kit/infra/e2e-tests/simple-account-v7.e2e.test.ts index c5581553b2..5c1951b237 100644 --- a/account-kit/infra/e2e-tests/simple-account-v7.e2e.test.ts +++ b/account-kit/infra/e2e-tests/simple-account-v7.e2e.test.ts @@ -2,7 +2,6 @@ import { LocalAccountSigner, createSimpleSmartAccount, getEntryPoint, - sepolia, type UserOperationStruct, } from "@aa-sdk/core"; import type { CreateSimpleAccountParams } from "@aa-sdk/core/dist/types/account/simple.js"; @@ -13,6 +12,7 @@ import { alchemyEnhancedApiActions } from "../src/client/decorators/alchemyEnhan import { createAlchemySmartAccountClientFromRpcClient } from "../src/client/internal/smartAccountClientFromRpc.js"; import { createAlchemyPublicRpcClient, + sepolia, type AlchemySmartAccountClientConfig, } from "../src/index.js"; import { diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index bd13904ea8..7b446df672 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -147,6 +147,8 @@ export interface AlchemyGasEstimationOptions { /** * Dummy paymaster and data middleware for the alchemy gas manager * + * NOTE: right now, this only really works for 0.6.0 unless you pass in overrides in the config for 0.7.0 + * * @template {ClientWithAlchemyMethods} C * @param {ClientWithAlchemyMethods} client client with alchemy methods * @param {AlchemyGasManagerConfig} config alchemy gas manager configuration @@ -156,15 +158,20 @@ const dummyPaymasterAndData = ( client: C, config: AlchemyGasManagerConfig - ) => - () => { + ): ClientMiddlewareFn => + async (uo) => { const paymaster = config.paymasterAddress ?? getAlchemyPaymasterAddress(client.chain); const paymasterData = config.dummyData ?? "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"; - return concat([paymaster, paymasterData]); // or you can also return { paymaster, paymasterData } + return { + ...uo, + paymaster, + paymasterData, + paymasterAndData: concat([paymaster, paymasterData]), + }; }; /** @@ -238,9 +245,9 @@ export function alchemyGasManagerMiddleware( maxPriorityFeePerGas, }; }, - paymasterAndData: disableGasEstimation + ...(disableGasEstimation ? requestPaymasterAndData(client, config) - : requestGasAndPaymasterData(client, config), + : requestGasAndPaymasterData(client, config)), }; } @@ -304,10 +311,9 @@ const overrideField = < function requestGasAndPaymasterData( client: C, config: AlchemyGasManagerConfig -): ClientMiddlewareConfig["paymasterAndData"] { +): Pick { return { dummyPaymasterAndData: dummyPaymasterAndData(client, config), - paymasterAndData: async ( struct, { overrides: overrides_, feeOptions, account } @@ -410,7 +416,10 @@ function requestGasAndPaymasterData( const requestPaymasterAndData: ( client: C, config: AlchemyGasManagerConfig -) => ClientMiddlewareConfig["paymasterAndData"] = (client, config) => ({ +) => Pick< + ClientMiddlewareConfig, + "dummyPaymasterAndData" | "paymasterAndData" +> = (client, config) => ({ dummyPaymasterAndData: dummyPaymasterAndData(client, config), paymasterAndData: async (struct, { account }) => { const result = await client.request({ diff --git a/account-kit/infra/src/middleware/userOperationSimulator.ts b/account-kit/infra/src/middleware/userOperationSimulator.ts index 47f1585e44..b0cbf234b1 100644 --- a/account-kit/infra/src/middleware/userOperationSimulator.ts +++ b/account-kit/infra/src/middleware/userOperationSimulator.ts @@ -2,6 +2,7 @@ import { deepHexlify, resolveProperties, type ClientMiddlewareFn, + type UserOperationContext, } from "@aa-sdk/core"; import type { ClientWithAlchemyMethods } from "../client/types"; @@ -25,8 +26,11 @@ import type { ClientWithAlchemyMethods } from "../client/types"; * @returns {ClientMiddlewareFn} A middleware function to simulate and process user operations */ export function alchemyUserOperationSimulator< - C extends ClientWithAlchemyMethods ->(client: C): ClientMiddlewareFn { + C extends ClientWithAlchemyMethods, + TContext extends UserOperationContext | undefined = + | UserOperationContext + | undefined +>(client: C): ClientMiddlewareFn { return async (struct, { account }) => { const uoSimResult = await client.request({ method: "alchemy_simulateUserOperationAssetChanges", diff --git a/account-kit/smart-contracts/src/light-account/e2e-tests/light-account-v2.e2e.test.ts b/account-kit/smart-contracts/src/light-account/e2e-tests/light-account-v2.e2e.test.ts index ea16148b61..0c85b454c4 100644 --- a/account-kit/smart-contracts/src/light-account/e2e-tests/light-account-v2.e2e.test.ts +++ b/account-kit/smart-contracts/src/light-account/e2e-tests/light-account-v2.e2e.test.ts @@ -2,13 +2,13 @@ import { LocalAccountSigner, LogLevel, Logger, - arbitrumSepolia, createBundlerClient, createSmartAccountClientFromExisting, getEntryPoint, type SmartAccountSigner, type UserOperationFeeOptions, } from "@aa-sdk/core"; +import { arbitrumSepolia } from "@account-kit/infra"; import { custom, http, diff --git a/account-kit/smart-contracts/src/light-account/e2e-tests/light-account.e2e.test.ts b/account-kit/smart-contracts/src/light-account/e2e-tests/light-account.e2e.test.ts index d0df14eb18..8f63dd0d1c 100644 --- a/account-kit/smart-contracts/src/light-account/e2e-tests/light-account.e2e.test.ts +++ b/account-kit/smart-contracts/src/light-account/e2e-tests/light-account.e2e.test.ts @@ -4,10 +4,10 @@ import { Logger, createBundlerClient, createSmartAccountClientFromExisting, - sepolia, type SmartAccountSigner, type UserOperationFeeOptions, } from "@aa-sdk/core"; +import { sepolia } from "@account-kit/infra"; import { http, isAddress, diff --git a/account-kit/smart-contracts/src/light-account/e2e-tests/multi-owner-light-account.e2e.test.ts b/account-kit/smart-contracts/src/light-account/e2e-tests/multi-owner-light-account.e2e.test.ts index 91d1a8745d..7ce4e5af73 100644 --- a/account-kit/smart-contracts/src/light-account/e2e-tests/multi-owner-light-account.e2e.test.ts +++ b/account-kit/smart-contracts/src/light-account/e2e-tests/multi-owner-light-account.e2e.test.ts @@ -2,12 +2,12 @@ import { LocalAccountSigner, LogLevel, Logger, - arbitrumSepolia, createBundlerClient, createSmartAccountClientFromExisting, type SmartAccountSigner, type UserOperationFeeOptions, } from "@aa-sdk/core"; +import { arbitrumSepolia } from "@account-kit/infra"; import { http, isAddress, @@ -18,7 +18,7 @@ import { import { generatePrivateKey } from "viem/accounts"; import { multiOwnerPluginActions, - type LightAccountVersion, + type GetLightAccountVersion, } from "../../index.js"; import { getMSCAUpgradeToData } from "../../msca/utils.js"; import { createMultiOwnerLightAccountClient } from "../clients/multiOwnerLightAccount.js"; @@ -278,7 +278,7 @@ const givenConnectedClient = async ({ chain: Chain; accountAddress?: Address; feeOptions?: UserOperationFeeOptions; - version?: LightAccountVersion<"MultiOwnerLightAccount">; + version?: GetLightAccountVersion<"MultiOwnerLightAccount">; }) => { return createMultiOwnerLightAccountClient({ transport: http(`${chain.rpcUrls.alchemy.http[0]}/${API_KEY!}`), diff --git a/site/pages/reference/aa-sdk/core/functions/erc7677Middleware.mdx b/site/pages/reference/aa-sdk/core/functions/erc7677Middleware.mdx new file mode 100644 index 0000000000..756e868be6 --- /dev/null +++ b/site/pages/reference/aa-sdk/core/functions/erc7677Middleware.mdx @@ -0,0 +1,44 @@ +--- +# This file is autogenerated + +title: erc7677Middleware +description: Overview of the erc7677Middleware method +--- + +# erc7677Middleware + +Middleware function for interacting with ERC-7677 enabled clients. It supports resolving paymaster and data fields for user operations. +This middleware assumes that your RPC provider supports the ERC-7677 methods (pm_getPaymasterStubData and pm_getPaymasterData). + +## Import + +```ts +import { erc7677Middleware } from "@aa-sdk/core"; +``` + +## Usage + +```ts +import { createSmartAccountClient, erc7677Middleware } from "@aa-sdk/core"; +import { http } from "viem"; +import { sepolia } from "viem/chains"; + +const client = createSmartAccountClient({ + transport: http("rpc-url"), + chain: sepolia, + // this assumes that your RPC provider supports the ERC-7677 methods AND takes no context + ...erc7677Middleware(), +}); +``` + +## Parameters + +### params + +`Erc7677MiddlewareParams` +Middleware parameters including context function or object. Context can be resolved dynamically by passing in a function which takes in the context at the time of sending a user op + +## Returns + +`Pick` +An object containing middleware functions `dummyPaymasterAndData` and `paymasterAndData` for processing user operations with the paymaster data diff --git a/site/sidebar/reference/aa-sdk/core.ts b/site/sidebar/reference/aa-sdk/core.ts index 326c03f2b5..0acdf3888c 100644 --- a/site/sidebar/reference/aa-sdk/core.ts +++ b/site/sidebar/reference/aa-sdk/core.ts @@ -94,6 +94,10 @@ export const aaSdkCoreReferenceSidebar: SidebarItem[] = [ text: "dropAndReplaceUserOperation", link: "/reference/aa-sdk/core/functions/dropAndReplaceUserOperation", }, + { + text: "erc7677Middleware", + link: "/reference/aa-sdk/core/functions/erc7677Middleware", + }, { text: "filterUndefined", link: "/reference/aa-sdk/core/functions/filterUndefined",