UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

344 lines (343 loc) 15.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const cdp_sdk_1 = require("@coinbase/cdp-sdk"); const cdpEvmWalletProvider_1 = require("./cdpEvmWalletProvider"); // ========================================================= // consts // ========================================================= const mockPublicClient = { waitForTransactionReceipt: jest.fn(), readContract: jest.fn(), getTransactionCount: jest.fn(), estimateFeesPerGas: jest.fn(), estimateGas: jest.fn(), getBalance: jest.fn(), }; const mockWalletClient = { sendTransaction: jest.fn(), }; // ========================================================= // mocks // ========================================================= jest.mock("../analytics", () => ({ sendAnalyticsEvent: jest.fn().mockImplementation(() => Promise.resolve()), })); jest.mock("../../package.json", () => ({ version: "1.0.0", })); jest.mock("viem", () => { return { createPublicClient: jest.fn(() => mockPublicClient), createWalletClient: jest.fn(() => mockWalletClient), http: jest.fn(), serializeTransaction: jest.fn((_tx) => "0xserialized"), }; }); jest.mock("../network", () => { return { NETWORK_ID_TO_CHAIN_ID: { "base-mainnet": "8453", "base-sepolia": "84532", }, NETWORK_ID_TO_VIEM_CHAIN: { "base-mainnet": { id: 8453 }, "base-sepolia": { id: 84532 }, }, }; }); // Mock CdpClient jest.mock("@coinbase/cdp-sdk", () => { const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; const MOCK_SIGNATURE = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b01"; const mockCreateAccount = jest.fn().mockImplementation(() => Promise.resolve({ address: MOCK_ADDRESS, signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE), signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE), signTransaction: jest.fn().mockResolvedValue({ signature: MOCK_SIGNATURE }), })); const mockSignTransaction = jest .fn() .mockImplementation(async () => ({ signature: MOCK_SIGNATURE })); const mockSendTransaction = jest .fn() .mockImplementation(async () => ({ transactionHash: MOCK_TRANSACTION_HASH })); const mockEvmClient = { createAccount: mockCreateAccount, getAccount: jest.fn(), signTransaction: mockSignTransaction, sendTransaction: mockSendTransaction, }; return { CdpClient: jest.fn().mockImplementation(() => ({ evm: mockEvmClient, })), EvmServerAccount: jest.fn().mockImplementation(() => ({ address: MOCK_ADDRESS, signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE), signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE), signTransaction: jest.fn().mockResolvedValue({ signature: MOCK_SIGNATURE }), })), }; }); // ========================================================= // test constants // ========================================================= const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; const MOCK_NETWORK_ID = "base-mainnet"; const MOCK_TRANSACTION_HASH = "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"; const MOCK_SIGNATURE = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b01"; const MOCK_BALANCE = 1000000000000000000n; const MOCK_NETWORK = { protocolFamily: "evm", networkId: MOCK_NETWORK_ID, chainId: "8453", }; const MOCK_TRANSACTION_RECEIPT = { transactionHash: MOCK_TRANSACTION_HASH, }; describe("CdpEvmWalletProvider", () => { let provider; let mockCdpClient; let mockServerAccount; let mockSignTransaction; let mockSendTransaction; beforeEach(async () => { jest.clearAllMocks(); mockSignTransaction = jest.fn().mockImplementation(async () => ({ signature: MOCK_SIGNATURE })); mockSendTransaction = jest .fn() .mockImplementation(async () => ({ transactionHash: MOCK_TRANSACTION_HASH })); mockCdpClient = new cdp_sdk_1.CdpClient({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", }); mockServerAccount = { address: MOCK_ADDRESS, signMessage: jest.fn().mockResolvedValue(MOCK_SIGNATURE), signTypedData: jest.fn().mockResolvedValue(MOCK_SIGNATURE), }; // Set up the mock server account for the provider mockCdpClient.evm.createAccount.mockResolvedValue(mockServerAccount); mockCdpClient.evm.signTransaction = mockSignTransaction; mockCdpClient.evm.sendTransaction = mockSendTransaction; mockPublicClient.waitForTransactionReceipt.mockResolvedValue(MOCK_TRANSACTION_RECEIPT); mockPublicClient.readContract.mockResolvedValue("mock_result"); mockPublicClient.getTransactionCount.mockResolvedValue(1); mockPublicClient.estimateFeesPerGas.mockResolvedValue({ maxFeePerGas: BigInt(100000000), maxPriorityFeePerGas: BigInt(10000000), }); mockPublicClient.estimateGas.mockResolvedValue(BigInt(21000)); mockPublicClient.getBalance.mockResolvedValue(MOCK_BALANCE); mockWalletClient.sendTransaction.mockResolvedValue(MOCK_TRANSACTION_HASH); provider = await cdpEvmWalletProvider_1.CdpEvmWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", networkId: MOCK_NETWORK_ID, }); }); // ========================================================= // initialization tests // ========================================================= describe("initialization", () => { it("should initialize with API keys", async () => { const provider = await cdpEvmWalletProvider_1.CdpEvmWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", networkId: MOCK_NETWORK_ID, }); expect(provider.getAddress()).toBe(MOCK_ADDRESS); expect(provider.getNetwork()).toEqual(MOCK_NETWORK); }); it("should default to base-sepolia if network not provided", async () => { const provider = await cdpEvmWalletProvider_1.CdpEvmWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", }); expect(provider.getNetwork().networkId).toBe("base-sepolia"); }); it("should handle initialization failures gracefully", async () => { // Create a new mock client for this test const mockCreateAccount = jest.fn().mockRejectedValue(new Error("Failed to create account")); const mockEvmClient = { createAccount: mockCreateAccount, getAccount: jest.fn(), signTransaction: jest.fn(), }; // Override the mock for this test const mockCdpClient = new cdp_sdk_1.CdpClient({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", }); mockCdpClient.evm = mockEvmClient; // Override the CdpClient constructor mock cdp_sdk_1.CdpClient.mockImplementation(() => mockCdpClient); await expect(cdpEvmWalletProvider_1.CdpEvmWalletProvider.configureWithWallet({ apiKeyId: "test-key-id", apiKeySecret: "test-key-secret", walletSecret: "test-wallet-secret", networkId: MOCK_NETWORK_ID, })).rejects.toThrow("Failed to create account"); }); }); // ========================================================= // basic wallet method 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("cdp_evm_wallet_provider"); }); it("should get the balance", async () => { const balance = await provider.getBalance(); expect(balance).toBe(MOCK_BALANCE); expect(mockPublicClient.getBalance).toHaveBeenCalledWith({ address: MOCK_ADDRESS, }); }); it("should handle connection errors during balance check", async () => { mockPublicClient.getBalance.mockRejectedValueOnce(new Error("Network connection error")); await expect(provider.getBalance()).rejects.toThrow("Network connection error"); }); }); // ========================================================= // signing operation tests // ========================================================= describe("signing operations", () => { it("should sign messages", async () => { const signature = await provider.signMessage("Hello, world!"); expect(mockServerAccount.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(mockServerAccount.signTypedData).toHaveBeenCalledWith(typedData); expect(signature).toBe(MOCK_SIGNATURE); }); it("should sign transactions", async () => { const tx = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; const signature = await provider.signTransaction(tx); expect(mockCdpClient.evm.signTransaction).toHaveBeenCalledWith({ address: MOCK_ADDRESS, transaction: expect.any(String), }); expect(signature).toBe(MOCK_SIGNATURE); }); it("should handle signing failures", async () => { // Create a failing mock for this test const mockFailingSignTransaction = jest.fn().mockRejectedValue(new Error("Signing failed")); mockCdpClient.evm.signTransaction = mockFailingSignTransaction; const tx = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await expect(provider.signTransaction(tx)).rejects.toThrow("Signing failed"); // Restore the original mock mockCdpClient.evm.signTransaction = mockSignTransaction; }); }); // ========================================================= // transaction operation tests // ========================================================= describe("transaction operations", () => { it("should send transactions", async () => { const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; const txHash = await provider.sendTransaction(transaction); expect(mockSendTransaction).toHaveBeenCalled(); expect(txHash).toBe(MOCK_TRANSACTION_HASH); }); it("should handle transaction failures during send", async () => { mockSendTransaction.mockRejectedValueOnce(new Error("Transaction signing failed")); const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await expect(provider.sendTransaction(transaction)).rejects.toThrow("Transaction signing failed"); }); it("should handle receipt timeout errors", async () => { mockPublicClient.waitForTransactionReceipt.mockRejectedValueOnce(new Error("Timed out")); const hash = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; await expect(provider.waitForTransactionReceipt(hash)).rejects.toThrow("Timed out"); }); it("should handle transaction with invalid address", async () => { mockSendTransaction.mockRejectedValueOnce(new Error("Invalid address format")); const invalidAddress = "not_a_valid_address"; const value = "1000000000000000000"; // 1 ETH in wei await expect(provider.nativeTransfer(invalidAddress, value)).rejects.toThrow("Invalid address format"); }); }); // ========================================================= // 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 network errors during contract reads", async () => { mockPublicClient.readContract.mockRejectedValueOnce(new Error("Contract read error")); 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 error"); }); it("should handle invalid ABI format in contract reads", async () => { mockPublicClient.readContract.mockRejectedValueOnce(new TypeError("Invalid ABI format")); const invalidAbi = "not_a_valid_abi"; await expect(provider.readContract({ address: "0x1234567890123456789012345678901234567890", abi: invalidAbi, functionName: "balanceOf", args: [MOCK_ADDRESS], })).rejects.toThrow("Invalid ABI format"); }); }); });