UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

396 lines (395 loc) 17.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const erc20ActionProvider_1 = require("./erc20ActionProvider"); const schemas_1 = require("./schemas"); const MOCK_AMOUNT = 15; const MOCK_DECIMALS = 6; const MOCK_CONTRACT_ADDRESS = "0x1234567890123456789012345678901234567890"; const MOCK_DESTINATION = "0x9876543210987654321098765432109876543210"; const MOCK_ADDRESS = "0x1234567890123456789012345678901234567890"; const MOCK_SPENDER = "0xabcdef1234567890123456789012345678901234"; describe("Transfer Schema", () => { it("should successfully parse valid input", () => { const validInput = { amount: MOCK_AMOUNT.toString(), tokenAddress: MOCK_CONTRACT_ADDRESS, destinationAddress: MOCK_DESTINATION, }; const result = schemas_1.TransferSchema.safeParse(validInput); expect(result.success).toBe(true); expect(result.data).toEqual(validInput); }); it("should fail parsing empty input", () => { const emptyInput = {}; const result = schemas_1.TransferSchema.safeParse(emptyInput); expect(result.success).toBe(false); }); }); describe("Get Balance Action", () => { let mockWallet; let mockMulticall; const actionProvider = (0, erc20ActionProvider_1.erc20ActionProvider)(); beforeEach(async () => { mockMulticall = jest.fn(); const mockPublicClient = { multicall: mockMulticall, getCode: jest.fn().mockResolvedValue("0x"), }; mockWallet = { getAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), getPublicClient: jest.fn().mockReturnValue(mockPublicClient), }; }); it("should successfully respond", async () => { mockMulticall.mockResolvedValueOnce([ { result: "MockToken" }, // name { result: MOCK_DECIMALS }, // decimals { result: BigInt(MOCK_AMOUNT * 10 ** MOCK_DECIMALS) }, // balance ]); const args = { tokenAddress: MOCK_CONTRACT_ADDRESS, }; const response = await actionProvider.getBalance(mockWallet, args); expect(mockMulticall).toHaveBeenCalled(); expect(response).toContain(`Balance of MockToken (${MOCK_CONTRACT_ADDRESS}) at address ${MOCK_ADDRESS} is ${MOCK_AMOUNT}`); }); it("should fail with an error", async () => { const args = { tokenAddress: MOCK_CONTRACT_ADDRESS, }; mockMulticall.mockRejectedValue(new Error("Failed to get balance")); const response = await actionProvider.getBalance(mockWallet, args); expect(mockMulticall).toHaveBeenCalled(); expect(response).toContain("Error: Could not fetch token details"); }); }); describe("Transfer Action", () => { const TRANSACTION_HASH = "0xghijkl987654321"; let mockWallet; let mockMulticall; const actionProvider = (0, erc20ActionProvider_1.erc20ActionProvider)(); beforeEach(async () => { mockMulticall = jest.fn(); const mockPublicClient = { multicall: mockMulticall, getCode: jest.fn().mockResolvedValue("0x"), }; mockWallet = { sendTransaction: jest.fn(), waitForTransactionReceipt: jest.fn(), getName: jest.fn().mockReturnValue("evm_wallet_provider"), getNetwork: jest.fn().mockReturnValue({ networkId: "base-mainnet", }), getPublicClient: jest.fn().mockReturnValue(mockPublicClient), getAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), }; mockWallet.sendTransaction.mockResolvedValue(TRANSACTION_HASH); mockWallet.waitForTransactionReceipt.mockResolvedValue({}); }); it("should successfully respond", async () => { mockMulticall.mockResolvedValueOnce([ { result: "MockToken" }, // name { result: MOCK_DECIMALS }, // decimals { result: BigInt(100000 * 10 ** MOCK_DECIMALS) }, // balance ]); const args = { amount: MOCK_AMOUNT.toString(), tokenAddress: MOCK_CONTRACT_ADDRESS, destinationAddress: MOCK_DESTINATION, }; const response = await actionProvider.transfer(mockWallet, args); expect(mockMulticall).toHaveBeenCalled(); expect(mockWallet.sendTransaction).toHaveBeenCalled(); expect(mockWallet.waitForTransactionReceipt).toHaveBeenCalledWith(TRANSACTION_HASH); expect(response).toContain(`Transferred ${MOCK_AMOUNT} of MockToken (${MOCK_CONTRACT_ADDRESS}) to ${MOCK_DESTINATION}`); expect(response).toContain(`Transaction hash for the transfer: ${TRANSACTION_HASH}`); }); it("should fail with an error", async () => { mockMulticall.mockRejectedValue(new Error("Failed to get token details")); const args = { amount: MOCK_AMOUNT.toString(), tokenAddress: MOCK_CONTRACT_ADDRESS, destinationAddress: MOCK_DESTINATION, }; const response = await actionProvider.transfer(mockWallet, args); expect(mockMulticall).toHaveBeenCalled(); expect(response).toContain("Error: Could not fetch token details"); }); describe("supportsNetwork", () => { it("should return true when protocolFamily is evm", () => { expect(actionProvider.supportsNetwork({ protocolFamily: "evm" })).toBe(true); }); it("should return false when protocolFamily is not evm", () => { expect(actionProvider.supportsNetwork({ protocolFamily: "solana" })).toBe(false); }); }); }); describe("GetTokenAddress Schema", () => { it("should successfully parse valid token symbol", () => { const validInput = { symbol: "usdc" }; const result = schemas_1.GetTokenAddressSchema.safeParse(validInput); expect(result.success).toBe(true); expect(result.data?.symbol).toBe("USDC"); // Should be uppercase }); it("should fail parsing empty symbol", () => { const emptyInput = { symbol: "" }; const result = schemas_1.GetTokenAddressSchema.safeParse(emptyInput); expect(result.success).toBe(false); }); it("should fail parsing symbol too long", () => { const longInput = { symbol: "VERYLONGTOKENSYMBOL" }; const result = schemas_1.GetTokenAddressSchema.safeParse(longInput); expect(result.success).toBe(false); }); }); describe("Approve Schema", () => { it("should successfully parse valid input", () => { const validInput = { amount: MOCK_AMOUNT.toString(), tokenAddress: MOCK_CONTRACT_ADDRESS, spenderAddress: MOCK_SPENDER, }; const result = schemas_1.ApproveSchema.safeParse(validInput); expect(result.success).toBe(true); expect(result.data).toEqual(validInput); }); it("should fail parsing empty input", () => { const emptyInput = {}; const result = schemas_1.ApproveSchema.safeParse(emptyInput); expect(result.success).toBe(false); }); it("should fail parsing invalid token address", () => { const invalidInput = { amount: MOCK_AMOUNT.toString(), tokenAddress: "invalid-address", spenderAddress: MOCK_SPENDER, }; const result = schemas_1.ApproveSchema.safeParse(invalidInput); expect(result.success).toBe(false); }); it("should fail parsing invalid spender address", () => { const invalidInput = { amount: MOCK_AMOUNT.toString(), tokenAddress: MOCK_CONTRACT_ADDRESS, spenderAddress: "invalid-address", }; const result = schemas_1.ApproveSchema.safeParse(invalidInput); expect(result.success).toBe(false); }); }); describe("Allowance Schema", () => { it("should successfully parse valid input", () => { const validInput = { tokenAddress: MOCK_CONTRACT_ADDRESS, spenderAddress: MOCK_SPENDER, }; const result = schemas_1.AllowanceSchema.safeParse(validInput); expect(result.success).toBe(true); expect(result.data).toEqual(validInput); }); it("should fail parsing empty input", () => { const emptyInput = {}; const result = schemas_1.AllowanceSchema.safeParse(emptyInput); expect(result.success).toBe(false); }); it("should fail parsing invalid token address", () => { const invalidInput = { tokenAddress: "invalid-address", spenderAddress: MOCK_SPENDER, }; const result = schemas_1.AllowanceSchema.safeParse(invalidInput); expect(result.success).toBe(false); }); it("should fail parsing invalid spender address", () => { const invalidInput = { tokenAddress: MOCK_CONTRACT_ADDRESS, spenderAddress: "invalid-address", }; const result = schemas_1.AllowanceSchema.safeParse(invalidInput); expect(result.success).toBe(false); }); }); describe("Approve Action", () => { const TRANSACTION_HASH = "0xapprove123456789"; let mockWallet; let mockMulticall; const actionProvider = (0, erc20ActionProvider_1.erc20ActionProvider)(); beforeEach(async () => { mockMulticall = jest.fn(); const mockPublicClient = { multicall: mockMulticall, getCode: jest.fn().mockResolvedValue("0x"), }; mockWallet = { sendTransaction: jest.fn(), waitForTransactionReceipt: jest.fn(), getName: jest.fn().mockReturnValue("evm_wallet_provider"), getNetwork: jest.fn().mockReturnValue({ networkId: "base-mainnet", }), getPublicClient: jest.fn().mockReturnValue(mockPublicClient), getAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), }; mockWallet.sendTransaction.mockResolvedValue(TRANSACTION_HASH); mockWallet.waitForTransactionReceipt.mockResolvedValue({}); }); it("should successfully approve tokens", async () => { mockMulticall.mockResolvedValueOnce([ { result: "MockToken" }, // name { result: MOCK_DECIMALS }, // decimals { result: BigInt(100000 * 10 ** MOCK_DECIMALS) }, // balance ]); const args = { amount: MOCK_AMOUNT.toString(), tokenAddress: MOCK_CONTRACT_ADDRESS, spenderAddress: MOCK_SPENDER, }; const response = await actionProvider.approve(mockWallet, args); expect(mockMulticall).toHaveBeenCalled(); expect(mockWallet.sendTransaction).toHaveBeenCalled(); expect(mockWallet.waitForTransactionReceipt).toHaveBeenCalledWith(TRANSACTION_HASH); expect(response).toContain(`Approved ${MOCK_AMOUNT} MockToken (${MOCK_CONTRACT_ADDRESS}) for spender ${MOCK_SPENDER}`); expect(response).toContain(`Transaction hash: ${TRANSACTION_HASH}`); }); it("should fail with an error when token details cannot be fetched", async () => { mockMulticall.mockRejectedValue(new Error("Failed to get token details")); const args = { amount: MOCK_AMOUNT.toString(), tokenAddress: MOCK_CONTRACT_ADDRESS, spenderAddress: MOCK_SPENDER, }; const response = await actionProvider.approve(mockWallet, args); expect(mockMulticall).toHaveBeenCalled(); expect(response).toContain("Error: Could not fetch token details"); }); it("should fail with an error when transaction fails", async () => { mockMulticall.mockResolvedValueOnce([ { result: "MockToken" }, // name { result: MOCK_DECIMALS }, // decimals { result: BigInt(100000 * 10 ** MOCK_DECIMALS) }, // balance ]); mockWallet.sendTransaction.mockRejectedValue(new Error("Transaction failed")); const args = { amount: MOCK_AMOUNT.toString(), tokenAddress: MOCK_CONTRACT_ADDRESS, spenderAddress: MOCK_SPENDER, }; const response = await actionProvider.approve(mockWallet, args); expect(response).toContain("Error approving tokens: Error: Transaction failed"); }); }); describe("Get Allowance Action", () => { let mockWallet; let mockMulticall; let mockReadContract; const actionProvider = (0, erc20ActionProvider_1.erc20ActionProvider)(); beforeEach(async () => { mockMulticall = jest.fn(); mockReadContract = jest.fn(); const mockPublicClient = { multicall: mockMulticall, getCode: jest.fn().mockResolvedValue("0x"), }; mockWallet = { readContract: mockReadContract, getName: jest.fn().mockReturnValue("evm_wallet_provider"), getNetwork: jest.fn().mockReturnValue({ networkId: "base-mainnet", }), getPublicClient: jest.fn().mockReturnValue(mockPublicClient), getAddress: jest.fn().mockReturnValue(MOCK_ADDRESS), }; }); it("should successfully get allowance", async () => { const allowanceAmount = BigInt(MOCK_AMOUNT * 10 ** MOCK_DECIMALS); mockMulticall.mockResolvedValueOnce([ { result: "MockToken" }, // name { result: MOCK_DECIMALS }, // decimals { result: BigInt(100000 * 10 ** MOCK_DECIMALS) }, // balance ]); mockReadContract.mockResolvedValue(allowanceAmount); const args = { tokenAddress: MOCK_CONTRACT_ADDRESS, spenderAddress: MOCK_SPENDER, }; const response = await actionProvider.getAllowance(mockWallet, args); expect(mockMulticall).toHaveBeenCalled(); expect(mockReadContract).toHaveBeenCalled(); expect(response).toContain(`Allowance for ${MOCK_SPENDER} to spend MockToken (${MOCK_CONTRACT_ADDRESS}) is ${MOCK_AMOUNT}`); }); it("should fail with an error when token details cannot be fetched", async () => { mockMulticall.mockRejectedValue(new Error("Failed to get token details")); const args = { tokenAddress: MOCK_CONTRACT_ADDRESS, spenderAddress: MOCK_SPENDER, }; const response = await actionProvider.getAllowance(mockWallet, args); expect(mockMulticall).toHaveBeenCalled(); expect(response).toContain("Error: Could not fetch token details"); }); it("should fail with an error when allowance read fails", async () => { mockMulticall.mockResolvedValueOnce([ { result: "MockToken" }, // name { result: MOCK_DECIMALS }, // decimals { result: BigInt(100000 * 10 ** MOCK_DECIMALS) }, // balance ]); mockReadContract.mockRejectedValue(new Error("Allowance read failed")); const args = { tokenAddress: MOCK_CONTRACT_ADDRESS, spenderAddress: MOCK_SPENDER, }; const response = await actionProvider.getAllowance(mockWallet, args); expect(response).toContain("Error checking allowance: Error: Allowance read failed"); }); }); describe("Get Token Address Action", () => { let mockWallet; const actionProvider = (0, erc20ActionProvider_1.erc20ActionProvider)(); beforeEach(() => { mockWallet = { getNetwork: jest.fn(), }; }); it("should return token address for valid symbol on base-mainnet", async () => { mockWallet.getNetwork.mockReturnValue({ protocolFamily: "evm", networkId: "base-mainnet", }); const response = await actionProvider.getTokenAddress(mockWallet, { symbol: "USDC" }); expect(response).toContain("Token address for USDC on base-mainnet: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"); }); it("should return token address for valid symbol on base-sepolia", async () => { mockWallet.getNetwork.mockReturnValue({ protocolFamily: "evm", networkId: "base-sepolia", }); const response = await actionProvider.getTokenAddress(mockWallet, { symbol: "EURC" }); expect(response).toContain("Token address for EURC on base-sepolia: 0x808456652fdb597867f38412077A9182bf77359F"); }); it("should return error for unsupported network", async () => { mockWallet.getNetwork.mockReturnValue({ protocolFamily: "evm", networkId: "unsupported-network", }); const response = await actionProvider.getTokenAddress(mockWallet, { symbol: "USDC" }); expect(response).toContain('Error: Token symbol "USDC" not found on unsupported-network'); }); it("should return error for unknown token symbol", async () => { mockWallet.getNetwork.mockReturnValue({ protocolFamily: "evm", networkId: "base-mainnet", }); const response = await actionProvider.getTokenAddress(mockWallet, { symbol: "UNKNOWN" }); expect(response).toContain('Error: Token symbol "UNKNOWN" not found on base-mainnet'); expect(response).toContain('Error: Token symbol "UNKNOWN" not found on base-mainnet'); }); it("should return error when network ID is not available", async () => { mockWallet.getNetwork.mockReturnValue({ protocolFamily: "evm", // networkId is undefined }); const response = await actionProvider.getTokenAddress(mockWallet, { symbol: "USDC" }); expect(response).toContain('Error: Token symbol "USDC" not found on undefined'); }); });