diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index 6bdc3eb7..328f1c91 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -1,6 +1,7 @@ name: Run checks on Pull Requests on: pull_request: + workflow_dispatch: jobs: enforce_title: @@ -25,19 +26,22 @@ jobs: name: Lint and Build and Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - - name: Setup Node.js + - name: Setup Node.js 18.x uses: actions/setup-node@v1 with: node-version: "18.13" cache: "yarn" - - name: Install dependencies + - name: Install SDK dependencies run: yarn install --frozen-lockfile + - name: Lint SDK + run: yarn lint:check + - name: Build SDK run: yarn build @@ -49,11 +53,5 @@ jobs: - name: E2E tests env: - API_URL: ${{ secrets.API_URL }} API_KEY: ${{ secrets.API_KEY }} - TEAM_ID: ${{ secrets.TEAM_ID }} - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} run: yarn --cwd e2e-tests test:e2e - - # - name: Unit Test - # run: yarn test diff --git a/packages/accounts/src/kernel-zerodev/__tests__/kernel-account.test.ts b/packages/accounts/src/kernel-zerodev/__tests__/kernel-account.test.ts index b68ada7c..226e91a2 100644 --- a/packages/accounts/src/kernel-zerodev/__tests__/kernel-account.test.ts +++ b/packages/accounts/src/kernel-zerodev/__tests__/kernel-account.test.ts @@ -4,12 +4,15 @@ import { parseAbiParameters, encodeFunctionData, type Hash, + createPublicClient, + http, } from "viem"; import { polygonMumbai } from "viem/chains"; import { generatePrivateKey } from "viem/accounts"; import { LocalAccountSigner } from "@alchemy/aa-core"; import { TEST_ERC20Abi } from "../abis/Test_ERC20Abi.js"; import { ECDSAProvider } from "../validator-provider/index.js"; +import { CHAIN_ID_TO_NODE } from "../constants.js"; export const config = { privateKey: (process.env.PRIVATE_KEY as Hex) ?? generatePrivateKey(), @@ -36,6 +39,10 @@ describe("Kernel Account Tests", () => { "0x022430a80f723d8789f0d4fb346bdd013b546e4b96fcacf8aceca2b1a65a19dc"; const mockOwner = LocalAccountSigner.privateKeyToAccountSigner(dummyPrivateKey); + const client = createPublicClient({ + chain: polygonMumbai, + transport: http(CHAIN_ID_TO_NODE[polygonMumbai.id]), + }); it( "getAddress returns valid counterfactual address", @@ -132,7 +139,7 @@ describe("Kernel Account Tests", () => { const ownerSignedMessage = "0xdad9efc9364e94756f6b16380b7d60c24a14526946ddaa52ef181e7ad065eaa146c9125b7ee62258caad708f57344cfc80d74bd69c0c1e95d75e7c7645c71e401b"; const factoryCode = - "0x296601cd000000000000000000000000f048ad83cb2dfd6037a43902a2a5be04e53cd2eb0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000084d1f57894000000000000000000000000d9ab5096a832b9ce79914329daee236f8eea039000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000014abcfC3DB1e0f5023F5a4f40c03D149f316E6A5cc00000000000000000000000000000000000000000000000000000000000000000000000000000000"; + "0x296601cd0000000000000000000000000da6a956b9488ed4dd761e59f52fdc6c8068e6b50000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000084d1f57894000000000000000000000000d9ab5096a832b9ce79914329daee236f8eea039000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000014abcfC3DB1e0f5023F5a4f40c03D149f316E6A5cc00000000000000000000000000000000000000000000000000000000000000000000000000000000"; const signature = encodeAbiParameters(parseAbiParameters("address, bytes, bytes"), [ config.accountFactoryAddress, @@ -172,37 +179,41 @@ describe("Kernel Account Tests", () => { { timeout: 100000 } ); - it("signMessage should correctly sign the message", async () => { - const messageToBeSigned: Hex = - "0xa70d0af2ebb03a44dcd0714a8724f622e3ab876d0aa312f0ee04823285d6fb1b"; + it( + "signMessage should correctly sign the message", + async () => { + const messageToBeSigned: Hex = + "0xa70d0af2ebb03a44dcd0714a8724f622e3ab876d0aa312f0ee04823285d6fb1b"; - let ecdsaProvider = await ECDSAProvider.init({ - projectId: config.projectId, - owner: mockOwner, - }); + let ecdsaProvider = await ECDSAProvider.init({ + projectId: config.projectId, + owner: mockOwner, + }); - const signer = ecdsaProvider.getAccount(); + const signer = ecdsaProvider.getAccount(); - expect(await signer.signMessage(messageToBeSigned)).toBe( - "0x002e1c0f007a5d2723b28da3165ee45c6fc49bf1fdf3cfeaef66de31dd90248647781142beadcb8524bd5a9be0da455881bfa003e3334fffd2ee6d648a78e06d1c" - ); + expect(await signer.signMessage(messageToBeSigned)).toBe( + "0x002e1c0f007a5d2723b28da3165ee45c6fc49bf1fdf3cfeaef66de31dd90248647781142beadcb8524bd5a9be0da455881bfa003e3334fffd2ee6d648a78e06d1c" + ); - ecdsaProvider = await ECDSAProvider.init({ - projectId: config.projectId, - owner: mockOwner, - opts: { - accountConfig: { - index: 1000n, + ecdsaProvider = await ECDSAProvider.init({ + projectId: config.projectId, + owner: mockOwner, + opts: { + accountConfig: { + index: 1000n, + }, }, - }, - }); + }); - const signer2 = ecdsaProvider.getAccount(); + const signer2 = ecdsaProvider.getAccount(); - expect(await signer2.signMessage(messageToBeSigned)).toBe( - "0x002e1c0f007a5d2723b28da3165ee45c6fc49bf1fdf3cfeaef66de31dd90248647781142beadcb8524bd5a9be0da455881bfa003e3334fffd2ee6d648a78e06d1c" - ); - }); + expect(await signer2.signMessage(messageToBeSigned)).toBe( + "0x002e1c0f007a5d2723b28da3165ee45c6fc49bf1fdf3cfeaef66de31dd90248647781142beadcb8524bd5a9be0da455881bfa003e3334fffd2ee6d648a78e06d1c" + ); + }, + { timeout: 100000 } + ); // NOTE - this test case will fail if the gas fee is sponsored it( @@ -289,9 +300,20 @@ describe("Kernel Account Tests", () => { //to fix bug in old versions await ecdsaProvider.getAccount().getInitCode(); + const mintData = encodeFunctionData({ + abi: TEST_ERC20Abi, + args: [await ecdsaProvider.getAddress(), 700000000000000000n], + functionName: "mint", + }); + const balanceBefore = await client.readContract({ + address: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B", + abi: TEST_ERC20Abi, + functionName: "balanceOf", + args: [await ecdsaProvider.getAddress()], + }); const result = ecdsaProvider.sendUserOperation({ - target: "0xA02CDdFa44B8C01b4257F54ac1c43F75801E8175", - data: "0x", + target: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B", + data: mintData, value: 0n, }); await expect(result).resolves.not.toThrowError(); @@ -300,6 +322,13 @@ describe("Kernel Account Tests", () => { await result ).hash as Hash ); + const balanceAfter = await client.readContract({ + address: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B", + abi: TEST_ERC20Abi, + functionName: "balanceOf", + args: [await ecdsaProvider.getAddress()], + }); + expect(balanceAfter).to.eq(balanceBefore + 700000000000000000n); }, { timeout: 100000 } ); @@ -325,9 +354,13 @@ describe("Kernel Account Tests", () => { }, }, }); - //to fix bug in old versions - await ecdsaProvider.getAccount().getInitCode(); + const balanceBefore = await client.readContract({ + address: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B", + abi: TEST_ERC20Abi, + functionName: "balanceOf", + args: [await ecdsaProvider.getAddress()], + }); const mintData = encodeFunctionData({ abi: TEST_ERC20Abi, args: [await ecdsaProvider.getAddress(), 700000000000000000n], @@ -346,6 +379,15 @@ describe("Kernel Account Tests", () => { await result ).hash as Hash ); + const balanceAfter = await client.readContract({ + address: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B", + abi: TEST_ERC20Abi, + functionName: "balanceOf", + args: [await ecdsaProvider.getAddress()], + }); + expect(balanceAfter.toString(16)).to.not.eq( + (balanceBefore + 700000000000000000n).toString(16) + ); }, { timeout: 100000 } ); @@ -385,6 +427,12 @@ describe("Kernel Account Tests", () => { args: [await owner.getAddress(), 133700000000n], functionName: "transfer", }); + const balanceBefore = await client.readContract({ + address: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B", + abi: TEST_ERC20Abi, + functionName: "balanceOf", + args: [await ecdsaProvider.getAddress()], + }); const result = ecdsaProvider.sendUserOperation([ { target: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B", @@ -398,6 +446,15 @@ describe("Kernel Account Tests", () => { }, ]); await expect(result).resolves.not.toThrowError(); + const balanceAfter = await client.readContract({ + address: "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B", + abi: TEST_ERC20Abi, + functionName: "balanceOf", + args: [await ecdsaProvider.getAddress()], + }); + expect(Number(balanceAfter.toString())).to.lt( + Number((balanceBefore + 700000000000000000n - 133700000000n).toString()) + ); }, { timeout: 100000 } ); diff --git a/packages/accounts/src/kernel-zerodev/__tests__/kernel-erc165-session-key-provider.test.ts b/packages/accounts/src/kernel-zerodev/__tests__/kernel-erc165-session-key-provider.test.ts index d512d4d1..28b67c0a 100644 --- a/packages/accounts/src/kernel-zerodev/__tests__/kernel-erc165-session-key-provider.test.ts +++ b/packages/accounts/src/kernel-zerodev/__tests__/kernel-erc165-session-key-provider.test.ts @@ -11,7 +11,7 @@ import { polygonMumbai } from "viem/chains"; import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; import { config } from "./kernel-account.test.js"; import { ECDSAProvider } from "../validator-provider/index.js"; -import { TOKEN_ACTION } from "../constants.js"; +import { CHAIN_ID_TO_NODE, TOKEN_ACTION } from "../constants.js"; import { ValidatorMode } from "../validator/base.js"; import { Test_ERC721Abi } from "../abis/Test_ERC721Abi.js"; import { ERC165SessionKeyProvider } from "../validator-provider/erc165-session-key-provider.js"; @@ -34,12 +34,12 @@ describe("Kernel ERC165SessionKey Provider Test", async () => { const client = createPublicClient({ chain: polygonMumbai, - transport: http("https://rpc.ankr.com/polygon_mumbai"), + transport: http(CHAIN_ID_TO_NODE[polygonMumbai.id]), }); const walletClient = createWalletClient({ account: privateKeyToAccount(config.privateKey), chain: polygonMumbai, - transport: http("https://rpc.ankr.com/polygon_mumbai"), + transport: http(CHAIN_ID_TO_NODE[polygonMumbai.id]), }); const selector = getFunctionSelector( "transferERC721Action(address, uint256, address)" diff --git a/packages/accounts/src/kernel-zerodev/__tests__/kernel-recovery-provider.test.ts b/packages/accounts/src/kernel-zerodev/__tests__/kernel-recovery-provider.test.ts index fc3c5360..e369cfb9 100644 --- a/packages/accounts/src/kernel-zerodev/__tests__/kernel-recovery-provider.test.ts +++ b/packages/accounts/src/kernel-zerodev/__tests__/kernel-recovery-provider.test.ts @@ -59,7 +59,6 @@ describe("Kernel Recovery Provider Test", async () => { const client = createPublicClient({ chain: polygonMumbai, - // transport: http("https://rpc.ankr.com/polygon_mumbai"), transport: http(CHAIN_ID_TO_NODE[polygonMumbai.id]), }); let accountAddress: Hex = "0x"; diff --git a/packages/accounts/src/kernel-zerodev/__tests__/kernel-validator-provider.test.ts b/packages/accounts/src/kernel-zerodev/__tests__/kernel-validator-provider.test.ts index 35f5af46..b138ddec 100644 --- a/packages/accounts/src/kernel-zerodev/__tests__/kernel-validator-provider.test.ts +++ b/packages/accounts/src/kernel-zerodev/__tests__/kernel-validator-provider.test.ts @@ -5,6 +5,7 @@ import { config } from "./kernel-account.test.js"; import { LocalAccountSigner } from "@alchemy/aa-core"; import { generatePrivateKey } from "viem/accounts"; import { ECDSAProvider } from "../validator-provider/index.js"; +import { CHAIN_ID_TO_NODE } from "../constants.js"; // [TODO] - Organize the test code properly describe("Kernel Validator Provider Test", async () => { @@ -15,7 +16,7 @@ describe("Kernel Validator Provider Test", async () => { const client = createPublicClient({ chain: polygonMumbai, - transport: http("https://rpc.ankr.com/polygon_mumbai"), + transport: http(CHAIN_ID_TO_NODE[polygonMumbai.id]), }); let accountAddress: Hex = "0x"; diff --git a/packages/accounts/src/kernel-zerodev/abis/KernelAccountAbi.ts b/packages/accounts/src/kernel-zerodev/abis/KernelAccountAbi.ts index 84d6a474..2f0711a8 100644 --- a/packages/accounts/src/kernel-zerodev/abis/KernelAccountAbi.ts +++ b/packages/accounts/src/kernel-zerodev/abis/KernelAccountAbi.ts @@ -74,6 +74,25 @@ export const KernelAccountAbi = [ name: "ExecutionChanged", type: "event", }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "Received", + type: "event", + }, { anonymous: false, inputs: [ @@ -179,7 +198,7 @@ export const KernelAccountAbi = [ }, { internalType: "enum Operation", - name: "operation", + name: "", type: "uint8", }, ], @@ -188,6 +207,36 @@ export const KernelAccountAbi = [ stateMutability: "payable", type: "function", }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + internalType: "struct Call[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "executeBatch", + outputs: [], + stateMutability: "payable", + type: "function", + }, { inputs: [], name: "getDefaultValidator", @@ -503,12 +552,12 @@ export const KernelAccountAbi = [ type: "address", }, { - internalType: "uint48", + internalType: "ValidUntil", name: "_validUntil", type: "uint48", }, { - internalType: "uint48", + internalType: "ValidAfter", name: "_validAfter", type: "uint48", }, @@ -597,7 +646,7 @@ export const KernelAccountAbi = [ }, ], internalType: "struct UserOperation", - name: "userOp", + name: "_userOp", type: "tuple", }, { diff --git a/packages/accounts/src/kernel-zerodev/account.ts b/packages/accounts/src/kernel-zerodev/account.ts index 86c8f5c3..688d7228 100644 --- a/packages/accounts/src/kernel-zerodev/account.ts +++ b/packages/accounts/src/kernel-zerodev/account.ts @@ -180,16 +180,28 @@ export class KernelSmartContractAccount< async encodeBatchExecute( _txs: BatchUserOperationCallData ): Promise<`0x${string}`> { - const multiSendCalldata = encodeFunctionData({ - abi: MultiSendAbi, - functionName: "multiSend", - args: [encodeMultiSend(_txs)], - }); - return await this.encodeExecuteDelegate( - MULTISEND_ADDR, - BigInt(0), - multiSendCalldata - ); + const kernelImplAddr = await this.getKernelImplementationAddess(); + const initCode = await this.getInitCode(); + // [TODO] - Remove this check once the kernel implementation is updated + // Also, remove the check for the hardcoded kernel implementation address + const shouldUseMultiSend = + kernelImplAddr?.toLowerCase() !== KERNEL_IMPL_ADDRESS.toLowerCase() && + kernelImplAddr?.toLowerCase() !== + "0x8dD4DBB54d8A8Cf0DE6F9CCC4609470A30EfF18C".toLowerCase() && + initCode === "0x"; + if (shouldUseMultiSend) { + const multiSendCalldata = encodeFunctionData({ + abi: MultiSendAbi, + functionName: "multiSend", + args: [encodeMultiSend(_txs)], + }); + return await this.encodeExecuteDelegate( + MULTISEND_ADDR, + BigInt(0), + multiSendCalldata + ); + } + return await this.encodeExecuteBatchAction(_txs); } async encodeExecuteDelegate( @@ -221,6 +233,26 @@ export class KernelSmartContractAccount< }); } + async encodeUgradeTo() { + return encodeFunctionData({ + abi: KernelAccountAbi, + functionName: "upgradeTo", + args: [KERNEL_IMPL_ADDRESS], + }); + } + + async getKernelImplementationAddess(): Promise { + try { + const strgAddr = await this.rpcProvider.getStorageAt({ + address: await this.getAddress(), + slot: "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + }); + return strgAddr ? (("0x" + strgAddr.slice(26)) as Hex) : strgAddr; + } catch (error) { + return; + } + } + async signMessageWith6492(msg: string | Uint8Array): Promise { try { if (!this.validator) { @@ -302,6 +334,21 @@ export class KernelSmartContractAccount< args: [target, value, data, code], }); } + + protected encodeExecuteBatchAction(_txs: BatchUserOperationCallData): Hex { + return encodeFunctionData({ + abi: KernelAccountAbi, + functionName: "executeBatch", + args: [ + _txs.map((tx) => ({ + to: tx.target, + value: tx.value ?? 0n, + data: tx.data, + })), + ], + }); + } + protected async getAccountInitCode(): Promise { return concatHex([this.factoryAddress, await this.getFactoryInitCode()]); } diff --git a/packages/accounts/src/kernel-zerodev/constants.ts b/packages/accounts/src/kernel-zerodev/constants.ts index a3286aee..cf2eab3b 100644 --- a/packages/accounts/src/kernel-zerodev/constants.ts +++ b/packages/accounts/src/kernel-zerodev/constants.ts @@ -5,7 +5,8 @@ import { polygon } from "viem/chains"; export const DEFAULT_SEND_TX_MAX_RETRIES = 3; export const DEFAULT_SEND_TX_RETRY_INTERVAL_MS = 60000; // 1 minutes export const BUNDLER_URL = "https://v0-6-meta-bundler.onrender.com"; -export const PAYMASTER_URL = "https://v0-6-paymaster.onrender.com"; +export const PAYMASTER_URL = + "https://meta-aa-provider.onrender.com/api/v1/paymaster"; export const ENTRYPOINT_ADDRESS: Hex = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; export const MULTISEND_ADDR: Hex = "0x8ae01fcf7c655655ff2c6ef907b8b4718ab4e17c"; @@ -15,7 +16,7 @@ export const ECDSA_VALIDATOR_ADDRESS = "0xd9AB5096a832b9ce79914329DAEE236f8Eea0390"; export const KERNEL_FACTORY_ADDRESS = "0x5de4839a76cf55d0c90e2061ef4386d962E15ae3"; -export const KERNEL_IMPL_ADDRESS = "0xf048AD83CB2dfd6037A43902a2A5Be04e53cd2Eb"; +export const KERNEL_IMPL_ADDRESS = "0x0DA6a956B9488eD4dd761E59f52FDc6c8068E6B5"; export const KILL_SWITCH_VALIDATOR_ADDRESS = "0x7393A7dA58CCfFb78f52adb09705BE6E20F704BC"; export const KILL_SWITCH_ACTION = "0x3f38e479304c7F18F988269a1bDa7d646bd48243"; diff --git a/packages/accounts/src/kernel-zerodev/ethers-provider/account-signer.ts b/packages/accounts/src/kernel-zerodev/ethers-provider/account-signer.ts index dbb7dfd0..ca8b1aac 100644 --- a/packages/accounts/src/kernel-zerodev/ethers-provider/account-signer.ts +++ b/packages/accounts/src/kernel-zerodev/ethers-provider/account-signer.ts @@ -56,7 +56,7 @@ export class ZeroDevAccountSigner ); } - getAddress(): Promise { + getAddress(): Promise { if (!this.account) { throw new Error( "connect the signer to a provider that has a connected account" diff --git a/packages/accounts/src/kernel-zerodev/middleware/gas-estimator.ts b/packages/accounts/src/kernel-zerodev/middleware/gas-estimator.ts index 9173ce01..936e7ded 100644 --- a/packages/accounts/src/kernel-zerodev/middleware/gas-estimator.ts +++ b/packages/accounts/src/kernel-zerodev/middleware/gas-estimator.ts @@ -83,8 +83,7 @@ export const withZeroDevGasEstimator = ( request.preVerificationGas = (BigInt(preVerificationGas) * 12n) / 10n ?? request.preVerificationGas; request.verificationGasLimit = - (BigInt(verificationGasLimit) * 12n) / 10n ?? - request.verificationGasLimit; + BigInt(verificationGasLimit) ?? request.verificationGasLimit; request.callGasLimit = (BigInt(callGasLimit) * 12n) / 10n ?? request.callGasLimit; diff --git a/packages/accounts/src/kernel-zerodev/middleware/paymaster.ts b/packages/accounts/src/kernel-zerodev/middleware/paymaster.ts index 7ef25ef8..298cf3d7 100644 --- a/packages/accounts/src/kernel-zerodev/middleware/paymaster.ts +++ b/packages/accounts/src/kernel-zerodev/middleware/paymaster.ts @@ -2,6 +2,7 @@ import { type ConnectedSmartAccountProvider } from "@alchemy/aa-core"; import type { ZeroDevProvider } from "../provider.js"; import { Paymasters } from "../paymaster/index.js"; import type { PaymasterConfig, PaymasterPolicy } from "../paymaster/types.js"; +import { AxiosError } from "axios"; export const withZeroDevPaymasterAndData = ( provider: Provider, @@ -30,22 +31,24 @@ export const zeroDevPaymasterAndDataMiddleware = < return struct; }, paymasterDataMiddleware: async (struct) => { - const preVerificationGas = BigInt("100000"); - const verificationGasLimit = BigInt("1000000"); - const callGasLimit = BigInt("55000"); const paymaster = new Paymasters[paymasterConfig.policy]( provider, paymasterConfig ); - const paymasterResp = await paymaster.getPaymasterResponse( - { - ...struct, - preVerificationGas, - verificationGasLimit, - callGasLimit, - }, - paymasterConfig.paymasterProvider - ); + let paymasterResp; + try { + paymasterResp = await paymaster.getPaymasterResponse( + struct, + paymasterConfig.paymasterProvider + ); + } catch (error: any) { + console.error(error); + if (paymasterConfig.onlySendSponsoredTransaction) { + if (error instanceof AxiosError) + throw Error(error.response?.data.message); + else throw error; + } + } if ( paymasterConfig.onlySendSponsoredTransaction && (!paymasterResp || diff --git a/packages/accounts/src/kernel-zerodev/paymaster/base.ts b/packages/accounts/src/kernel-zerodev/paymaster/base.ts index ead577c5..0477b107 100644 --- a/packages/accounts/src/kernel-zerodev/paymaster/base.ts +++ b/packages/accounts/src/kernel-zerodev/paymaster/base.ts @@ -36,45 +36,51 @@ export abstract class Paymaster { paymasterProvider?: PaymasterAndBundlerProviders; shouldOverrideFee?: boolean; }): Promise { - try { - const hexifiedUserOp = deepHexlify(await resolveProperties(userOp)); - let resolvedERC20UserOp; - let hexifiedERC20UserOp: any; - if (erc20UserOp) { - resolvedERC20UserOp = await resolveProperties(erc20UserOp); + const hexifiedUserOp = deepHexlify(await resolveProperties(userOp)); + let resolvedERC20UserOp; + let hexifiedERC20UserOp: any; + if (erc20UserOp) { + resolvedERC20UserOp = await resolveProperties(erc20UserOp); - hexifiedERC20UserOp = hexifyUserOp(resolvedERC20UserOp); - } - const chainId = await getChainId(this.provider.getProjectId()); - if (!chainId) throw new Error("ChainId not found"); - let requestBodyParams = Object.fromEntries( - Object.entries({ - projectId: this.provider.getProjectId(), - chainId, - userOp: hexifiedUserOp, - entryPointAddress: ENTRYPOINT_ADDRESS, - callData: callData instanceof Promise ? await callData : callData, - tokenAddress: gasTokenAddress, - erc20UserOp: hexifiedERC20UserOp, - erc20CallData: - erc20CallData instanceof Promise - ? await erc20CallData - : erc20CallData, - paymasterProvider, - shouldOverrideFee, - }).filter(([_, value]) => value !== undefined) - ); - const { data: paymasterResp } = await axios.post( - `${PAYMASTER_URL}/sign`, - { - ...requestBodyParams, - }, - { headers: { "Content-Type": "application/json" } } - ); - return paymasterResp; - } catch (error) { - console.log(error); - return undefined; + hexifiedERC20UserOp = hexifyUserOp(resolvedERC20UserOp); } + const chainId = await getChainId(this.provider.getProjectId()); + if (!chainId) throw new Error("ChainId not found"); + let requestBodyParams = Object.fromEntries( + Object.entries({ + projectId: this.provider.getProjectId(), + chainId, + userOp: hexifiedUserOp, + entryPointAddress: ENTRYPOINT_ADDRESS, + callData: callData instanceof Promise ? await callData : callData, + gasTokenData: + gasTokenAddress && hexifiedERC20UserOp && erc20CallData + ? { + tokenAddress: gasTokenAddress, + erc20UserOp: hexifiedERC20UserOp, + erc20CallData: + erc20CallData instanceof Promise + ? await erc20CallData + : erc20CallData, + } + : undefined, + tokenAddress: gasTokenAddress, + erc20UserOp: hexifiedERC20UserOp, + erc20CallData: + erc20CallData instanceof Promise + ? await erc20CallData + : erc20CallData, + paymasterProvider, + shouldOverrideFee, + }).filter(([_, value]) => value !== undefined) + ); + const { data: paymasterResp } = await axios.post( + `${PAYMASTER_URL}/getPaymasterAndData`, + { + ...requestBodyParams, + }, + { headers: { "Content-Type": "application/json" } } + ); + return paymasterResp; } } diff --git a/packages/accounts/src/kernel-zerodev/paymaster/token-paymaster.ts b/packages/accounts/src/kernel-zerodev/paymaster/token-paymaster.ts index 579eebc0..ca06832d 100644 --- a/packages/accounts/src/kernel-zerodev/paymaster/token-paymaster.ts +++ b/packages/accounts/src/kernel-zerodev/paymaster/token-paymaster.ts @@ -3,6 +3,7 @@ import { type BytesLike, type UserOperationCallData, type UserOperationStruct, + type BatchUserOperationCallData, } from "@alchemy/aa-core"; import axios from "axios"; import { @@ -14,12 +15,11 @@ import { } from "viem"; import { ERC20Abi } from "../abis/ERC20Abi.js"; import { KernelAccountAbi } from "../abis/KernelAccountAbi.js"; -import { MultiSendAbi } from "../abis/MultiSendAbi.js"; import { PAYMASTER_URL, ENTRYPOINT_ADDRESS, - MULTISEND_ADDR, ERC20_APPROVAL_AMOUNT, + MULTISEND_ADDR, } from "../constants.js"; import { AccountNotConnected, @@ -36,7 +36,13 @@ import { type PaymasterConfig, } from "./types.js"; import { getChainId } from "../api/index.js"; +import { MultiSendAbi } from "../abis/MultiSendAbi.js"; +export function isBatchUserOperationCallData( + data: any +): data is BatchUserOperationCallData { + return data && Array.isArray(data); +} export class TokenPaymaster extends Paymaster { constructor( provider: ZeroDevProvider, @@ -68,7 +74,12 @@ export class TokenPaymaster extends Paymaster { async decodeMainCallFromCallData( kernelAddress: PromiseOrValue, callData: PromiseOrValue - ): Promise { + ): Promise< + | UserOperationCallData + | BatchUserOperationCallData + | UserOperationCallDataWithDelegate + | undefined + > { let data: Hex = "0x"; if (callData instanceof Promise) { const _data = await callData; @@ -113,6 +124,13 @@ export class TokenPaymaster extends Paymaster { }; } return mainCall; + } else if (functionName === "executeBatch") { + const [txs] = args; + return txs.map((tx) => ({ + target: tx.to, + value: tx.value ?? 0n, + data: tx.data, + })); } } catch (error) { return { @@ -127,9 +145,11 @@ export class TokenPaymaster extends Paymaster { return; } - async getERC20UserOp( + async getERC20UserOp< + T extends UserOperationCallData | BatchUserOperationCallData + >( struct: UserOperationStruct, - mainCall: UserOperationCallDataWithDelegate, + mainCall: T, gasTokenAddress: Hex, paymasterAddress: Hex ): Promise { @@ -146,10 +166,17 @@ export class TokenPaymaster extends Paymaster { if (!this.provider.account) { throw AccountNotConnected; } - const erc20CallData = await this.provider.account.encodeBatchExecute([ - approveData, - mainCall, - ]); + + let calls: BatchUserOperationCallData; + + if (isBatchUserOperationCallData(mainCall)) { + calls = [approveData, ...mainCall]; + } else { + calls = [approveData, mainCall]; + } + const erc20CallData = await this.provider.account.encodeBatchExecute( + calls + ); return { ...struct, callData: erc20CallData, @@ -176,52 +203,46 @@ export class TokenPaymaster extends Paymaster { paymasterProvider?: PaymasterAndBundlerProviders, shouldOverrideFee?: boolean ): Promise { - try { - const mainCall = await this.decodeMainCallFromCallData( - struct.sender, - struct.callData - ); - if (!mainCall) { - throw IncorrectCallDataForTokenPaymaster; - } - const chainId = await getChainId(this.provider.getProjectId()); - if (!chainId) { - throw new Error("ChainId not found"); - } - const gasTokenAddress = getGasTokenAddress( - this.paymasterConfig.gasToken, - chainId + const mainCall = await this.decodeMainCallFromCallData( + struct.sender, + struct.callData + ); + if (!mainCall) { + throw IncorrectCallDataForTokenPaymaster; + } + const chainId = await getChainId(this.provider.getProjectId()); + if (!chainId) { + throw new Error("ChainId not found"); + } + const gasTokenAddress = getGasTokenAddress( + this.paymasterConfig.gasToken, + chainId + ); + let paymasterAddress = await this.getPaymasterAddress(paymasterProvider); + if ( + gasTokenAddress !== undefined && + paymasterAddress !== undefined && + isAddress(paymasterAddress) + ) { + const erc20UserOp = await this.getERC20UserOp( + struct, + mainCall, + gasTokenAddress, + paymasterAddress ); - let paymasterAddress = await this.getPaymasterAddress(paymasterProvider); - if ( - gasTokenAddress !== undefined && - paymasterAddress !== undefined && - isAddress(paymasterAddress) - ) { - const erc20UserOp = await this.getERC20UserOp( - struct, - mainCall, - gasTokenAddress, - paymasterAddress - ); - if (!erc20UserOp) { - return; - } - const paymasterResp = await this.signUserOp({ - userOp: struct, - callData: struct.callData, - gasTokenAddress, - erc20UserOp, - erc20CallData: erc20UserOp.callData, - paymasterProvider, - shouldOverrideFee, - }); - if (paymasterResp) { - return paymasterResp; - } + if (!erc20UserOp) { + return; } - } catch (error) { - console.log(error); + const paymasterResp = await this.signUserOp({ + userOp: struct, + callData: struct.callData, + gasTokenAddress, + erc20UserOp, + erc20CallData: erc20UserOp.callData, + paymasterProvider, + shouldOverrideFee, + }); + return paymasterResp; } return; } diff --git a/packages/accounts/src/kernel-zerodev/paymaster/verifying-paymaster.ts b/packages/accounts/src/kernel-zerodev/paymaster/verifying-paymaster.ts index 448913cf..203ef858 100644 --- a/packages/accounts/src/kernel-zerodev/paymaster/verifying-paymaster.ts +++ b/packages/accounts/src/kernel-zerodev/paymaster/verifying-paymaster.ts @@ -19,19 +19,12 @@ export class VerifyingPaymaster extends Paymaster { paymasterProvider?: PaymasterAndBundlerProviders, shouldOverrideFee?: boolean ): Promise { - try { - const hexifiedUserOp = deepHexlify(await resolveProperties(struct)); - const paymasterResp = await this.signUserOp({ - userOp: hexifiedUserOp, - paymasterProvider, - shouldOverrideFee, - }); - if (paymasterResp) { - return paymasterResp; - } - } catch (error) { - console.log(error); - } - return; + const hexifiedUserOp = deepHexlify(await resolveProperties(struct)); + const paymasterResp = await this.signUserOp({ + userOp: hexifiedUserOp, + paymasterProvider, + shouldOverrideFee, + }); + return paymasterResp; } } diff --git a/packages/accounts/src/kernel-zerodev/provider.ts b/packages/accounts/src/kernel-zerodev/provider.ts index a2f1cd7b..d2b21455 100644 --- a/packages/accounts/src/kernel-zerodev/provider.ts +++ b/packages/accounts/src/kernel-zerodev/provider.ts @@ -190,8 +190,8 @@ export class ZeroDevProvider extends SmartAccountProvider { uoStruct = await asyncPipe( this.dummyPaymasterDataMiddleware, this.feeDataGetter, - this.paymasterDataMiddleware, this.gasEstimator, + this.paymasterDataMiddleware, this.customMiddleware ?? noOpMiddleware, async (struct) => ({ ...struct, ...overrides }) )({ @@ -298,8 +298,7 @@ export class ZeroDevProvider extends SmartAccountProvider { dummyPaymasterDataMiddleware: AccountMiddlewareFn = async ( struct: UserOperationStruct ): Promise => { - struct.paymasterAndData = - "0xfe7dbcab8aaee4eb67943c1e6be95b1d065985c6000000000000000000000000000000000000000000000000000001869aa31cf400000000000000000000000000000000000000000000000000000000000000007dfe2190f34af27b265bae608717cdc9368b471fc0c097ab7b4088f255b4961e57b039e7e571b15221081c5dce7bcb93459b27a3ab65d2f8a889f4a40b4022801b"; + struct.paymasterAndData = "0x"; return struct; };