UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

389 lines (388 loc) 17.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const coinbaseSdk = __importStar(require("@coinbase/coinbase-sdk")); global.fetch = jest.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve({}), })); jest.mock("../analytics", () => ({ sendAnalyticsEvent: jest.fn().mockImplementation(() => Promise.resolve()), })); // ========================================================= // constants // ========================================================= const MOCK_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; const MOCK_CHAIN_ID = "1"; const MOCK_NETWORK_ID = "mainnet"; const MOCK_TRANSACTION_HASH = "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"; const MOCK_BALANCE = BigInt(1000000000000000000); const mockPublicClient = { waitForTransactionReceipt: jest.fn(), readContract: jest.fn(), }; var UserOperationStatus; (function (UserOperationStatus) { UserOperationStatus["CREATED"] = "created"; UserOperationStatus["PENDING"] = "pending"; UserOperationStatus["COMPLETE"] = "complete"; })(UserOperationStatus || (UserOperationStatus = {})); // ========================================================= // mocks // ========================================================= jest.mock("viem", () => { return { createPublicClient: jest.fn(() => mockPublicClient), http: jest.fn(), parseEther: jest.fn((_value) => MOCK_BALANCE), }; }); jest.mock("../network", () => { return { NETWORK_ID_TO_CHAIN_ID: { mainnet: "1", "base-sepolia": "84532", }, NETWORK_ID_TO_VIEM_CHAIN: { mainnet: {}, "base-sepolia": {}, }, }; }); jest.mock("@coinbase/coinbase-sdk", () => { return { CHAIN_ID_TO_NETWORK_ID: { "1": "mainnet", "84532": "base-sepolia", }, NETWORK_ID_TO_CHAIN_ID: { mainnet: "1", "base-sepolia": "84532", }, NETWORK_ID_TO_VIEM_CHAIN: { mainnet: {}, "base-sepolia": {}, }, Coinbase: { configure: jest.fn(), configureFromJson: jest.fn(), networks: { BaseSepolia: "base-sepolia", }, }, waitForUserOperation: jest.fn(), createSmartWallet: jest.fn(), }; }); // ========================================================= // tests // ========================================================= describe("LegacyCdpSmartWalletProvider", () => { let provider; let mockNetworkScopedWallet; let mockWaitForUserOperation; beforeEach(() => { jest.clearAllMocks(); const mockGetBalance = jest.fn(); mockGetBalance.mockResolvedValue(MOCK_BALANCE); const mockSendTransaction = jest.fn(); mockSendTransaction.mockResolvedValue(MOCK_TRANSACTION_HASH); const mockSendUserOperation = jest.fn(); mockNetworkScopedWallet = { address: MOCK_ADDRESS, getBalance: mockGetBalance, sendTransaction: mockSendTransaction, sendUserOperation: mockSendUserOperation, }; const mockUserOperationWait = jest.fn(); mockUserOperationWait.mockResolvedValue({ status: UserOperationStatus.COMPLETE, transactionHash: MOCK_TRANSACTION_HASH, }); const mockUserOperation = { hash: MOCK_TRANSACTION_HASH, wait: mockUserOperationWait, }; mockNetworkScopedWallet.sendUserOperation.mockResolvedValue(mockUserOperation); mockPublicClient.waitForTransactionReceipt.mockResolvedValue({ transactionHash: MOCK_TRANSACTION_HASH, }); mockPublicClient.readContract.mockResolvedValue("mock_result"); provider = { sendTransaction: jest.fn(), sendUserOperation: jest.fn(), waitForTransactionReceipt: jest.fn(), signMessage: jest.fn(), signTypedData: jest.fn(), signTransaction: jest.fn(), getAddress: jest.fn(), getNetwork: jest.fn(), getName: jest.fn(), getBalance: jest.fn(), readContract: jest.fn(), nativeTransfer: jest.fn(), _smartWallet: mockNetworkScopedWallet, }; provider.getAddress.mockReturnValue(MOCK_ADDRESS); provider.getNetwork.mockReturnValue({ protocolFamily: "evm", networkId: MOCK_NETWORK_ID, chainId: MOCK_CHAIN_ID, }); provider.getName.mockReturnValue("legacy_cdp_smart_wallet_provider"); provider.getBalance.mockResolvedValue(MOCK_BALANCE); provider.sendTransaction.mockImplementation(async (tx) => { const _result = await mockNetworkScopedWallet.sendUserOperation({ calls: [tx] }); const waitResult = await _result.wait(); if (waitResult.status === "failed") { throw new Error(`Transaction failed with status ${waitResult.status}`); } return _result.hash; }); provider.sendUserOperation.mockImplementation(async (op) => { const _result = await mockNetworkScopedWallet.sendUserOperation(op); return _result.hash; }); provider.waitForTransactionReceipt.mockImplementation((hash) => mockPublicClient.waitForTransactionReceipt({ hash })); provider.readContract.mockImplementation(async (params) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any return mockPublicClient.readContract(params); }); provider.nativeTransfer.mockImplementation(async (to, _value) => { await mockNetworkScopedWallet.sendUserOperation({ calls: [ { to, value: BigInt(1000000000000000000), }, ], }); return MOCK_TRANSACTION_HASH; }); const notImplementedError = new Error("Not implemented"); provider.signMessage.mockRejectedValue(notImplementedError); provider.signTypedData.mockRejectedValue(notImplementedError); provider.signTransaction.mockRejectedValue(notImplementedError); mockWaitForUserOperation = jest.fn(); mockWaitForUserOperation.mockImplementation((operation) => { return operation.wait(); }); jest .spyOn(coinbaseSdk, "waitForUserOperation") .mockImplementation(mockWaitForUserOperation); }); // ========================================================= // transaction operations // ========================================================= describe("transaction operations", () => { it("should send transactions", async () => { const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; const txHash = await provider.sendTransaction(transaction); expect(txHash).toBe(MOCK_TRANSACTION_HASH); expect(mockNetworkScopedWallet.sendUserOperation).toHaveBeenCalled(); }); it("should send a user operation", async () => { const calls = [ { to: "0x1234567890123456789012345678901234567890", data: "0xabcdef", value: 0n, }, ]; const txHash = await provider.sendUserOperation({ calls }); expect(txHash).toBe(MOCK_TRANSACTION_HASH); expect(mockNetworkScopedWallet.sendUserOperation).toHaveBeenCalledWith({ calls }); }); it("should wait for transaction receipts", async () => { await provider.waitForTransactionReceipt(MOCK_TRANSACTION_HASH); expect(mockPublicClient.waitForTransactionReceipt).toHaveBeenCalled(); }); it("should handle transaction failures", async () => { mockWaitForUserOperation.mockRejectedValueOnce(new Error("Failed to send transaction")); mockNetworkScopedWallet.sendUserOperation.mockRejectedValueOnce(new Error("Failed to send transaction")); await expect(provider.sendTransaction({ to: MOCK_ADDRESS, value: MOCK_BALANCE, })).rejects.toThrow("Failed to send transaction"); }); it("should handle network errors in transactions", async () => { mockNetworkScopedWallet.sendUserOperation.mockRejectedValueOnce(new Error("Network connection error")); await expect(provider.sendTransaction({ to: MOCK_ADDRESS, value: MOCK_BALANCE, })).rejects.toThrow("Network connection error"); }); it("should handle invalid address errors", async () => { mockNetworkScopedWallet.sendUserOperation.mockRejectedValueOnce(new Error("Invalid address format")); const invalidAddressHex = "0xinvalid"; await expect(provider.sendTransaction({ to: invalidAddressHex, value: MOCK_BALANCE, })).rejects.toThrow("Invalid address format"); }); it("should handle receipt retrieval failures", async () => { mockPublicClient.waitForTransactionReceipt.mockRejectedValueOnce(new Error("Receipt retrieval failed")); await expect(provider.waitForTransactionReceipt(MOCK_TRANSACTION_HASH)).rejects.toThrow("Receipt retrieval failed"); }); it("should handle operation failures when sending transactions", async () => { const mockUserOperationWait = jest.fn(); mockUserOperationWait.mockResolvedValue({ status: "failed", transactionHash: MOCK_TRANSACTION_HASH, }); const failedOperation = { hash: MOCK_TRANSACTION_HASH, wait: mockUserOperationWait, }; mockNetworkScopedWallet.sendUserOperation.mockResolvedValueOnce(failedOperation); const transaction = { to: "0x1234567890123456789012345678901234567890", value: BigInt(1000000000000000000), }; await expect(provider.sendTransaction(transaction)).rejects.toThrow("Transaction failed with status failed"); }); it("should handle exceptions when sending user operations", async () => { mockNetworkScopedWallet.sendUserOperation.mockRejectedValueOnce(new Error("Failed to send")); const calls = [ { to: "0x1234567890123456789012345678901234567890", data: "0xabcdef", value: BigInt(0), }, ]; await expect(provider.sendUserOperation({ calls })).rejects.toThrow("Failed to send"); }); it("should handle send user operation timeout", async () => { mockNetworkScopedWallet.sendUserOperation.mockRejectedValueOnce(new Error("User operation timed out")); const calls = [ { to: "0x1234567890123456789012345678901234567890", data: "0xabcdef", value: 0n, }, ]; await expect(provider.sendUserOperation({ calls })).rejects.toThrow("User operation timed out"); }); }); // ========================================================= // native token transfer operations // ========================================================= describe("native token operations", () => { it("should transfer native tokens", async () => { const to = "0x1234567890123456789012345678901234567890"; const value = "1"; const txHash = await provider.nativeTransfer(to, value); expect(mockNetworkScopedWallet.sendUserOperation).toHaveBeenCalled(); expect(txHash).toBe(MOCK_TRANSACTION_HASH); }); it("should handle operation failures when transferring native tokens", async () => { provider.nativeTransfer.mockRejectedValueOnce(new Error("Transfer failed with status failed")); const to = "0x1234567890123456789012345678901234567890"; const value = "1"; await expect(provider.nativeTransfer(to, value)).rejects.toThrow("Transfer failed with status failed"); }); it("should handle invalid address format in native transfer", async () => { provider.nativeTransfer.mockRejectedValueOnce(new Error("Invalid address format")); const invalidAddress = "not_a_valid_address"; await expect(provider.nativeTransfer(invalidAddress, "1")).rejects.toThrow("Invalid address format"); }); it("should handle network errors in native token transfers", async () => { provider.nativeTransfer.mockRejectedValueOnce(new Error("Network error")); await expect(provider.nativeTransfer("0x1234567890123456789012345678901234567890", "1")).rejects.toThrow("Network error"); }); }); // ========================================================= // contract interaction methods // ========================================================= describe("contract interactions", () => { it("should read from contracts", async () => { 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 errors in contract reads", async () => { mockPublicClient.readContract.mockRejectedValueOnce(new Error("Contract read failed")); await expect(provider.readContract({ address: "0x1234567890123456789012345678901234567890", abi: [], functionName: "balanceOf", args: [MOCK_ADDRESS], })).rejects.toThrow("Contract read failed"); }); it("should handle read contract with invalid ABI", async () => { const invalidAbi = "not_an_abi"; const params = { address: "0x1234567890123456789012345678901234567890", abi: invalidAbi, functionName: "balanceOf", args: ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e"], }; mockPublicClient.readContract.mockImplementationOnce(() => { throw new TypeError("Invalid ABI format"); }); await expect(provider.readContract(params)).rejects.toThrow("Invalid ABI format"); }); }); // ========================================================= // signing methods (unsupported operations) // ========================================================= describe("unsupported operations", () => { it("should throw error on sign message", async () => { await expect(provider.signMessage("test")).rejects.toThrow("Not implemented"); }); it("should throw error on sign typed data", async () => { await expect(provider.signTypedData({ domain: {}, types: {}, primaryType: "", message: {}, })).rejects.toThrow("Not implemented"); }); it("should throw error on sign transaction", async () => { await expect(provider.signTransaction({ to: MOCK_ADDRESS, value: MOCK_BALANCE, })).rejects.toThrow("Not implemented"); }); }); });