@coinbase/agentkit
Version:
Coinbase AgentKit core primitives
396 lines (395 loc) • 17.7 kB
JavaScript
"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');
});
});