Skip to content

Commit 3efd50d

Browse files
committed
added unit test for aptos plugin
1 parent 8fd2037 commit 3efd50d

File tree

3 files changed

+179
-19
lines changed

3 files changed

+179
-19
lines changed
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const APT_DECIMALS = 8;

packages/plugin-aptos/src/providers/wallet.ts

+74-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza";
1+
import {
2+
IAgentRuntime,
3+
ICacheManager,
4+
Memory,
5+
Provider,
6+
State,
7+
} from "@ai16z/eliza";
28
import {
39
Account,
410
Aptos,
@@ -10,6 +16,8 @@ import {
1016
} from "@aptos-labs/ts-sdk";
1117
import BigNumber from "bignumber.js";
1218
import NodeCache from "node-cache";
19+
import * as path from "path";
20+
import { APT_DECIMALS } from "../constants";
1321

1422
// Provider configuration
1523
const PROVIDER_CONFIG = {
@@ -19,7 +27,7 @@ const PROVIDER_CONFIG = {
1927

2028
interface WalletPortfolio {
2129
totalUsd: string;
22-
totalApt?: string;
30+
totalApt: string;
2331
}
2432

2533
interface Prices {
@@ -28,15 +36,56 @@ interface Prices {
2836

2937
export class WalletProvider {
3038
private cache: NodeCache;
39+
private cacheKey: string = "aptos/wallet";
3140

3241
constructor(
3342
private aptosClient: Aptos,
34-
private address: string
43+
private address: string,
44+
private cacheManager: ICacheManager
3545
) {
3646
this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes
3747
}
3848

39-
private async fetchAptPriceWithRetry() {
49+
private async readFromCache<T>(key: string): Promise<T | null> {
50+
const cached = await this.cacheManager.get<T>(
51+
path.join(this.cacheKey, key)
52+
);
53+
return cached;
54+
}
55+
56+
private async writeToCache<T>(key: string, data: T): Promise<void> {
57+
await this.cacheManager.set(path.join(this.cacheKey, key), data, {
58+
expires: Date.now() + 5 * 60 * 1000,
59+
});
60+
}
61+
62+
private async getCachedData<T>(key: string): Promise<T | null> {
63+
// Check in-memory cache first
64+
const cachedData = this.cache.get<T>(key);
65+
if (cachedData) {
66+
return cachedData;
67+
}
68+
69+
// Check file-based cache
70+
const fileCachedData = await this.readFromCache<T>(key);
71+
if (fileCachedData) {
72+
// Populate in-memory cache
73+
this.cache.set(key, fileCachedData);
74+
return fileCachedData;
75+
}
76+
77+
return null;
78+
}
79+
80+
private async setCachedData<T>(cacheKey: string, data: T): Promise<void> {
81+
// Set in-memory cache
82+
this.cache.set(cacheKey, data);
83+
84+
// Write to file-based cache
85+
await this.writeToCache(cacheKey, data);
86+
}
87+
88+
private async fetchPricesWithRetry() {
4089
let lastError: Error;
4190

4291
for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) {
@@ -77,19 +126,20 @@ export class WalletProvider {
77126
async fetchPortfolioValue(): Promise<WalletPortfolio> {
78127
try {
79128
const cacheKey = `portfolio-${this.address}`;
80-
const cachedValue = this.cache.get<WalletPortfolio>(cacheKey);
129+
const cachedValue =
130+
await this.getCachedData<WalletPortfolio>(cacheKey);
81131

82132
if (cachedValue) {
83-
console.log("Cache hit for fetchPortfolioValue");
133+
console.log("Cache hit for fetchPortfolioValue", cachedValue);
84134
return cachedValue;
85135
}
86136
console.log("Cache miss for fetchPortfolioValue");
87137

88-
const aptPrice = await this.fetchAptPrice().catch((error) => {
138+
const prices = await this.fetchPrices().catch((error) => {
89139
console.error("Error fetching APT price:", error);
90140
throw error;
91141
});
92-
const aptAmount = await this.aptosClient
142+
const aptAmountOnChain = await this.aptosClient
93143
.getAccountAPTAmount({
94144
accountAddress: this.address,
95145
})
@@ -98,32 +148,36 @@ export class WalletProvider {
98148
throw error;
99149
});
100150

101-
const totalUsd = new BigNumber(aptAmount).times(aptPrice.apt.usd);
151+
const aptAmount = new BigNumber(aptAmountOnChain).div(
152+
new BigNumber(10).pow(APT_DECIMALS)
153+
);
154+
const totalUsd = new BigNumber(aptAmount).times(prices.apt.usd);
102155

103156
const portfolio = {
104157
totalUsd: totalUsd.toString(),
105158
totalApt: aptAmount.toString(),
106159
};
107-
this.cache.set(cacheKey, portfolio);
160+
this.setCachedData(cacheKey, portfolio);
161+
console.log("Fetched portfolio:", portfolio);
108162
return portfolio;
109163
} catch (error) {
110164
console.error("Error fetching portfolio:", error);
111165
throw error;
112166
}
113167
}
114168

115-
async fetchAptPrice(): Promise<Prices> {
169+
async fetchPrices(): Promise<Prices> {
116170
try {
117171
const cacheKey = "prices";
118-
const cachedValue = this.cache.get<Prices>(cacheKey);
172+
const cachedValue = await this.getCachedData<Prices>(cacheKey);
119173

120174
if (cachedValue) {
121175
console.log("Cache hit for fetchPrices");
122176
return cachedValue;
123177
}
124178
console.log("Cache miss for fetchPrices");
125179

126-
const aptPriceData = await this.fetchAptPriceWithRetry().catch(
180+
const aptPriceData = await this.fetchPricesWithRetry().catch(
127181
(error) => {
128182
console.error("Error fetching APT price:", error);
129183
throw error;
@@ -132,7 +186,7 @@ export class WalletProvider {
132186
const prices: Prices = {
133187
apt: { usd: aptPriceData.pair.priceUsd },
134188
};
135-
this.cache.set(cacheKey, prices);
189+
this.setCachedData(cacheKey, prices);
136190
return prices;
137191
} catch (error) {
138192
console.error("Error fetching prices:", error);
@@ -141,13 +195,13 @@ export class WalletProvider {
141195
}
142196

143197
formatPortfolio(runtime, portfolio: WalletPortfolio): string {
144-
let output = `${runtime.character.description}\n`;
145-
output += `Wallet Address: ${this.address}\n\n`;
198+
let output = `${runtime.character.name}\n`;
199+
output += `Wallet Address: ${this.address}\n`;
146200

147201
const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2);
148-
const totalAptFormatted = portfolio.totalApt;
202+
const totalAptFormatted = new BigNumber(portfolio.totalApt).toFixed(4);
149203

150-
output += `Total Value: $${totalUsdFormatted} (${totalAptFormatted} APT)\n\n`;
204+
output += `Total Value: $${totalUsdFormatted} (${totalAptFormatted} APT)\n`;
151205

152206
return output;
153207
}
@@ -188,7 +242,8 @@ const walletProvider: Provider = {
188242
);
189243
const provider = new WalletProvider(
190244
aptosClient,
191-
aptosAccount.accountAddress.toStringLong()
245+
aptosAccount.accountAddress.toStringLong(),
246+
runtime.cacheManager
192247
);
193248
return await provider.getFormattedPortfolio(runtime);
194249
} catch (error) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
2+
import { WalletProvider } from "../providers/wallet.ts";
3+
import {
4+
Account,
5+
Aptos,
6+
AptosConfig,
7+
Ed25519PrivateKey,
8+
Network,
9+
PrivateKey,
10+
PrivateKeyVariants,
11+
} from "@aptos-labs/ts-sdk";
12+
import { defaultCharacter } from "@ai16z/eliza";
13+
import BigNumber from "bignumber.js";
14+
import { APT_DECIMALS } from "../constants.ts";
15+
16+
// Mock NodeCache
17+
vi.mock("node-cache", () => {
18+
return {
19+
default: vi.fn().mockImplementation(() => ({
20+
set: vi.fn(),
21+
get: vi.fn().mockReturnValue(null),
22+
})),
23+
};
24+
});
25+
26+
// Mock path module
27+
vi.mock("path", async () => {
28+
const actual = await vi.importActual("path");
29+
return {
30+
...actual,
31+
join: vi.fn().mockImplementation((...args) => args.join("/")),
32+
};
33+
});
34+
35+
// Mock the ICacheManager
36+
const mockCacheManager = {
37+
get: vi.fn().mockResolvedValue(null),
38+
set: vi.fn(),
39+
delete: vi.fn(),
40+
};
41+
42+
describe("WalletProvider", () => {
43+
let walletProvider;
44+
let mockedRuntime;
45+
46+
beforeEach(() => {
47+
vi.clearAllMocks();
48+
mockCacheManager.get.mockResolvedValue(null);
49+
50+
const aptosClient = new Aptos(
51+
new AptosConfig({
52+
network: Network.TESTNET,
53+
})
54+
);
55+
const aptosAccount = Account.fromPrivateKey({
56+
privateKey: new Ed25519PrivateKey(
57+
PrivateKey.formatPrivateKey(
58+
// this is a testnet private key
59+
"0x90e02bf2439492bd9be1ec5f569704accefd65ba88a89c4dcef1977e0203211e",
60+
PrivateKeyVariants.Ed25519
61+
)
62+
),
63+
});
64+
65+
// Create new instance of TokenProvider with mocked dependencies
66+
walletProvider = new WalletProvider(
67+
aptosClient,
68+
aptosAccount.accountAddress.toStringLong(),
69+
mockCacheManager
70+
);
71+
72+
mockedRuntime = {
73+
character: defaultCharacter,
74+
};
75+
});
76+
77+
afterEach(() => {
78+
vi.clearAllTimers();
79+
});
80+
81+
describe("Wallet Integration", () => {
82+
it("should check wallet address", async () => {
83+
const result =
84+
await walletProvider.getFormattedPortfolio(mockedRuntime);
85+
86+
const prices = await walletProvider.fetchPrices();
87+
const aptAmountOnChain =
88+
await walletProvider.aptosClient.getAccountAPTAmount({
89+
accountAddress: walletProvider.address,
90+
});
91+
const aptAmount = new BigNumber(aptAmountOnChain)
92+
.div(new BigNumber(10).pow(APT_DECIMALS))
93+
.toFixed(4);
94+
const totalUsd = new BigNumber(aptAmount)
95+
.times(prices.apt.usd)
96+
.toFixed(2);
97+
98+
expect(result).toEqual(
99+
`Eliza\nWallet Address: ${walletProvider.address}\n` +
100+
`Total Value: $${totalUsd} (${aptAmount} APT)\n`
101+
);
102+
});
103+
});
104+
});

0 commit comments

Comments
 (0)