@coinbase/agentkit
Version:
Coinbase AgentKit core primitives
797 lines (796 loc) • 31.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vaultsfyiActionProvider_1 = require("./vaultsfyiActionProvider");
const constants_1 = require("./constants");
const mockFetchResult = (status, data) => {
return {
json: async () => data,
status,
ok: status >= 200 && status < 300,
};
};
const mockVault = (num) => ({
apiResult: {
address: "0xB99B6dF96d4d5448cC0a5B3e0ef7896df9507Cf5",
network: {
name: "base",
chainId: 8453,
networkCaip: "eip155:8453",
},
asset: {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
assetCaip: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
name: "USD Coin",
symbol: "USDC",
decimals: 6,
assetLogo: "https://images.vaults.fyi/tokens/usdc.png",
assetPriceInUsd: "0.99983592",
assetGroup: "USD",
},
isTransactional: true,
isAppFeatured: true,
name: `vault-${num}`,
protocol: {
name: "40acres",
product: "",
version: "",
protocolLogo: "https://images.vaults.fyi/protocols/40acres.png",
},
lendUrl: "https://app.vaults.fyi/opportunity/base/0xB99B6dF96d4d5448cC0a5B3e0ef7896df9507Cf5",
description: `Description for vault-${num}`,
protocolVaultUrl: "https://www.40acres.finance/",
tags: ["Lending"],
holdersData: {
totalCount: num,
totalBalance: (num * 1000000).toString(),
topHolders: [
{
address: "0x1d59868D7767d703929393bDaB313302840f533c",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x1c6586f4895A569d9EFac5ABd231b79E0D47cAAD",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x011b0a055E02425461A1ae95B30F483c4fF05bE7",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x9551EeC2a31025D582Be358E05D88a9c95cAD86E",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x10076ed296571cE4Fde5b1FDF0eB9014a880e47B",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x9e33Fef28A75303B4FEB7b4c713c27Fed2AC78DD",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x166B9a0390474C455115dFb64579D1D79286588F",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x318962D28813fe14B9b6265fE2dAFB241C7F7777",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x22e4bB70905c7Ad93B65BC9Bd5B1b06e67378124",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x57609a91CC6eA77D9805137656A9308F5bF52f23",
lpTokenBalance: (num * 1000000).toString(),
},
],
},
apy: {
"1day": {
base: num,
reward: num,
total: num,
},
"7day": {
base: num,
reward: num,
total: num,
},
"30day": {
base: num,
reward: num,
total: num,
},
"1hour": {
base: num,
reward: num,
total: num,
},
},
tvl: {
usd: num.toString(),
native: (num * 1000000).toString(),
},
lastUpdateTimestamp: 1764259200,
rewards: [],
score: {
vaultScore: num,
vaultTvlScore: num,
protocolTvlScore: num,
holderScore: num,
networkScore: num,
assetScore: num,
},
lpToken: {
address: "0xB99B6dF96d4d5448cC0a5B3e0ef7896df9507Cf5",
tokenCaip: "eip155:8453/erc20:0xB99B6dF96d4d5448cC0a5B3e0ef7896df9507Cf5",
name: `Vault-${num}`,
symbol: `VAULT-${num}`,
decimals: 6,
},
transactionalProperties: {
depositStepsType: "instant",
redeemStepsType: "instant",
rewardsSupported: false,
},
warnings: [],
},
transformedResult: {
address: "0xB99B6dF96d4d5448cC0a5B3e0ef7896df9507Cf5",
network: {
name: "base",
chainId: 8453,
networkCaip: "eip155:8453",
},
asset: {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
assetCaip: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
name: "USD Coin",
symbol: "USDC",
decimals: 6,
assetLogo: "https://images.vaults.fyi/tokens/usdc.png",
assetPriceInUsd: "0.99983592",
assetGroup: "USD",
},
isTransactional: true,
isAppFeatured: true,
name: `vault-${num}`,
protocol: {
name: "40acres",
product: "",
version: "",
protocolLogo: "https://images.vaults.fyi/protocols/40acres.png",
},
lendUrl: "https://app.vaults.fyi/opportunity/base/0xB99B6dF96d4d5448cC0a5B3e0ef7896df9507Cf5",
description: `Description for vault-${num}`,
protocolVaultUrl: "https://www.40acres.finance/",
tags: ["Lending"],
holdersData: {
totalCount: num,
totalBalance: (num * 1000000).toString(),
topHolders: [
{
address: "0x1d59868D7767d703929393bDaB313302840f533c",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x1c6586f4895A569d9EFac5ABd231b79E0D47cAAD",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x011b0a055E02425461A1ae95B30F483c4fF05bE7",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x9551EeC2a31025D582Be358E05D88a9c95cAD86E",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x10076ed296571cE4Fde5b1FDF0eB9014a880e47B",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x9e33Fef28A75303B4FEB7b4c713c27Fed2AC78DD",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x166B9a0390474C455115dFb64579D1D79286588F",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x318962D28813fe14B9b6265fE2dAFB241C7F7777",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x22e4bB70905c7Ad93B65BC9Bd5B1b06e67378124",
lpTokenBalance: (num * 1000000).toString(),
},
{
address: "0x57609a91CC6eA77D9805137656A9308F5bF52f23",
lpTokenBalance: (num * 1000000).toString(),
},
],
},
apy: {
"1day": {
base: `${(num * 100).toFixed(2)}%`,
reward: `${(num * 100).toFixed(2)}%`,
total: `${(num * 100).toFixed(2)}%`,
},
"7day": {
base: `${(num * 100).toFixed(2)}%`,
reward: `${(num * 100).toFixed(2)}%`,
total: `${(num * 100).toFixed(2)}%`,
},
"30day": {
base: `${(num * 100).toFixed(2)}%`,
reward: `${(num * 100).toFixed(2)}%`,
total: `${(num * 100).toFixed(2)}%`,
},
"1hour": {
base: `${(num * 100).toFixed(2)}%`,
reward: `${(num * 100).toFixed(2)}%`,
total: `${(num * 100).toFixed(2)}%`,
},
},
tvl: {
usd: num.toString(),
native: (num * 1000000).toString(),
},
lastUpdateTimestamp: new Date(1764259200 * 1000).toISOString(),
rewards: [],
score: {
vaultScore: num,
vaultTvlScore: num,
protocolTvlScore: num,
holderScore: num,
networkScore: num,
assetScore: num,
},
lpToken: {
address: "0xB99B6dF96d4d5448cC0a5B3e0ef7896df9507Cf5",
tokenCaip: "eip155:8453/erc20:0xB99B6dF96d4d5448cC0a5B3e0ef7896df9507Cf5",
name: `Vault-${num}`,
symbol: `VAULT-${num}`,
decimals: 6,
},
transactionalProperties: {
depositStepsType: "instant",
redeemStepsType: "instant",
rewardsSupported: false,
},
warnings: [],
link: `https://app.vaults.fyi/opportunity/base/0xB99B6dF96d4d5448cC0a5B3e0ef7896df9507Cf5`,
},
});
const MOCK_TX_HASH = "0xmock-hash";
describe("VaultsfyiActionProvider", () => {
const provider = new vaultsfyiActionProvider_1.VaultsfyiActionProvider({ apiKey: "test-api-key" });
let mockWalletProvider;
let mockedFetch;
const originalFetch = global.fetch;
beforeAll(() => {
global.fetch = mockedFetch = jest.fn();
});
afterAll(() => {
global.fetch = originalFetch;
});
beforeEach(() => {
mockWalletProvider = {
getAddress: jest.fn(),
getBalance: jest.fn(),
getName: jest.fn(),
getNetwork: jest.fn().mockReturnValue({
protocolFamily: "evm",
networkId: "test-network",
}),
nativeTransfer: jest.fn(),
readContract: jest.fn(() => Promise.resolve(18)), // token decimals
sendTransaction: jest.fn(() => Promise.resolve(MOCK_TX_HASH)),
waitForTransactionReceipt: jest.fn(),
};
});
describe("network support", () => {
it("should support all vaultsfyi networks", () => {
constants_1.SUPPORTED_CHAIN_IDS.forEach(chainId => {
expect(provider.supportsNetwork({
protocolFamily: "evm",
chainId: chainId,
})).toBe(true);
});
});
it("should not support other protocol families", () => {
expect(provider.supportsNetwork({
protocolFamily: "evm",
chainId: "some-other-chain",
})).toBe(false);
});
it("should handle invalid network objects", () => {
expect(provider.supportsNetwork({})).toBe(false);
});
});
describe("vaults action", () => {
it("should return a transformed vault", async () => {
const mockedVault = mockVault(1);
mockedFetch.mockResolvedValue(mockFetchResult(200, { data: [mockedVault.apiResult] }));
const args = {};
const result = await provider.vaults(mockWalletProvider, args);
expect(JSON.parse(result)).toStrictEqual({
data: [mockedVault.transformedResult],
});
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
const args = {};
expect(await provider.vaults(mockWalletProvider, args)).toBe("Failed to fetch vaults: some more info");
});
});
describe("execute_step action", () => {
it("should execute deposit", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(200, {
currentActionIndex: 0,
actions: [
{
tx: {
to: "0x123",
data: "0x456",
value: "1",
chainId: 1,
},
description: "Deposit to vault",
},
],
}));
const args = {
vaultAddress: "0x123",
assetAddress: "0x456",
network: "mainnet",
amount: 1n,
action: "deposit",
};
const response = await provider.executeStep(mockWalletProvider, args);
expect(response).toBe("Successfully executed deposit step");
expect(mockWalletProvider.sendTransaction).toHaveBeenCalledWith({
to: "0x123",
data: "0x456",
value: 1n,
});
expect(mockWalletProvider.waitForTransactionReceipt).toHaveBeenCalledWith(MOCK_TX_HASH);
});
it("should execute multiple transactions", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(200, {
currentActionIndex: 0,
actions: [
{
tx: {
to: "0x123",
data: "0x456",
value: "1",
chainId: 1,
},
description: "Deposit to vault",
},
{
tx: {
to: "0x789",
data: "0xabc",
value: "2",
chainId: 1,
},
description: "Deposit to vault",
},
],
}));
const args = {
vaultAddress: "0x123",
assetAddress: "0x456",
network: "mainnet",
amount: 1n,
action: "deposit",
};
const response = await provider.executeStep(mockWalletProvider, args);
expect(response).toBe("Successfully executed deposit step");
expect(mockWalletProvider.sendTransaction).toHaveBeenCalledWith({
to: "0x123",
data: "0x456",
value: 1n,
});
expect(mockWalletProvider.sendTransaction).toHaveBeenCalledWith({
to: "0x789",
data: "0xabc",
value: 2n,
});
expect(mockWalletProvider.waitForTransactionReceipt).toHaveBeenCalledWith(MOCK_TX_HASH);
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
const args = {
vaultAddress: "0x123",
assetAddress: "0x456",
network: "mainnet",
amount: 1n,
action: "deposit",
};
expect(await provider.executeStep(mockWalletProvider, args)).toBe("Failed to execute step: some more info");
});
});
describe("user_idle_assets action", () => {
it("should return the response", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(200, {
mainnet: [
{
address: "0x123",
name: "token-1",
symbol: "T1",
balance: (10 ** 18).toString(),
decimals: 18,
},
],
}));
const response = await provider.idleAssets(mockWalletProvider);
expect(JSON.parse(response)).toStrictEqual({
mainnet: [
{
address: "0x123",
name: "token-1",
symbol: "T1",
balance: (10 ** 18).toString(),
decimals: 18,
},
],
});
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
expect(await provider.idleAssets(mockWalletProvider)).toBe("Failed to fetch idle assets: some more info");
});
});
describe("wallet positions action", () => {
it("should transform the response correctly", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(200, {
data: [
{
vaultName: "vault-1",
vaultAddress: "0x123",
asset: {
assetAddress: "0x456",
name: "token-1",
symbol: "T1",
decimals: 18,
},
balanceNative: (10 ** 18).toString(),
balanceLp: (10 ** 18).toString(),
unclaimedUsd: "100",
apy: {
base: 0.1,
reward: 0.1,
total: 0.1,
},
},
],
}));
const response = await provider.positions(mockWalletProvider);
expect(JSON.parse(response)).toStrictEqual({
data: [
{
vaultName: "vault-1",
vaultAddress: "0x123",
asset: {
assetAddress: "0x456",
name: "token-1",
symbol: "T1",
decimals: 18,
},
balanceNative: (10 ** 18).toString(),
balanceLp: (10 ** 18).toString(),
unclaimedUsd: "100",
apy: {
base: "10.00%",
reward: "10.00%",
total: "10.00%",
},
},
],
});
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
expect(await provider.positions(mockWalletProvider)).toBe("Failed to fetch positions: some more info");
});
});
describe("detailed_vault action", () => {
it("should return the response", async () => {
const detailedVault = mockVault(1);
mockedFetch.mockResolvedValue(mockFetchResult(200, detailedVault.apiResult));
const args = {
vaultAddress: "0x123456",
network: "mainnet",
};
const result = await provider.vaultDetails(mockWalletProvider, args);
const parsedResult = JSON.parse(result);
expect(parsedResult).toStrictEqual({
...detailedVault.transformedResult,
});
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
const args = {
vaultAddress: "0x123456",
network: "mainnet",
};
expect(await provider.vaultDetails(mockWalletProvider, args)).toBe("Failed to fetch vault: some more info");
});
});
describe("vault_historical_data action", () => {
it("transforms the response correctly", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(200, {
data: [
{
timestamp: 1704067200,
blockNumber: 12345678,
apy: {
base: 0.5,
reward: 0.3,
total: 0.8,
},
tvl: {
usd: 1000000,
native: 1000000,
},
},
],
}));
const args = {
vaultAddress: "0x123456",
network: "mainnet",
fromDate: "2024-01-01T00:00:00Z",
toDate: "2024-01-02T00:00:00Z",
granularity: "1hour",
apyInterval: "1day",
};
const result = await provider.vaultHistoricalData(mockWalletProvider, args);
const parsedResult = JSON.parse(result);
expect(parsedResult).toEqual({
data: [
{
timestamp: "2024-01-01T00:00:00.000Z",
blockNumber: 12345678,
apy: {
base: "50.00%",
reward: "30.00%",
total: "80.00%",
},
tvl: {
usd: 1000000,
native: 1000000,
},
},
],
});
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
const args = {
vaultAddress: "0x123456",
network: "mainnet",
fromDate: "2024-01-01T00:00:00Z",
toDate: "2024-01-02T00:00:00Z",
granularity: "1hour",
apyInterval: "1day",
};
expect(await provider.vaultHistoricalData(mockWalletProvider, args)).toBe("Failed to fetch vault historical data: some more info");
});
});
describe("rewards_context action", () => {
it("should return the response", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(200, {
data: [
{
some: "data",
},
],
}));
const response = await provider.rewardsContext(mockWalletProvider);
expect(JSON.parse(response)).toStrictEqual({ data: [{ some: "data" }] });
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
expect(await provider.rewardsContext(mockWalletProvider)).toBe("Failed to fetch rewards context: some more info");
});
});
describe("get_benchmark_apy action", () => {
it("should transform the response correctly", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(200, {
apy: {
"1day": {
base: 0.5,
reward: 0.3,
total: 0.8,
},
"7day": {
base: 0.5,
reward: 0.3,
total: 0.8,
},
"30day": {
base: 0.5,
reward: 0.3,
total: 0.8,
},
},
timestamp: 1704067200,
}));
const args = {
network: "mainnet",
benchmarkCode: "eth",
};
const result = await provider.benchmarkApy(mockWalletProvider, args);
const parsedResult = JSON.parse(result);
expect(parsedResult).toEqual({
apy: {
"1day": {
base: "50.00%",
reward: "30.00%",
total: "80.00%",
},
"7day": {
base: "50.00%",
reward: "30.00%",
total: "80.00%",
},
"30day": {
base: "50.00%",
reward: "30.00%",
total: "80.00%",
},
},
timestamp: new Date(1704067200 * 1000).toISOString(),
});
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
const args = {
network: "mainnet",
benchmarkCode: "eth",
};
expect(await provider.benchmarkApy(mockWalletProvider, args)).toBe("Failed to fetch benchmark: some more info");
});
});
describe("historical_benchmark_apy action", () => {
it("should transform the response correctly", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(200, {
data: [
{
timestamp: 1704067200,
apy: {
"1day": {
base: 0.5,
reward: 0.3,
total: 0.8,
},
"7day": {
base: 0.5,
reward: 0.3,
total: 0.8,
},
"30day": {
base: 0.5,
reward: 0.3,
total: 0.8,
},
},
},
],
}));
const args = {
network: "mainnet",
benchmarkCode: "eth",
fromDate: "2024-01-01T00:00:00Z",
toDate: "2024-01-02T00:00:00Z",
page: 1,
perPage: 10,
};
const result = await provider.historicalBenchmarkApy(mockWalletProvider, args);
const parsedResult = JSON.parse(result);
expect(parsedResult).toEqual({
data: [
{
timestamp: "2024-01-01T00:00:00.000Z",
apy: {
"1day": {
base: "50.00%",
reward: "30.00%",
total: "80.00%",
},
"7day": {
base: "50.00%",
reward: "30.00%",
total: "80.00%",
},
"30day": {
base: "50.00%",
reward: "30.00%",
total: "80.00%",
},
},
},
],
});
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
const args = {
network: "mainnet",
benchmarkCode: "eth",
fromDate: "2024-01-01T00:00:00Z",
toDate: "2024-01-02T00:00:00Z",
page: 1,
perPage: 10,
};
expect(await provider.historicalBenchmarkApy(mockWalletProvider, args)).toBe("Failed to fetch historical benchmark: some more info");
});
});
describe("total_vault_returns action", () => {
it("should return the response", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(200, {
data: {
totalReturns: 1000000,
},
}));
const args = {
vaultAddress: "0x123456",
network: "mainnet",
};
const result = await provider.totalVaultReturns(mockWalletProvider, args);
expect(JSON.parse(result)).toEqual({
data: {
totalReturns: 1000000,
},
});
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
const args = {
vaultAddress: "0x123456",
network: "mainnet",
};
expect(await provider.totalVaultReturns(mockWalletProvider, args)).toBe("Failed to fetch total vault returns: some more info");
});
});
describe("user_events action", () => {
it("should transform the response correctly", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(200, {
data: [
{
timestamp: 1704067200,
action: "deposit",
amount: 1000000,
},
],
}));
const args = {
vaultAddress: "0x123456",
network: "mainnet",
};
const result = await provider.userEvents(mockWalletProvider, args);
expect(JSON.parse(result)).toEqual({
data: [
{
timestamp: "2024-01-01T00:00:00.000Z",
action: "deposit",
amount: 1000000,
},
],
});
});
it("should return an error if the API request fails", async () => {
mockedFetch.mockResolvedValue(mockFetchResult(500, { error: "Internal Server Error", message: "some more info" }));
const args = {
vaultAddress: "0x123456",
network: "mainnet",
};
expect(await provider.userEvents(mockWalletProvider, args)).toBe("Failed to fetch user events: some more info");
});
});
});