From 15a83165b6d0131fa1953ed54368d7f7850648b7 Mon Sep 17 00:00:00 2001 From: "Pengfei(Andy) Zhang" Date: Wed, 23 Apr 2025 15:12:36 -0400 Subject: [PATCH 1/9] feat: add erc20Context to sdk --- account-kit/infra/src/actions/types.ts | 8 +++++- .../infra/src/middleware/gasManager.ts | 28 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/account-kit/infra/src/actions/types.ts b/account-kit/infra/src/actions/types.ts index 4a52dbb229..ff3f5e6b98 100644 --- a/account-kit/infra/src/actions/types.ts +++ b/account-kit/infra/src/actions/types.ts @@ -57,6 +57,9 @@ export type RequestGasAndPaymasterAndDataRequest = [ { policyId: string | string[]; entryPoint: Address; + erc20Context?: { + tokenAddress: string; + }; dummySignature: Hex; userOperation: UserOperationRequest; overrides?: UserOperationOverrides; @@ -76,6 +79,7 @@ export type RequestGasAndPaymasterAndDataResponse< (TEntryPointVersion extends "0.6.0" ? { paymasterAndData: UserOperationRequest<"0.6.0">["paymasterAndData"]; + maxTokenAmount: undefined | string; } : TEntryPointVersion extends "0.7.0" ? Pick< @@ -84,5 +88,7 @@ export type RequestGasAndPaymasterAndDataResponse< | "paymasterData" | "paymasterVerificationGasLimit" | "paymasterPostOpGasLimit" - > + > & { + maxTokenAmount: undefined | string; + } : never); diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index 362088c336..77bd8e70bc 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -57,6 +57,18 @@ interface AlchemyGasAndPaymasterAndDataMiddlewareParams { transport: AlchemyTransport; gasEstimatorOverride?: ClientMiddlewareFn; feeEstimatorOverride?: ClientMiddlewareFn; + policyToken?: PolicyToken; +} + +export type PolicyToken = { + address: string; + approvalMode: ApprovalMode; +}; + +export enum ApprovalMode { + INJECT_APPROVAL = 2, + PERMIT = 1, + NONE = 0, } /** @@ -94,8 +106,13 @@ export function alchemyGasAndPaymasterAndDataMiddleware( ClientMiddlewareConfig, "dummyPaymasterAndData" | "feeEstimator" | "gasEstimator" | "paymasterAndData" > { - const { policyId, transport, gasEstimatorOverride, feeEstimatorOverride } = - params; + const { + policyId, + transport, + gasEstimatorOverride, + feeEstimatorOverride, + policyToken, + } = params; return { dummyPaymasterAndData: async (uo, args) => { if ( @@ -197,6 +214,13 @@ export function alchemyGasAndPaymasterAndDataMiddleware( userOperation: userOp, dummySignature: await account.getDummySignature(), overrides, + ...(policyToken + ? { + erc20Context: { + tokenAddress: policyToken.address, + }, + } + : {}), }, ], }); From 6d51636125583dde7ded65c19eb18c60efd67542 Mon Sep 17 00:00:00 2001 From: "Pengfei(Andy) Zhang" Date: Thu, 24 Apr 2025 17:01:42 -0400 Subject: [PATCH 2/9] feat: insert permit for erc20 paymaster --- .../infra/src/middleware/gasManager.ts | 84 +++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index 77bd8e70bc..ac1d11c93a 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -20,7 +20,7 @@ import { noopMiddleware, resolveProperties, } from "@aa-sdk/core"; -import { fromHex, isHex, type Hex } from "viem"; +import { fromHex, isHex, toHex, type Hex } from "viem"; import type { AlchemySmartAccountClient } from "../client/smartAccountClient.js"; import type { AlchemyTransport } from "../alchemyTransport.js"; import { alchemyFeeEstimator } from "./feeEstimator.js"; @@ -62,7 +62,10 @@ interface AlchemyGasAndPaymasterAndDataMiddlewareParams { export type PolicyToken = { address: string; + maxTokenAmount: bigint; approvalMode: ApprovalMode; + erc20Name: string; + version: string; }; export enum ApprovalMode { @@ -205,6 +208,79 @@ export function alchemyGasAndPaymasterAndDataMiddleware( : {}), }); + let erc20Context = undefined; + if ( + policyToken !== undefined && + policyToken.approvalMode == ApprovalMode.PERMIT + ) { + // get a paymaster address + let paymasterAddress = "0x"; + const paymasterData = await ( + client as AlchemySmartAccountClient + ).request({ + method: "pm_getPaymasterStubData", + params: [ + userOp, + account.getEntryPoint().address, + toHex(client.chain.id), + { + policyId: Array.isArray(policyId) ? policyId[0] : policyId, + }, + ], + }); + paymasterAddress = paymasterData.paymaster + ? paymasterData.paymaster + : paymasterData.paymasterAndData + ? paymasterData.paymasterAndData.slice(0, 42) + : "0x"; + const typed_permit_data = { + types: { + Permit: [ + { + name: "owner", + type: "address", + }, + { + name: "spender", + type: "address", + }, + { + name: "value", + type: "uint256", + }, + { + name: "nonce", + type: "uint256", + }, + { + name: "deadline", + type: "uint256", + }, + ], + }, + primaryType: "Permit", + domain: { + name: policyToken.erc20Name, + version: policyToken.version, + chainId: client.chain.id, + verifyingContract: policyToken.address as Hex, + }, + message: { + owner: account.address as Hex, + spender: paymasterAddress as Hex, + value: policyToken.maxTokenAmount, + nonce: (await account.getAccountNonce()) + BigInt(1), + deadline: BigInt(0xffffffffffffffffffffffffffffffff), + }, + } as const; + + erc20Context = { + tokenAddress: policyToken.address, + permit: client.account?.signTypedData(typed_permit_data), + maxTokenAmount: policyToken.maxTokenAmount, + }; + } + const result = await (client as AlchemySmartAccountClient).request({ method: "alchemy_requestGasAndPaymasterAndData", params: [ @@ -214,11 +290,9 @@ export function alchemyGasAndPaymasterAndDataMiddleware( userOperation: userOp, dummySignature: await account.getDummySignature(), overrides, - ...(policyToken + ...(erc20Context ? { - erc20Context: { - tokenAddress: policyToken.address, - }, + erc20Context: erc20Context, } : {}), }, From 22b057ef2e0430d3faf20f3c668507956a9c8861 Mon Sep 17 00:00:00 2001 From: "Pengfei(Andy) Zhang" Date: Thu, 24 Apr 2025 17:01:42 -0400 Subject: [PATCH 3/9] feat: insert permit for erc20 paymaster --- account-kit/infra/src/middleware/gasManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index ac1d11c93a..257e1c16b3 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -211,7 +211,7 @@ export function alchemyGasAndPaymasterAndDataMiddleware( let erc20Context = undefined; if ( policyToken !== undefined && - policyToken.approvalMode == ApprovalMode.PERMIT + policyToken.approvalMode === ApprovalMode.PERMIT ) { // get a paymaster address let paymasterAddress = "0x"; @@ -276,7 +276,7 @@ export function alchemyGasAndPaymasterAndDataMiddleware( erc20Context = { tokenAddress: policyToken.address, - permit: client.account?.signTypedData(typed_permit_data), + permit: account.signTypedData(typed_permit_data), maxTokenAmount: policyToken.maxTokenAmount, }; } From 4be296b9136e6e82e5cec84acfb4600dc9bb1b85 Mon Sep 17 00:00:00 2001 From: "Pengfei(Andy) Zhang" Date: Fri, 25 Apr 2025 11:00:10 -0400 Subject: [PATCH 4/9] feat: add policyToken to config --- .../infra/src/client/smartAccountClient.ts | 8 ++++++++ account-kit/infra/src/middleware/gasManager.ts | 17 ++++------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/account-kit/infra/src/client/smartAccountClient.ts b/account-kit/infra/src/client/smartAccountClient.ts index 6c6791550a..e965ada5b5 100644 --- a/account-kit/infra/src/client/smartAccountClient.ts +++ b/account-kit/infra/src/client/smartAccountClient.ts @@ -48,6 +48,13 @@ export type AlchemySmartAccountClientConfig< account?: account; useSimulation?: boolean; policyId?: string | string[]; + policyToken?: { + address: string; + approvalMode?: "NONE" | "PERMIT" | "INJECT_APPROVAL"; + maxTokenAmount: bigint; + erc20Name: string; + version: string; + }; } & Pick< SmartAccountClientConfig, | "customMiddleware" @@ -163,6 +170,7 @@ export function createAlchemySmartAccountClient( ...(config.policyId ? alchemyGasAndPaymasterAndDataMiddleware({ policyId: config.policyId, + policyToken: config.policyToken, transport: config.transport, gasEstimatorOverride: config.gasEstimator, feeEstimatorOverride: config.feeEstimator, diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index 257e1c16b3..3e508fc6fd 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -54,26 +54,20 @@ export function alchemyGasManagerMiddleware( interface AlchemyGasAndPaymasterAndDataMiddlewareParams { policyId: string | string[]; + policyToken?: PolicyToken; transport: AlchemyTransport; gasEstimatorOverride?: ClientMiddlewareFn; feeEstimatorOverride?: ClientMiddlewareFn; - policyToken?: PolicyToken; } export type PolicyToken = { address: string; maxTokenAmount: bigint; - approvalMode: ApprovalMode; + approvalMode?: "NONE" | "PERMIT" | "INJECT_APPROVAL"; erc20Name: string; version: string; }; -export enum ApprovalMode { - INJECT_APPROVAL = 2, - PERMIT = 1, - NONE = 0, -} - /** * Paymaster middleware factory that uses Alchemy's Gas Manager for sponsoring * transactions. Uses Alchemy's custom `alchemy_requestGasAndPaymasterAndData` @@ -111,10 +105,10 @@ export function alchemyGasAndPaymasterAndDataMiddleware( > { const { policyId, + policyToken, transport, gasEstimatorOverride, feeEstimatorOverride, - policyToken, } = params; return { dummyPaymasterAndData: async (uo, args) => { @@ -209,10 +203,7 @@ export function alchemyGasAndPaymasterAndDataMiddleware( }); let erc20Context = undefined; - if ( - policyToken !== undefined && - policyToken.approvalMode === ApprovalMode.PERMIT - ) { + if (policyToken !== undefined && policyToken.approvalMode === "PERMIT") { // get a paymaster address let paymasterAddress = "0x"; const paymasterData = await ( From d66b14571b5a1ee8348a689aa0ae98f7b0fd1481 Mon Sep 17 00:00:00 2001 From: "Pengfei(Andy) Zhang" Date: Fri, 25 Apr 2025 11:44:23 -0400 Subject: [PATCH 5/9] feat: drop INJECT_APPROVE, pass erc20Context for NONE --- account-kit/infra/src/actions/types.ts | 7 +- .../infra/src/client/smartAccountClient.ts | 2 +- .../infra/src/middleware/gasManager.ts | 154 ++++++++++-------- 3 files changed, 94 insertions(+), 69 deletions(-) diff --git a/account-kit/infra/src/actions/types.ts b/account-kit/infra/src/actions/types.ts index ff3f5e6b98..be3d3e2ff6 100644 --- a/account-kit/infra/src/actions/types.ts +++ b/account-kit/infra/src/actions/types.ts @@ -59,6 +59,8 @@ export type RequestGasAndPaymasterAndDataRequest = [ entryPoint: Address; erc20Context?: { tokenAddress: string; + permit?: Hex; + maxTokenAmount: BigInt; }; dummySignature: Hex; userOperation: UserOperationRequest; @@ -79,7 +81,6 @@ export type RequestGasAndPaymasterAndDataResponse< (TEntryPointVersion extends "0.6.0" ? { paymasterAndData: UserOperationRequest<"0.6.0">["paymasterAndData"]; - maxTokenAmount: undefined | string; } : TEntryPointVersion extends "0.7.0" ? Pick< @@ -88,7 +89,5 @@ export type RequestGasAndPaymasterAndDataResponse< | "paymasterData" | "paymasterVerificationGasLimit" | "paymasterPostOpGasLimit" - > & { - maxTokenAmount: undefined | string; - } + > : never); diff --git a/account-kit/infra/src/client/smartAccountClient.ts b/account-kit/infra/src/client/smartAccountClient.ts index e965ada5b5..a023a1e16e 100644 --- a/account-kit/infra/src/client/smartAccountClient.ts +++ b/account-kit/infra/src/client/smartAccountClient.ts @@ -50,7 +50,7 @@ export type AlchemySmartAccountClientConfig< policyId?: string | string[]; policyToken?: { address: string; - approvalMode?: "NONE" | "PERMIT" | "INJECT_APPROVAL"; + approvalMode?: "NONE" | "PERMIT"; maxTokenAmount: bigint; erc20Name: string; version: string; diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index 3e508fc6fd..8e8b603c50 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -63,7 +63,7 @@ interface AlchemyGasAndPaymasterAndDataMiddlewareParams { export type PolicyToken = { address: string; maxTokenAmount: bigint; - approvalMode?: "NONE" | "PERMIT" | "INJECT_APPROVAL"; + approvalMode?: "NONE" | "PERMIT"; erc20Name: string; version: string; }; @@ -203,73 +203,99 @@ export function alchemyGasAndPaymasterAndDataMiddleware( }); let erc20Context = undefined; - if (policyToken !== undefined && policyToken.approvalMode === "PERMIT") { - // get a paymaster address - let paymasterAddress = "0x"; - const paymasterData = await ( - client as AlchemySmartAccountClient - ).request({ - method: "pm_getPaymasterStubData", - params: [ - userOp, - account.getEntryPoint().address, - toHex(client.chain.id), - { - policyId: Array.isArray(policyId) ? policyId[0] : policyId, - }, - ], - }); - paymasterAddress = paymasterData.paymaster - ? paymasterData.paymaster - : paymasterData.paymasterAndData - ? paymasterData.paymasterAndData.slice(0, 42) - : "0x"; - const typed_permit_data = { - types: { - Permit: [ - { - name: "owner", - type: "address", - }, - { - name: "spender", - type: "address", - }, - { - name: "value", - type: "uint256", - }, - { - name: "nonce", - type: "uint256", - }, - { - name: "deadline", - type: "uint256", - }, - ], - }, - primaryType: "Permit", - domain: { - name: policyToken.erc20Name, - version: policyToken.version, - chainId: client.chain.id, - verifyingContract: policyToken.address as Hex, - }, - message: { - owner: account.address as Hex, - spender: paymasterAddress as Hex, - value: policyToken.maxTokenAmount, - nonce: (await account.getAccountNonce()) + BigInt(1), - deadline: BigInt(0xffffffffffffffffffffffffffffffff), - }, - } as const; - + if (policyToken !== undefined) { erc20Context = { tokenAddress: policyToken.address, - permit: account.signTypedData(typed_permit_data), maxTokenAmount: policyToken.maxTokenAmount, }; + if (policyToken.approvalMode === "PERMIT") { + // get a paymaster address + let paymasterAddress = "0x"; + const paymasterData = await ( + client as AlchemySmartAccountClient + ).request({ + method: "pm_getPaymasterStubData", + params: [ + userOp, + account.getEntryPoint().address, + toHex(client.chain.id), + { + policyId: Array.isArray(policyId) ? policyId[0] : policyId, + }, + ], + }); + // todo: make a eth_estimateUserOperationGas to avoid client pass + // maxAmountToken + paymasterAddress = paymasterData.paymaster + ? paymasterData.paymaster + : paymasterData.paymasterAndData + ? paymasterData.paymasterAndData.slice(0, 42) + : "0x"; + const typed_permit_data = { + types: { + EIP712Domain: [ + { + name: "name", + type: "string", + }, + { + name: "version", + type: "string", + }, + { + name: "chainId", + type: "uint256", + }, + { + name: "verifyingContract", + type: "address", + }, + ], + Permit: [ + { + name: "owner", + type: "address", + }, + { + name: "spender", + type: "address", + }, + { + name: "value", + type: "uint256", + }, + { + name: "nonce", + type: "uint256", + }, + { + name: "deadline", + type: "uint256", + }, + ], + }, + primaryType: "Permit", + domain: { + name: policyToken.erc20Name, + version: policyToken.version, + chainId: BigInt(client.chain.id), + verifyingContract: policyToken.address as Hex, + }, + message: { + owner: account.address as Hex, + spender: paymasterAddress as Hex, + value: policyToken.maxTokenAmount, + nonce: (await account.getAccountNonce()) + BigInt(1), + deadline: BigInt(0xffffffffffffffffffffffffffffffff), + }, + } as const; + + erc20Context = { + tokenAddress: policyToken.address, + permit: await account.signTypedData(typed_permit_data), + maxTokenAmount: policyToken.maxTokenAmount, + }; + } } const result = await (client as AlchemySmartAccountClient).request({ From a526f99ff9b9bf6cf7b7eff185c47ad7853d7f4e Mon Sep 17 00:00:00 2001 From: "Pengfei(Andy) Zhang" Date: Mon, 28 Apr 2025 13:51:28 -0400 Subject: [PATCH 6/9] feat: use MaxUint256 if no maxToken provided --- .../infra/src/client/smartAccountClient.ts | 10 ++--- .../infra/src/middleware/gasManager.ts | 42 +++++++++++-------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/account-kit/infra/src/client/smartAccountClient.ts b/account-kit/infra/src/client/smartAccountClient.ts index a023a1e16e..b5f634fc8a 100644 --- a/account-kit/infra/src/client/smartAccountClient.ts +++ b/account-kit/infra/src/client/smartAccountClient.ts @@ -12,7 +12,7 @@ import { type SmartContractAccountWithSigner, type UserOperationContext, } from "@aa-sdk/core"; -import { type Chain } from "viem"; +import { type Address, type Chain } from "viem"; import { alchemy, convertHeadersToObject, @@ -49,11 +49,11 @@ export type AlchemySmartAccountClientConfig< useSimulation?: boolean; policyId?: string | string[]; policyToken?: { - address: string; + address: Address; approvalMode?: "NONE" | "PERMIT"; - maxTokenAmount: bigint; - erc20Name: string; - version: string; + maxTokenAmount?: bigint; + erc20Name?: string; + version?: string; }; } & Pick< SmartAccountClientConfig, diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index 8e8b603c50..e0583a9894 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -1,4 +1,5 @@ import type { + Address, ClientMiddlewareConfig, ClientMiddlewareFn, EntryPointVersion, @@ -61,13 +62,15 @@ interface AlchemyGasAndPaymasterAndDataMiddlewareParams { } export type PolicyToken = { - address: string; - maxTokenAmount: bigint; + address: Address; + maxTokenAmount?: bigint; approvalMode?: "NONE" | "PERMIT"; - erc20Name: string; - version: string; + erc20Name?: string; + version?: string; }; +const MaxUint256: bigint = (1n << 256n) - 1n; + /** * Paymaster middleware factory that uses Alchemy's Gas Manager for sponsoring * transactions. Uses Alchemy's custom `alchemy_requestGasAndPaymasterAndData` @@ -202,11 +205,21 @@ export function alchemyGasAndPaymasterAndDataMiddleware( : {}), }); - let erc20Context = undefined; + let erc20Context: + | { + tokenAddress: Address; + maxTokenAmount: bigint; + permit?: Hex; + } + | undefined = undefined; if (policyToken !== undefined) { + let maxAmountToken = policyToken.maxTokenAmount + ? policyToken.maxTokenAmount + : MaxUint256; + erc20Context = { tokenAddress: policyToken.address, - maxTokenAmount: policyToken.maxTokenAmount, + maxTokenAmount: maxAmountToken, }; if (policyToken.approvalMode === "PERMIT") { // get a paymaster address @@ -224,14 +237,13 @@ export function alchemyGasAndPaymasterAndDataMiddleware( }, ], }); - // todo: make a eth_estimateUserOperationGas to avoid client pass - // maxAmountToken + paymasterAddress = paymasterData.paymaster ? paymasterData.paymaster : paymasterData.paymasterAndData ? paymasterData.paymasterAndData.slice(0, 42) : "0x"; - const typed_permit_data = { + const typedPermitData = { types: { EIP712Domain: [ { @@ -276,25 +288,21 @@ export function alchemyGasAndPaymasterAndDataMiddleware( }, primaryType: "Permit", domain: { - name: policyToken.erc20Name, - version: policyToken.version, + name: policyToken.erc20Name ? policyToken.erc20Name : "", + version: policyToken.version ? policyToken.version : "", chainId: BigInt(client.chain.id), verifyingContract: policyToken.address as Hex, }, message: { owner: account.address as Hex, spender: paymasterAddress as Hex, - value: policyToken.maxTokenAmount, + value: maxAmountToken, nonce: (await account.getAccountNonce()) + BigInt(1), deadline: BigInt(0xffffffffffffffffffffffffffffffff), }, } as const; - erc20Context = { - tokenAddress: policyToken.address, - permit: await account.signTypedData(typed_permit_data), - maxTokenAmount: policyToken.maxTokenAmount, - }; + erc20Context.permit = await account.signTypedData(typedPermitData); } } From b7f4d54beb9779339614650249f8ea849e8a18c9 Mon Sep 17 00:00:00 2001 From: "Pengfei(Andy) Zhang" Date: Tue, 29 Apr 2025 12:29:36 -0400 Subject: [PATCH 7/9] feat: add erc20 support and tested --- account-kit/infra/src/actions/types.ts | 4 +- .../infra/src/middleware/gasManager.ts | 67 ++++++++++++++----- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/account-kit/infra/src/actions/types.ts b/account-kit/infra/src/actions/types.ts index be3d3e2ff6..a1c50e6b7c 100644 --- a/account-kit/infra/src/actions/types.ts +++ b/account-kit/infra/src/actions/types.ts @@ -58,9 +58,9 @@ export type RequestGasAndPaymasterAndDataRequest = [ policyId: string | string[]; entryPoint: Address; erc20Context?: { - tokenAddress: string; + tokenAddress: Address; permit?: Hex; - maxTokenAmount: BigInt; + maxTokenAmount?: BigInt; }; dummySignature: Hex; userOperation: UserOperationRequest; diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index e0583a9894..f26e9d30fb 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -21,7 +21,16 @@ import { noopMiddleware, resolveProperties, } from "@aa-sdk/core"; -import { fromHex, isHex, toHex, type Hex } from "viem"; +import { + fromHex, + isHex, + toHex, + type Hex, + encodeAbiParameters, + encodeFunctionData, + parseAbi, + maxUint256, +} from "viem"; import type { AlchemySmartAccountClient } from "../client/smartAccountClient.js"; import type { AlchemyTransport } from "../alchemyTransport.js"; import { alchemyFeeEstimator } from "./feeEstimator.js"; @@ -69,8 +78,6 @@ export type PolicyToken = { version?: string; }; -const MaxUint256: bigint = (1n << 256n) - 1n; - /** * Paymaster middleware factory that uses Alchemy's Gas Manager for sponsoring * transactions. Uses Alchemy's custom `alchemy_requestGasAndPaymasterAndData` @@ -98,7 +105,7 @@ const MaxUint256: bigint = (1n << 256n) - 1n; * @param {AlchemyGasAndPaymasterAndDataMiddlewareParams.transport} params.transport fallback transport to use for fee estimation when not using the paymaster * @param {AlchemyGasAndPaymasterAndDataMiddlewareParams.gasEstimatorOverride} params.gasEstimatorOverride custom gas estimator middleware * @param {AlchemyGasAndPaymasterAndDataMiddlewareParams.feeEstimatorOverride} params.feeEstimatorOverride custom fee estimator middleware - * @returns {Pick} partial client middleware configuration containing `dummyPaymasterAndData` and `paymasterAndData` + * @returns {Pick} partial client middleware configuration containing `dummyPaymasterAndData`, `feeEstimator`, `gasEstimator`, and `paymasterAndData` */ export function alchemyGasAndPaymasterAndDataMiddleware( params: AlchemyGasAndPaymasterAndDataMiddlewareParams @@ -208,22 +215,24 @@ export function alchemyGasAndPaymasterAndDataMiddleware( let erc20Context: | { tokenAddress: Address; - maxTokenAmount: bigint; + maxTokenAmount?: bigint; permit?: Hex; } | undefined = undefined; if (policyToken !== undefined) { let maxAmountToken = policyToken.maxTokenAmount ? policyToken.maxTokenAmount - : MaxUint256; + : maxUint256; erc20Context = { tokenAddress: policyToken.address, - maxTokenAmount: maxAmountToken, + ...(policyToken.maxTokenAmount + ? { maxTokenAmount: maxAmountToken } + : {}), }; if (policyToken.approvalMode === "PERMIT") { // get a paymaster address - let paymasterAddress = "0x"; + let paymasterAddress: Address | undefined = undefined; const paymasterData = await ( client as AlchemySmartAccountClient ).request({ @@ -241,8 +250,28 @@ export function alchemyGasAndPaymasterAndDataMiddleware( paymasterAddress = paymasterData.paymaster ? paymasterData.paymaster : paymasterData.paymasterAndData - ? paymasterData.paymasterAndData.slice(0, 42) - : "0x"; + ? (paymasterData.paymasterAndData.slice(0, 42) as Address) + : undefined; + + if (paymasterAddress === undefined) { + throw new Error("no paymaster contract address available"); + } + const deadline = maxUint256; + const eip712Abi = [ + "function nonces(address owner) external view returns (uint)", + ]; + const { data } = await client.call({ + to: policyToken.address, + data: encodeFunctionData({ + abi: parseAbi(eip712Abi), + functionName: "nonces", + args: [client.account?.address], + }), + }); + if (!data) { + throw new Error("No nonces returned from erc20 contract call"); + } + console.log(data); const typedPermitData = { types: { EIP712Domain: [ @@ -288,21 +317,25 @@ export function alchemyGasAndPaymasterAndDataMiddleware( }, primaryType: "Permit", domain: { - name: policyToken.erc20Name ? policyToken.erc20Name : "", - version: policyToken.version ? policyToken.version : "", + name: policyToken.erc20Name ?? "", + version: policyToken.version ?? "", chainId: BigInt(client.chain.id), - verifyingContract: policyToken.address as Hex, + verifyingContract: policyToken.address, }, message: { owner: account.address as Hex, spender: paymasterAddress as Hex, value: maxAmountToken, - nonce: (await account.getAccountNonce()) + BigInt(1), - deadline: BigInt(0xffffffffffffffffffffffffffffffff), + nonce: BigInt(data), + deadline, }, } as const; - erc20Context.permit = await account.signTypedData(typedPermitData); + const signedPermit = await account.signTypedData(typedPermitData); + erc20Context.permit = encodeAbiParameters( + [{ type: "uint256" }, { type: "uint256" }, { type: "bytes" }], + [maxAmountToken, deadline, signedPermit] + ); } } @@ -317,7 +350,7 @@ export function alchemyGasAndPaymasterAndDataMiddleware( overrides, ...(erc20Context ? { - erc20Context: erc20Context, + erc20Context, } : {}), }, From 05f79575e8a9f015986a1d4544023a8d7972bc55 Mon Sep 17 00:00:00 2001 From: "Pengfei(Andy) Zhang" Date: Tue, 29 Apr 2025 14:51:38 -0400 Subject: [PATCH 8/9] refactor: move abi def and typed Permit type to other file --- account-kit/infra/src/gas-manager.ts | 37 ++++++++- .../infra/src/middleware/gasManager.ts | 83 ++++--------------- 2 files changed, 54 insertions(+), 66 deletions(-) diff --git a/account-kit/infra/src/gas-manager.ts b/account-kit/infra/src/gas-manager.ts index 9041f15612..e4173c900b 100644 --- a/account-kit/infra/src/gas-manager.ts +++ b/account-kit/infra/src/gas-manager.ts @@ -1,4 +1,4 @@ -import type { Address, Chain } from "viem"; +import type { Address, Chain, Hex } from "viem"; import { arbitrum, arbitrumSepolia, @@ -66,3 +66,38 @@ export const getAlchemyPaymasterAddress = (chain: Chain): Address => { throw new Error(`Unsupported chain: ${chain}`); } }; + +export const PermitTypes = { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" }, + ], + Permit: [ + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "value", type: "uint256" }, + { name: "nonce", type: "uint256" }, + { name: "deadline", type: "uint256" }, + ], +} as const; + +export const EIP712NoncesAbi = [ + "function nonces(address owner) external view returns (uint)", +] as const; + +export type PermitMessage = { + owner: Hex; + spender: Hex; + value: bigint; + nonce: bigint; + deadline: bigint; +}; + +export type PermitDomain = { + name: string; + version: string; + chainId: bigint; + verifyingContract: Hex; +}; diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index f26e9d30fb..30970e383f 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -30,10 +30,14 @@ import { encodeFunctionData, parseAbi, maxUint256, + sliceHex, } from "viem"; import type { AlchemySmartAccountClient } from "../client/smartAccountClient.js"; import type { AlchemyTransport } from "../alchemyTransport.js"; import { alchemyFeeEstimator } from "./feeEstimator.js"; +import type { RequestGasAndPaymasterAndDataRequest } from "../actions/types.js"; +import { PermitTypes, EIP712NoncesAbi } from "../gas-manager.js"; +import type { PermitMessage, PermitDomain } from "../gas-manager.js"; /** * Paymaster middleware factory that uses Alchemy's Gas Manager for sponsoring @@ -212,22 +216,15 @@ export function alchemyGasAndPaymasterAndDataMiddleware( : {}), }); - let erc20Context: - | { - tokenAddress: Address; - maxTokenAmount?: bigint; - permit?: Hex; - } - | undefined = undefined; + let erc20Context: RequestGasAndPaymasterAndDataRequest[0]["erc20Context"] = + undefined; if (policyToken !== undefined) { - let maxAmountToken = policyToken.maxTokenAmount - ? policyToken.maxTokenAmount - : maxUint256; + const maxAmountToken = policyToken.maxTokenAmount || maxUint256; erc20Context = { tokenAddress: policyToken.address, ...(policyToken.maxTokenAmount - ? { maxTokenAmount: maxAmountToken } + ? { maxTokenAmount: policyToken.maxTokenAmount } : {}), }; if (policyToken.approvalMode === "PERMIT") { @@ -250,22 +247,19 @@ export function alchemyGasAndPaymasterAndDataMiddleware( paymasterAddress = paymasterData.paymaster ? paymasterData.paymaster : paymasterData.paymasterAndData - ? (paymasterData.paymasterAndData.slice(0, 42) as Address) + ? sliceHex(paymasterData.paymasterAndData, 0, 20) : undefined; - if (paymasterAddress === undefined) { + if (paymasterAddress === undefined || paymasterAddress === "0x") { throw new Error("no paymaster contract address available"); } const deadline = maxUint256; - const eip712Abi = [ - "function nonces(address owner) external view returns (uint)", - ]; const { data } = await client.call({ to: policyToken.address, data: encodeFunctionData({ - abi: parseAbi(eip712Abi), + abi: parseAbi(EIP712NoncesAbi), functionName: "nonces", - args: [client.account?.address], + args: [account.address], }), }); if (!data) { @@ -273,62 +267,21 @@ export function alchemyGasAndPaymasterAndDataMiddleware( } console.log(data); const typedPermitData = { - types: { - EIP712Domain: [ - { - name: "name", - type: "string", - }, - { - name: "version", - type: "string", - }, - { - name: "chainId", - type: "uint256", - }, - { - name: "verifyingContract", - type: "address", - }, - ], - Permit: [ - { - name: "owner", - type: "address", - }, - { - name: "spender", - type: "address", - }, - { - name: "value", - type: "uint256", - }, - { - name: "nonce", - type: "uint256", - }, - { - name: "deadline", - type: "uint256", - }, - ], - }, - primaryType: "Permit", + types: PermitTypes, + primaryType: "Permit" as const, domain: { name: policyToken.erc20Name ?? "", version: policyToken.version ?? "", chainId: BigInt(client.chain.id), verifyingContract: policyToken.address, - }, + } as PermitDomain, message: { - owner: account.address as Hex, - spender: paymasterAddress as Hex, + owner: account.address, + spender: paymasterAddress, value: maxAmountToken, nonce: BigInt(data), deadline, - }, + } as PermitMessage, } as const; const signedPermit = await account.signTypedData(typedPermitData); From 918b990fd910fbd0c4267b6f0ef7d8441cdd3454 Mon Sep 17 00:00:00 2001 From: "Pengfei(Andy) Zhang" Date: Wed, 30 Apr 2025 09:30:06 -0400 Subject: [PATCH 9/9] refactor: use satisfies instead of as --- account-kit/infra/src/middleware/gasManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/account-kit/infra/src/middleware/gasManager.ts b/account-kit/infra/src/middleware/gasManager.ts index 30970e383f..b992178a0d 100644 --- a/account-kit/infra/src/middleware/gasManager.ts +++ b/account-kit/infra/src/middleware/gasManager.ts @@ -265,7 +265,7 @@ export function alchemyGasAndPaymasterAndDataMiddleware( if (!data) { throw new Error("No nonces returned from erc20 contract call"); } - console.log(data); + const typedPermitData = { types: PermitTypes, primaryType: "Permit" as const, @@ -274,14 +274,14 @@ export function alchemyGasAndPaymasterAndDataMiddleware( version: policyToken.version ?? "", chainId: BigInt(client.chain.id), verifyingContract: policyToken.address, - } as PermitDomain, + } satisfies PermitDomain, message: { owner: account.address, spender: paymasterAddress, value: maxAmountToken, nonce: BigInt(data), deadline, - } as PermitMessage, + } satisfies PermitMessage, } as const; const signedPermit = await account.signTypedData(typedPermitData);