Skip to content

Commit

Permalink
feat(aa-sdk/core): add erc7677 middleware (#823)
Browse files Browse the repository at this point in the history
  • Loading branch information
moldy530 committed Aug 28, 2024
1 parent fd48725 commit 4a30808
Show file tree
Hide file tree
Showing 14 changed files with 277 additions and 56 deletions.
17 changes: 2 additions & 15 deletions aa-sdk/core/src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -37,15 +34,5 @@ export type ClientMiddlewareConfig<
TContext extends UserOperationContext | undefined =
| UserOperationContext
| undefined
> = Omit<
Partial<ClientMiddleware<TContext>>,
"dummyPaymasterAndData" | "paymasterAndData"
> & {
paymasterAndData?: {
dummyPaymasterAndData: () =>
| UserOperationRequest<"0.6.0">["paymasterAndData"]
| Pick<UserOperationRequest<"0.7.0">, "paymaster" | "paymasterData">;
paymasterAndData: ClientMiddlewareFn<TContext>;
};
};
> = Partial<ClientMiddleware<TContext>>;
// [!endregion ClientMiddlewareConfig]
2 changes: 2 additions & 0 deletions aa-sdk/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
27 changes: 3 additions & 24 deletions aa-sdk/core/src/middleware/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
isHex,
type Chain,
type Client,
type PublicActions,
Expand All @@ -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";
Expand Down Expand Up @@ -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,
Expand Down
187 changes: 187 additions & 0 deletions aa-sdk/core/src/middleware/erc7677middleware.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>];
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<string, any>];
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<T extends Transport = Transport> = Client<
T,
Chain,
undefined,
Erc7677RpcSchema
>;

export type Erc7677MiddlewareParams<
TContext extends Record<string, any> | undefined =
| Record<string, any>
| undefined,
TEntryPointVersion extends EntryPointVersion = EntryPointVersion
> = {
context?:
| ((
struct: Deferrable<UserOperationStruct<TEntryPointVersion>>,
args: {
overrides?: UserOperationOverrides<TEntryPointVersion>;
feeOptions?: UserOperationFeeOptions;
}
) => Promise<TContext>)
| 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<TContext>} 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<ClientMiddlewareConfig, "dummyPaymasterAndData" | "paymasterAndData">} An object containing middleware functions `dummyPaymasterAndData` and `paymasterAndData` for processing user operations with the paymaster data
*/
export function erc7677Middleware<
TContext extends Record<string, any> | undefined =
| Record<string, any>
| undefined
>(
params?: Erc7677MiddlewareParams<TContext>
): Pick<ClientMiddlewareConfig, "dummyPaymasterAndData" | "paymasterAndData"> {
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,
};
}
5 changes: 4 additions & 1 deletion aa-sdk/core/src/middleware/noopMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { UserOperationContext } from "../actions/smartAccount/types";
import type { ClientMiddlewareFn } from "./types";

/**
Expand All @@ -6,6 +7,8 @@ import type { ClientMiddlewareFn } from "./types";
* @param {Deferrable<UserOperationStruct<TEntryPointVersion>>} args the client middleware arguments passed to the middleware
* @returns {Promise<Deferrable<UserOperationStruct<TEntryPointVersion>>>} 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;
};
2 changes: 2 additions & 0 deletions account-kit/infra/e2e-tests/constants.ts
Original file line number Diff line number Diff line change
@@ -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!;
2 changes: 1 addition & 1 deletion account-kit/infra/e2e-tests/simple-account-v7.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 {
Expand Down
25 changes: 17 additions & 8 deletions account-kit/infra/src/middleware/gasManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -156,15 +158,20 @@ const dummyPaymasterAndData =
<C extends ClientWithAlchemyMethods>(
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]),
};
};

/**
Expand Down Expand Up @@ -238,9 +245,9 @@ export function alchemyGasManagerMiddleware<C extends ClientWithAlchemyMethods>(
maxPriorityFeePerGas,
};
},
paymasterAndData: disableGasEstimation
...(disableGasEstimation
? requestPaymasterAndData(client, config)
: requestGasAndPaymasterData(client, config),
: requestGasAndPaymasterData(client, config)),
};
}

Expand Down Expand Up @@ -304,10 +311,9 @@ const overrideField = <
function requestGasAndPaymasterData<C extends ClientWithAlchemyMethods>(
client: C,
config: AlchemyGasManagerConfig
): ClientMiddlewareConfig["paymasterAndData"] {
): Pick<ClientMiddlewareConfig, "dummyPaymasterAndData" | "paymasterAndData"> {
return {
dummyPaymasterAndData: dummyPaymasterAndData(client, config),

paymasterAndData: async (
struct,
{ overrides: overrides_, feeOptions, account }
Expand Down Expand Up @@ -410,7 +416,10 @@ function requestGasAndPaymasterData<C extends ClientWithAlchemyMethods>(
const requestPaymasterAndData: <C extends ClientWithAlchemyMethods>(
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({
Expand Down
Loading

0 comments on commit 4a30808

Please sign in to comment.