UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

436 lines (435 loc) 19.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const zeroDevWalletProvider_1 = require("./zeroDevWalletProvider"); const viem_1 = require("viem"); const sdk_1 = require("@zerodev/sdk"); const ecdsa_validator_1 = require("@zerodev/ecdsa-validator"); const intent_1 = require("@zerodev/intent"); // ========================================================= // Consts // ========================================================= const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; const MOCK_NETWORK_ID = "mainnet"; const MOCK_PROJECT_ID = "project-1234"; const MOCK_TRANSACTION_HASH = "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"; const MOCK_SIGNATURE = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b"; const MOCK_NETWORK = { protocolFamily: "evm", networkId: MOCK_NETWORK_ID, chainId: "1", }; const mockPublicClient = { readContract: jest.fn(), waitForTransactionReceipt: jest.fn(), getBalance: jest.fn(), }; // ========================================================= // Mocks // ========================================================= // Mock Viem jest.mock("viem", () => { return { createPublicClient: jest.fn(() => mockPublicClient), http: jest.fn(), zeroAddress: "0x0000000000000000000000000000000000000000", toAccount: jest.fn(), }; }); // Mock ZeroDev SDK jest.mock("@zerodev/sdk", () => ({ createKernelAccount: jest.fn(), KERNEL_V3_2: "v3.2", getEntryPoint: jest.fn().mockReturnValue("0xENTRYPOINT"), })); // Mock ECDSA Validator jest.mock("@zerodev/ecdsa-validator", () => ({ signerToEcdsaValidator: jest.fn(), })); // Mock Intent jest.mock("@zerodev/intent", () => ({ createIntentClient: jest.fn(), installIntentExecutor: jest.fn(), INTENT_V0_3: "v0.3", })); jest.mock("../network", () => ({ NETWORK_ID_TO_VIEM_CHAIN: { mainnet: { id: 1, name: "Ethereum" }, sepolia: { id: 11155111, name: "Sepolia" }, }, })); jest.mock("../analytics", () => ({ sendAnalyticsEvent: jest.fn().mockImplementation(() => Promise.resolve()), })); // ========================================================= // Mock Implementations // ========================================================= const mockKernelAccount = { address: MOCK_ADDRESS, signMessage: jest.fn(), signTypedData: jest.fn(), }; const mockIntentClient = { sendUserIntent: jest.fn(), waitForUserIntentExecutionReceipt: jest.fn(), }; const mockEvmWalletProvider = { getAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), signMessage: jest.fn(), signTransaction: jest.fn(), signTypedData: jest.fn(), getNetwork: jest.fn().mockReturnValue(MOCK_NETWORK), toSigner: jest.fn().mockReturnValue({ address: MOCK_ADDRESS, signMessage: jest.fn(), signTransaction: jest.fn(), signTypedData: jest.fn(), }), }; // ========================================================= // Test Suite // ========================================================= describe("ZeroDevWalletProvider", () => { let provider; beforeEach(async () => { jest.clearAllMocks(); // Configure mock implementations sdk_1.createKernelAccount.mockResolvedValue(mockKernelAccount); intent_1.createIntentClient.mockResolvedValue(mockIntentClient); ecdsa_validator_1.signerToEcdsaValidator.mockResolvedValue({ validator: "mock-validator" }); mockPublicClient.readContract.mockResolvedValue("mock_result"); mockPublicClient.waitForTransactionReceipt.mockResolvedValue({ transactionHash: MOCK_TRANSACTION_HASH, }); mockPublicClient.getBalance.mockResolvedValue(BigInt(1000000000000000000)); mockKernelAccount.signMessage.mockResolvedValue(MOCK_SIGNATURE); mockKernelAccount.signTypedData.mockResolvedValue(MOCK_SIGNATURE); // Mock Intent Client behavior mockIntentClient.sendUserIntent.mockResolvedValue({ outputUiHash: { uiHash: "0xmockUiHash" }, inputsUiHash: { uiHash: "0xmockUiHash" }, }); mockIntentClient.waitForUserIntentExecutionReceipt.mockResolvedValue({ intentHash: "0xmockIntentHash", sender: MOCK_ADDRESS, relayer: MOCK_ADDRESS, executionChainId: "0x1", logs: [], receipt: { transactionHash: MOCK_TRANSACTION_HASH }, }); // Create provider instance provider = await zeroDevWalletProvider_1.ZeroDevWalletProvider.configureWithWallet({ signer: mockEvmWalletProvider.toSigner(), projectId: MOCK_PROJECT_ID, networkId: MOCK_NETWORK_ID, entryPointVersion: "0.7", }); }); // ========================================================= // Initialization Tests // ========================================================= describe("initialization", () => { it("should initialize with a signer", async () => { expect(provider).toBeInstanceOf(zeroDevWalletProvider_1.ZeroDevWalletProvider); expect(provider.getAddress()).toBe(MOCK_ADDRESS); expect(provider.getNetwork()).toEqual(MOCK_NETWORK); expect(sdk_1.createKernelAccount).toHaveBeenCalled(); expect(intent_1.createIntentClient).toHaveBeenCalled(); }); it("should throw error when signer is not provided", async () => { await expect(zeroDevWalletProvider_1.ZeroDevWalletProvider.configureWithWallet({ signer: undefined, projectId: MOCK_PROJECT_ID, networkId: MOCK_NETWORK_ID, })).rejects.toThrow("Signer is required"); }); it("should throw error when project ID is not provided", async () => { await expect(zeroDevWalletProvider_1.ZeroDevWalletProvider.configureWithWallet({ signer: mockEvmWalletProvider.toSigner(), projectId: "", networkId: MOCK_NETWORK_ID, })).rejects.toThrow("ZeroDev project ID is required"); }); it("should initialize with a specific address if provided", async () => { const customAddress = "0xCustomAddress"; await zeroDevWalletProvider_1.ZeroDevWalletProvider.configureWithWallet({ signer: mockEvmWalletProvider.toSigner(), projectId: MOCK_PROJECT_ID, networkId: MOCK_NETWORK_ID, address: customAddress, }); expect(sdk_1.createKernelAccount).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ address: customAddress, })); }); }); // ========================================================= // Basic Wallet Methods Tests // ========================================================= describe("basic wallet methods", () => { it("should get the address", () => { expect(provider.getAddress()).toBe(MOCK_ADDRESS); }); it("should get the network", () => { expect(provider.getNetwork()).toEqual(MOCK_NETWORK); }); it("should get the name", () => { expect(provider.getName()).toBe("zerodev_wallet_provider"); }); it("should get the balance", async () => { const balance = await provider.getBalance(); expect(balance).toBe(BigInt(1000000000000000000)); expect(mockPublicClient.getBalance).toHaveBeenCalledWith({ address: MOCK_ADDRESS, }); }); it("should get the kernel account", () => { expect(provider.getKernelAccount()).toBe(mockKernelAccount); }); it("should get the intent client", () => { expect(provider.getIntentClient()).toBe(mockIntentClient); }); }); // ========================================================= // Signing Operations Tests // ========================================================= describe("signing operations", () => { it("should sign messages", async () => { const message = "Hello, world!"; const signature = await provider.signMessage(message); expect(mockKernelAccount.signMessage).toHaveBeenCalledWith({ message: message, }); expect(signature).toBe(MOCK_SIGNATURE); }); it("should sign Uint8Array messages", async () => { const messageBytes = new TextEncoder().encode("Hello, world!"); const signature = await provider.signMessage(messageBytes); expect(mockKernelAccount.signMessage).toHaveBeenCalledWith({ message: "Hello, world!", }); expect(signature).toBe(MOCK_SIGNATURE); }); it("should sign typed data", async () => { const typedData = { domain: { name: "Example" }, types: { Test: [{ name: "test", type: "string" }] }, message: { test: "example" }, primaryType: "Test", }; const signature = await provider.signTypedData(typedData); expect(mockKernelAccount.signTypedData).toHaveBeenCalledWith(typedData); expect(signature).toBe(MOCK_SIGNATURE); }); it("should throw error when signing transactions directly", async () => { const tx = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await expect(provider.signTransaction(tx)).rejects.toThrow("signTransaction is not supported for ZeroDev Wallet Provider"); }); }); // ========================================================= // Transaction Operations Tests // ========================================================= describe("transaction operations", () => { it("should send native token transfer transactions", async () => { const to = "0x1234567890123456789012345678901234567890"; const value = BigInt(1000000000000000000); const transaction = { to, value, data: "0x", }; const txHash = await provider.sendTransaction(transaction); expect(mockIntentClient.sendUserIntent).toHaveBeenCalledWith({ calls: [ { to, value, data: "0x", }, ], outputTokens: [ { address: viem_1.zeroAddress, chainId: 1, amount: value, }, ], }); expect(mockIntentClient.waitForUserIntentExecutionReceipt).toHaveBeenCalled(); expect(txHash).toBe(MOCK_TRANSACTION_HASH); }); it("should send contract interaction transactions", async () => { const to = "0x1234567890123456789012345678901234567890"; const data = "0xabcdef"; const transaction = { to, data, }; const txHash = await provider.sendTransaction(transaction); expect(mockIntentClient.sendUserIntent).toHaveBeenCalledWith({ calls: [ { to, value: BigInt(0), data, }, ], chainId: 1, }); expect(mockIntentClient.waitForUserIntentExecutionReceipt).toHaveBeenCalled(); expect(txHash).toBe(MOCK_TRANSACTION_HASH); }); it("should handle native transfers using nativeTransfer method", async () => { const to = "0x1234567890123456789012345678901234567890"; const value = "1.0"; const valueInWei = BigInt(parseFloat(value) * 10 ** 18); const txHash = await provider.nativeTransfer(to, value); expect(mockIntentClient.sendUserIntent).toHaveBeenCalledWith({ calls: [ { to: to, value: valueInWei, data: "0x", }, ], outputTokens: [ { address: viem_1.zeroAddress, chainId: 1, amount: valueInWei, }, ], }); expect(mockIntentClient.waitForUserIntentExecutionReceipt).toHaveBeenCalled(); expect(txHash).toBe(MOCK_TRANSACTION_HASH); }); it("should wait for transaction receipt", async () => { const hash = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; await provider.waitForTransactionReceipt(hash); expect(mockPublicClient.waitForTransactionReceipt).toHaveBeenCalledWith({ hash }); }); it("should handle transaction failures", async () => { mockIntentClient.sendUserIntent.mockRejectedValueOnce(new Error("Transaction failed")); const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await expect(provider.sendTransaction(transaction)).rejects.toThrow("Transaction failed"); }); it("should handle receipt waiting failures", async () => { mockIntentClient.waitForUserIntentExecutionReceipt.mockRejectedValueOnce(new Error("Receipt waiting failed")); const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await expect(provider.sendTransaction(transaction)).rejects.toThrow("Receipt waiting failed"); }); it("should handle missing transaction hash in receipt", async () => { mockIntentClient.waitForUserIntentExecutionReceipt.mockResolvedValueOnce({ receipt: {}, }); const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; const txHash = await provider.sendTransaction(transaction); expect(txHash).toBe("0x"); }); }); // ========================================================= // Contract Interaction Tests // ========================================================= describe("contract interactions", () => { it("should read contract data", async () => { const abi = [ { name: "balanceOf", type: "function", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "balance", type: "uint256" }], stateMutability: "view", }, ]; const result = await provider.readContract({ address: "0x1234567890123456789012345678901234567890", abi, functionName: "balanceOf", args: [MOCK_ADDRESS], }); expect(result).toBe("mock_result"); expect(mockPublicClient.readContract).toHaveBeenCalled(); }); it("should handle contract read failures", async () => { mockPublicClient.readContract.mockRejectedValueOnce(new Error("Contract read failed")); const abi = [ { name: "balanceOf", type: "function", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "balance", type: "uint256" }], stateMutability: "view", }, ]; await expect(provider.readContract({ address: "0x1234567890123456789012345678901234567890", abi, functionName: "balanceOf", args: [MOCK_ADDRESS], })).rejects.toThrow("Contract read failed"); }); }); // ========================================================= // Chain Abstracted Balance Tests // ========================================================= describe("chain abstracted balance", () => { it("should get chain abstracted balance", async () => { const mockCABResult = { balance: BigInt(1000000000000000000), tokenAddress: "0x1234567890123456789012345678901234567890", }; mockIntentClient.getCAB = jest.fn().mockResolvedValue(mockCABResult); const options = { tokenTickers: ["USDC"], networks: [1], }; const result = await provider.getCAB(options); expect(mockIntentClient.getCAB).toHaveBeenCalledWith(options); expect(result).toEqual(mockCABResult); }); it("should handle getCAB failures", async () => { mockIntentClient.getCAB = jest.fn().mockRejectedValue(new Error("CAB check failed")); const options = { tokenTickers: ["USDC"], networks: [1234], }; await expect(provider.getCAB(options)).rejects.toThrow("CAB check failed"); }); }); // ========================================================= // Error Handling Tests // ========================================================= describe("error handling", () => { it("should handle intent client initialization failures", async () => { intent_1.createIntentClient.mockRejectedValueOnce(new Error("Intent client initialization failed")); await expect(zeroDevWalletProvider_1.ZeroDevWalletProvider.configureWithWallet({ signer: mockEvmWalletProvider.toSigner(), projectId: MOCK_PROJECT_ID, networkId: MOCK_NETWORK_ID, })).rejects.toThrow("Intent client initialization failed"); }); it("should handle kernel account initialization failures", async () => { sdk_1.createKernelAccount.mockRejectedValueOnce(new Error("Kernel account initialization failed")); await expect(zeroDevWalletProvider_1.ZeroDevWalletProvider.configureWithWallet({ signer: mockEvmWalletProvider.toSigner(), projectId: MOCK_PROJECT_ID, networkId: MOCK_NETWORK_ID, })).rejects.toThrow("Kernel account initialization failed"); }); it("should handle balance check failures", async () => { mockPublicClient.getBalance.mockRejectedValueOnce(new Error("Balance check failed")); await expect(provider.getBalance()).rejects.toThrow("Balance check failed"); }); }); });