@exagent/agent 0.1.32 → 0.1.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-EMQYMJZD.mjs +5854 -0
- package/dist/chunk-Q23UPFHO.mjs +5842 -0
- package/dist/cli.js +401 -98
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +90 -13
- package/dist/index.d.ts +90 -13
- package/dist/index.js +402 -99
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -32,13 +32,296 @@ var path2 = __toESM(require("path"));
|
|
|
32
32
|
|
|
33
33
|
// src/runtime.ts
|
|
34
34
|
var import_sdk = require("@exagent/sdk");
|
|
35
|
-
var
|
|
35
|
+
var import_viem7 = require("viem");
|
|
36
36
|
var import_chains4 = require("viem/chains");
|
|
37
37
|
var import_accounts5 = require("viem/accounts");
|
|
38
38
|
|
|
39
39
|
// src/trading/market.ts
|
|
40
|
+
var import_viem2 = require("viem");
|
|
41
|
+
|
|
42
|
+
// src/trading/token-resolver.ts
|
|
40
43
|
var import_viem = require("viem");
|
|
44
|
+
var STORE_KEY = "__token_metadata";
|
|
45
|
+
var UNRESOLVABLE_KEY = "__unresolvable_tokens";
|
|
46
|
+
var HARDCODED = {
|
|
47
|
+
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee": { decimals: 18, symbol: "ETH" },
|
|
48
|
+
// Core (0)
|
|
49
|
+
"0x4200000000000000000000000000000000000006": { decimals: 18, symbol: "WETH" },
|
|
50
|
+
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { decimals: 6, symbol: "USDC" },
|
|
51
|
+
"0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22": { decimals: 18, symbol: "cbETH" },
|
|
52
|
+
"0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf": { decimals: 8, symbol: "cbBTC" },
|
|
53
|
+
"0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452": { decimals: 18, symbol: "wstETH" },
|
|
54
|
+
"0x50c5725949a6f0c72e6c4a641f24049a917db0cb": { decimals: 18, symbol: "DAI" },
|
|
55
|
+
"0xfde4c96c8593536e31f229ea8f37b2ada2699bb2": { decimals: 6, symbol: "USDT" },
|
|
56
|
+
"0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": { decimals: 6, symbol: "USDbC" },
|
|
57
|
+
"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42": { decimals: 6, symbol: "EURC" },
|
|
58
|
+
"0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c": { decimals: 18, symbol: "rETH" },
|
|
59
|
+
"0x0555e30da8f98308edb960aa94c0db47230d2b9c": { decimals: 8, symbol: "WBTC" },
|
|
60
|
+
// Established (1)
|
|
61
|
+
"0x940181a94a35a4569e4529a3cdfb74e38fd98631": { decimals: 18, symbol: "AERO" },
|
|
62
|
+
"0x04c0599ae5a44757c0af6f9ec3b93da8976c150a": { decimals: 18, symbol: "weETH" },
|
|
63
|
+
"0x2416092f143378750bb29b79ed961ab195cceea5": { decimals: 18, symbol: "ezETH" },
|
|
64
|
+
"0xa88594d404727625a9437c3f886c7643872296ae": { decimals: 18, symbol: "WELL" },
|
|
65
|
+
"0xbaa5cc21fd487b8fcc2f632f3f4e8d37262a0842": { decimals: 18, symbol: "MORPHO" },
|
|
66
|
+
"0x88fb150bdc53a65fe94dea0c9ba0a6daf8c6e196": { decimals: 18, symbol: "LINK" },
|
|
67
|
+
"0xc3de830ea07524a0761646a6a4e4be0e114a3c83": { decimals: 18, symbol: "UNI" },
|
|
68
|
+
"0x63706e401c06ac8513145b7687a14804d17f814b": { decimals: 18, symbol: "AAVE" },
|
|
69
|
+
"0x9e1028f5f1d5ede59748ffcee5532509976840e0": { decimals: 18, symbol: "COMP" },
|
|
70
|
+
"0x4158734d47fc9692176b5085e0f52ee0da5d47f1": { decimals: 18, symbol: "BAL" },
|
|
71
|
+
"0x8ee73c484a26e0a5df2ee2a4960b789967dd0415": { decimals: 18, symbol: "CRV" },
|
|
72
|
+
"0x22e6966b799c4d5b13be962e1d117b56327fda66": { decimals: 18, symbol: "SNX" },
|
|
73
|
+
// Derivatives (2)
|
|
74
|
+
"0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b": { decimals: 18, symbol: "VIRTUAL" },
|
|
75
|
+
"0x4f9fd6be4a90f2620860d680c0d4d5fb53d1a825": { decimals: 18, symbol: "AIXBT" },
|
|
76
|
+
"0x4ed4e862860bed51a9570b96d89af5e1b0efefed": { decimals: 18, symbol: "DEGEN" },
|
|
77
|
+
"0x0578d8a44db98b23bf096a382e016e29a5ce0ffe": { decimals: 18, symbol: "HIGHER" },
|
|
78
|
+
"0x1bc0c42215582d5a085795f4badbaac3ff36d1bcb": { decimals: 18, symbol: "CLANKER" },
|
|
79
|
+
// Emerging (3)
|
|
80
|
+
"0x532f27101965dd16442e59d40670faf5ebb142e4": { decimals: 18, symbol: "BRETT" },
|
|
81
|
+
"0xac1bd2486aaf3b5c0fc3fd868558b082a531b2b4": { decimals: 18, symbol: "TOSHI" },
|
|
82
|
+
"0x6921b130d297cc43754afba22e5eac0fbf8db75b": { decimals: 18, symbol: "DOGINME" },
|
|
83
|
+
"0xb1a03eda10342529bbf8eb700a06c60441fef25d": { decimals: 18, symbol: "MIGGLES" },
|
|
84
|
+
"0x7f12d13b34f5f4f0a9449c16bcd42f0da47af200": { decimals: 9, symbol: "NORMIE" },
|
|
85
|
+
"0x27d2decb4bfc9c76f0309b8e88dec3a601fe25a8": { decimals: 18, symbol: "BALD" },
|
|
86
|
+
"0x768be13e1680b5ebe0024c42c896e3db59ec0149": { decimals: 9, symbol: "SKI" }
|
|
87
|
+
};
|
|
88
|
+
var TokenResolver = class {
|
|
89
|
+
client;
|
|
90
|
+
store;
|
|
91
|
+
cache = {};
|
|
92
|
+
unresolvable = /* @__PURE__ */ new Set();
|
|
93
|
+
constructor(client, store) {
|
|
94
|
+
this.client = client;
|
|
95
|
+
this.store = store || null;
|
|
96
|
+
for (const [addr, info] of Object.entries(HARDCODED)) {
|
|
97
|
+
this.cache[addr] = {
|
|
98
|
+
decimals: info.decimals,
|
|
99
|
+
symbol: info.symbol,
|
|
100
|
+
source: "hardcoded",
|
|
101
|
+
resolvedAt: 0
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (this.store) {
|
|
105
|
+
const saved = this.store.get(STORE_KEY);
|
|
106
|
+
if (saved) {
|
|
107
|
+
for (const [addr, meta] of Object.entries(saved)) {
|
|
108
|
+
const key = addr.toLowerCase();
|
|
109
|
+
if (!this.cache[key]) {
|
|
110
|
+
this.cache[key] = meta;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const savedUnresolvable = this.store.get(UNRESOLVABLE_KEY);
|
|
115
|
+
if (savedUnresolvable) {
|
|
116
|
+
for (const addr of savedUnresolvable) {
|
|
117
|
+
this.unresolvable.add(addr.toLowerCase());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get decimals for a token (synchronous).
|
|
124
|
+
* Returns undefined if the token has never been resolved and is not hardcoded.
|
|
125
|
+
* Call resolve() first for unknown tokens.
|
|
126
|
+
*/
|
|
127
|
+
getDecimals(address) {
|
|
128
|
+
const meta = this.cache[address.toLowerCase()];
|
|
129
|
+
return meta?.decimals;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get symbol for a token (synchronous).
|
|
133
|
+
*/
|
|
134
|
+
getSymbol(address) {
|
|
135
|
+
return this.cache[address.toLowerCase()]?.symbol;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Set symbol for a token (used when DexScreener returns symbol data).
|
|
139
|
+
*/
|
|
140
|
+
setSymbol(address, symbol) {
|
|
141
|
+
const key = address.toLowerCase();
|
|
142
|
+
if (this.cache[key]) {
|
|
143
|
+
if (!this.cache[key].symbol) {
|
|
144
|
+
this.cache[key].symbol = symbol;
|
|
145
|
+
this.persist();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Check if a token has been marked as unresolvable by all tiers.
|
|
151
|
+
*/
|
|
152
|
+
isUnresolvable(address) {
|
|
153
|
+
return this.unresolvable.has(address.toLowerCase());
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Batch-resolve metadata for a list of token addresses.
|
|
157
|
+
*
|
|
158
|
+
* Skips already-resolved tokens. For unknown tokens:
|
|
159
|
+
* 1. Multicall3 on-chain batch (decimals + symbol)
|
|
160
|
+
* 2. Blockscout API for any multicall failures
|
|
161
|
+
* 3. Mark remaining as unresolvable
|
|
162
|
+
*/
|
|
163
|
+
async resolve(addresses) {
|
|
164
|
+
const unknown = addresses.filter((addr) => {
|
|
165
|
+
const key = addr.toLowerCase();
|
|
166
|
+
return !this.cache[key] && !this.unresolvable.has(key);
|
|
167
|
+
});
|
|
168
|
+
if (unknown.length === 0) return;
|
|
169
|
+
const unique = [...new Set(unknown.map((a) => a.toLowerCase()))];
|
|
170
|
+
const stillUnresolved = await this.resolveViaMulticall(unique);
|
|
171
|
+
if (stillUnresolved.length > 0) {
|
|
172
|
+
const afterBlockscout = await this.resolveViaBlockscout(stillUnresolved);
|
|
173
|
+
if (afterBlockscout.length > 0) {
|
|
174
|
+
for (const addr of afterBlockscout) {
|
|
175
|
+
this.unresolvable.add(addr);
|
|
176
|
+
}
|
|
177
|
+
console.warn(
|
|
178
|
+
`TokenResolver: ${afterBlockscout.length} token(s) unresolvable after all tiers \u2014 skipping from portfolio: ${afterBlockscout.map((a) => a.slice(0, 10) + "...").join(", ")}`
|
|
179
|
+
);
|
|
180
|
+
this.persistUnresolvable();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Tier 2: Batch-fetch decimals + symbol via Multicall3.
|
|
186
|
+
* Returns addresses that failed (for next tier).
|
|
187
|
+
*/
|
|
188
|
+
async resolveViaMulticall(addresses) {
|
|
189
|
+
const failed = [];
|
|
190
|
+
try {
|
|
191
|
+
const contracts = addresses.flatMap((addr) => [
|
|
192
|
+
{
|
|
193
|
+
address: addr,
|
|
194
|
+
abi: import_viem.erc20Abi,
|
|
195
|
+
functionName: "decimals"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
address: addr,
|
|
199
|
+
abi: import_viem.erc20Abi,
|
|
200
|
+
functionName: "symbol"
|
|
201
|
+
}
|
|
202
|
+
]);
|
|
203
|
+
const BATCH_SIZE = 100;
|
|
204
|
+
const allResults = [];
|
|
205
|
+
for (let i = 0; i < contracts.length; i += BATCH_SIZE) {
|
|
206
|
+
const batch = contracts.slice(i, i + BATCH_SIZE);
|
|
207
|
+
try {
|
|
208
|
+
const results = await this.client.multicall({
|
|
209
|
+
contracts: batch,
|
|
210
|
+
allowFailure: true
|
|
211
|
+
});
|
|
212
|
+
allResults.push(...results);
|
|
213
|
+
} catch {
|
|
214
|
+
for (let j = 0; j < batch.length; j += 2) {
|
|
215
|
+
allResults.push({ status: "failure" }, { status: "failure" });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
220
|
+
const decimalsResult = allResults[i * 2];
|
|
221
|
+
const symbolResult = allResults[i * 2 + 1];
|
|
222
|
+
const addr = addresses[i];
|
|
223
|
+
if (decimalsResult?.status === "success" && decimalsResult.result !== void 0) {
|
|
224
|
+
const decimals = Number(decimalsResult.result);
|
|
225
|
+
const symbol = symbolResult?.status === "success" && symbolResult.result ? String(symbolResult.result) : void 0;
|
|
226
|
+
this.cache[addr] = {
|
|
227
|
+
decimals,
|
|
228
|
+
symbol,
|
|
229
|
+
source: "onchain",
|
|
230
|
+
resolvedAt: Date.now()
|
|
231
|
+
};
|
|
232
|
+
if (symbol) {
|
|
233
|
+
console.log(`TokenResolver: ${symbol} (${addr.slice(0, 10)}...) \u2192 ${decimals} decimals [onchain]`);
|
|
234
|
+
} else {
|
|
235
|
+
console.log(`TokenResolver: ${addr.slice(0, 10)}... \u2192 ${decimals} decimals [onchain]`);
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
failed.push(addr);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
this.persist();
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.warn("TokenResolver: Multicall3 failed entirely, trying Blockscout:", error instanceof Error ? error.message : error);
|
|
244
|
+
return addresses;
|
|
245
|
+
}
|
|
246
|
+
return failed;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Tier 3: Fetch token metadata from Blockscout (Base's block explorer API).
|
|
250
|
+
* Free, no API key, near-universal coverage for any token indexed by the explorer.
|
|
251
|
+
* Returns addresses that failed (truly unresolvable).
|
|
252
|
+
*/
|
|
253
|
+
async resolveViaBlockscout(addresses) {
|
|
254
|
+
const failed = [];
|
|
255
|
+
const CONCURRENCY = 5;
|
|
256
|
+
for (let i = 0; i < addresses.length; i += CONCURRENCY) {
|
|
257
|
+
const batch = addresses.slice(i, i + CONCURRENCY);
|
|
258
|
+
const results = await Promise.allSettled(
|
|
259
|
+
batch.map(async (addr) => {
|
|
260
|
+
const response = await fetch(
|
|
261
|
+
`https://base.blockscout.com/api/v2/tokens/${addr}`,
|
|
262
|
+
{ signal: AbortSignal.timeout(8e3) }
|
|
263
|
+
);
|
|
264
|
+
if (!response.ok) return null;
|
|
265
|
+
const data = await response.json();
|
|
266
|
+
if (data.decimals === null || data.decimals === void 0) return null;
|
|
267
|
+
const decimals = parseInt(data.decimals, 10);
|
|
268
|
+
if (isNaN(decimals)) return null;
|
|
269
|
+
return {
|
|
270
|
+
decimals,
|
|
271
|
+
symbol: data.symbol || data.name || void 0
|
|
272
|
+
};
|
|
273
|
+
})
|
|
274
|
+
);
|
|
275
|
+
for (let j = 0; j < batch.length; j++) {
|
|
276
|
+
const addr = batch[j];
|
|
277
|
+
const result = results[j];
|
|
278
|
+
if (result.status === "fulfilled" && result.value) {
|
|
279
|
+
this.cache[addr] = {
|
|
280
|
+
decimals: result.value.decimals,
|
|
281
|
+
symbol: result.value.symbol,
|
|
282
|
+
source: "blockscout",
|
|
283
|
+
resolvedAt: Date.now()
|
|
284
|
+
};
|
|
285
|
+
const sym = result.value.symbol || addr.slice(0, 10) + "...";
|
|
286
|
+
console.log(`TokenResolver: ${sym} \u2192 ${result.value.decimals} decimals [blockscout]`);
|
|
287
|
+
} else {
|
|
288
|
+
failed.push(addr);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (failed.length === 0 || addresses.length > failed.length) {
|
|
293
|
+
this.persist();
|
|
294
|
+
}
|
|
295
|
+
return failed;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Persist resolved metadata to FileStore.
|
|
299
|
+
*/
|
|
300
|
+
persist() {
|
|
301
|
+
if (!this.store) return;
|
|
302
|
+
const toSave = {};
|
|
303
|
+
for (const [addr, meta] of Object.entries(this.cache)) {
|
|
304
|
+
if (meta.source !== "hardcoded") {
|
|
305
|
+
toSave[addr] = meta;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
this.store.set(STORE_KEY, toSave);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Persist unresolvable token list to FileStore.
|
|
312
|
+
*/
|
|
313
|
+
persistUnresolvable() {
|
|
314
|
+
if (!this.store) return;
|
|
315
|
+
this.store.set(UNRESOLVABLE_KEY, [...this.unresolvable]);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// src/trading/market.ts
|
|
41
320
|
var NATIVE_ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
321
|
+
var _globalResolver = null;
|
|
322
|
+
function setGlobalResolver(resolver) {
|
|
323
|
+
_globalResolver = resolver;
|
|
324
|
+
}
|
|
42
325
|
var TOKEN_DECIMALS = {
|
|
43
326
|
[NATIVE_ETH.toLowerCase()]: 18,
|
|
44
327
|
// Native ETH
|
|
@@ -117,54 +400,19 @@ var TOKEN_DECIMALS = {
|
|
|
117
400
|
"0x768be13e1680b5ebe0024c42c896e3db59ec0149": 9
|
|
118
401
|
// SKI
|
|
119
402
|
};
|
|
120
|
-
var decimalsCache = {};
|
|
121
|
-
var symbolCache = {};
|
|
122
403
|
function getTokenDecimals(address) {
|
|
123
404
|
const key = address.toLowerCase();
|
|
405
|
+
if (_globalResolver) {
|
|
406
|
+
const d = _globalResolver.getDecimals(key);
|
|
407
|
+
if (d !== void 0) return d;
|
|
408
|
+
if (_globalResolver.isUnresolvable(key)) return 18;
|
|
409
|
+
}
|
|
124
410
|
const known = TOKEN_DECIMALS[key];
|
|
125
411
|
if (known !== void 0) return known;
|
|
126
|
-
const cached = decimalsCache[key];
|
|
127
|
-
if (cached !== void 0) return cached;
|
|
128
|
-
console.warn(`Unknown token decimals for ${address}, defaulting to 18. Call fetchTokenDecimals() first for accuracy.`);
|
|
129
412
|
return 18;
|
|
130
413
|
}
|
|
131
414
|
function getTokenSymbol(address) {
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
async function fetchTokenDecimals(client, address) {
|
|
135
|
-
const key = address.toLowerCase();
|
|
136
|
-
const known = TOKEN_DECIMALS[key];
|
|
137
|
-
if (known !== void 0) return known;
|
|
138
|
-
const cached = decimalsCache[key];
|
|
139
|
-
if (cached !== void 0) return cached;
|
|
140
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
141
|
-
try {
|
|
142
|
-
const [decimals, symbol] = await Promise.all([
|
|
143
|
-
client.readContract({
|
|
144
|
-
address,
|
|
145
|
-
abi: import_viem.erc20Abi,
|
|
146
|
-
functionName: "decimals"
|
|
147
|
-
}),
|
|
148
|
-
client.readContract({
|
|
149
|
-
address,
|
|
150
|
-
abi: import_viem.erc20Abi,
|
|
151
|
-
functionName: "symbol"
|
|
152
|
-
}).catch(() => void 0)
|
|
153
|
-
// symbol is optional — some tokens don't have it
|
|
154
|
-
]);
|
|
155
|
-
const result = Number(decimals);
|
|
156
|
-
decimalsCache[key] = result;
|
|
157
|
-
if (symbol) symbolCache[key] = String(symbol);
|
|
158
|
-
return result;
|
|
159
|
-
} catch {
|
|
160
|
-
if (attempt === 0) {
|
|
161
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
console.warn(`Failed to fetch decimals for ${address} after 2 attempts, defaulting to 18`);
|
|
166
|
-
decimalsCache[key] = 18;
|
|
167
|
-
return 18;
|
|
415
|
+
return _globalResolver?.getSymbol(address.toLowerCase());
|
|
168
416
|
}
|
|
169
417
|
var TOKEN_TO_COINGECKO = {
|
|
170
418
|
[NATIVE_ETH.toLowerCase()]: "ethereum",
|
|
@@ -248,24 +496,31 @@ var PRICE_STALENESS_MS = 6e4;
|
|
|
248
496
|
var MarketDataService = class {
|
|
249
497
|
rpcUrl;
|
|
250
498
|
client;
|
|
499
|
+
resolver;
|
|
251
500
|
/** Cached prices from last fetch */
|
|
252
501
|
cachedPrices = {};
|
|
253
502
|
/** Timestamp of last successful price fetch */
|
|
254
503
|
lastPriceFetchAt = 0;
|
|
255
|
-
constructor(rpcUrl) {
|
|
256
|
-
this.rpcUrl = rpcUrl;
|
|
257
|
-
this.client = (0, import_viem.createPublicClient)({
|
|
258
|
-
transport: (0, import_viem.http)(rpcUrl, { timeout: 6e4 })
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
504
|
/** Cached volume data */
|
|
262
505
|
cachedVolume24h = {};
|
|
263
506
|
/** Cached price change data */
|
|
264
507
|
cachedPriceChange24h = {};
|
|
508
|
+
constructor(rpcUrl, store) {
|
|
509
|
+
this.rpcUrl = rpcUrl;
|
|
510
|
+
this.client = (0, import_viem2.createPublicClient)({
|
|
511
|
+
transport: (0, import_viem2.http)(rpcUrl, { timeout: 6e4 })
|
|
512
|
+
});
|
|
513
|
+
this.resolver = new TokenResolver(this.client, store);
|
|
514
|
+
}
|
|
515
|
+
/** Get the underlying TokenResolver for direct access */
|
|
516
|
+
getResolver() {
|
|
517
|
+
return this.resolver;
|
|
518
|
+
}
|
|
265
519
|
/**
|
|
266
520
|
* Fetch current market data for the agent
|
|
267
521
|
*/
|
|
268
522
|
async fetchMarketData(walletAddress, tokenAddresses) {
|
|
523
|
+
await this.resolver.resolve(tokenAddresses);
|
|
269
524
|
const prices = await this.fetchPrices(tokenAddresses);
|
|
270
525
|
const balances = await this.fetchBalances(walletAddress, tokenAddresses);
|
|
271
526
|
const portfolioValue = this.calculatePortfolioValue(balances, prices);
|
|
@@ -402,14 +657,14 @@ var MarketDataService = class {
|
|
|
402
657
|
const liq = pair.liquidity?.usd || 0;
|
|
403
658
|
if (price > 0 && (!bestPrices[addr] || liq > bestPrices[addr].liquidity)) {
|
|
404
659
|
bestPrices[addr] = { price, liquidity: liq };
|
|
405
|
-
if (pair.baseToken.symbol
|
|
406
|
-
|
|
660
|
+
if (pair.baseToken.symbol) {
|
|
661
|
+
this.resolver.setSymbol(addr, pair.baseToken.symbol);
|
|
407
662
|
}
|
|
408
663
|
}
|
|
409
664
|
}
|
|
410
665
|
for (const [addr, { price }] of Object.entries(bestPrices)) {
|
|
411
666
|
prices[addr] = price;
|
|
412
|
-
const sym =
|
|
667
|
+
const sym = this.resolver.getSymbol(addr) || addr.slice(0, 10);
|
|
413
668
|
console.log(`DexScreener price for ${sym}: $${price}`);
|
|
414
669
|
}
|
|
415
670
|
}
|
|
@@ -426,7 +681,7 @@ var MarketDataService = class {
|
|
|
426
681
|
}
|
|
427
682
|
for (const addr of tokenAddresses) {
|
|
428
683
|
if (!prices[addr.toLowerCase()]) {
|
|
429
|
-
const sym =
|
|
684
|
+
const sym = this.resolver.getSymbol(addr.toLowerCase());
|
|
430
685
|
console.warn(`No price available for ${sym ? `${sym} (${addr})` : addr} \u2014 CoinGecko, DeFi Llama, and DexScreener all returned nothing`);
|
|
431
686
|
prices[addr.toLowerCase()] = 0;
|
|
432
687
|
}
|
|
@@ -434,36 +689,45 @@ var MarketDataService = class {
|
|
|
434
689
|
return prices;
|
|
435
690
|
}
|
|
436
691
|
/**
|
|
437
|
-
* Fetch real on-chain balances: native ETH + ERC-20 tokens
|
|
692
|
+
* Fetch real on-chain balances: native ETH + ERC-20 tokens.
|
|
693
|
+
* Uses Multicall3 to batch all balanceOf calls into a single RPC request.
|
|
438
694
|
*/
|
|
439
695
|
async fetchBalances(walletAddress, tokenAddresses) {
|
|
440
696
|
const balances = {};
|
|
441
697
|
const wallet = walletAddress;
|
|
442
698
|
try {
|
|
443
|
-
const unknownTokens = tokenAddresses.filter(
|
|
444
|
-
(addr) => TOKEN_DECIMALS[addr.toLowerCase()] === void 0 && !decimalsCache[addr.toLowerCase()]
|
|
445
|
-
);
|
|
446
|
-
if (unknownTokens.length > 0) {
|
|
447
|
-
await Promise.all(unknownTokens.map((addr) => fetchTokenDecimals(this.client, addr)));
|
|
448
|
-
}
|
|
449
699
|
const nativeBalance = await this.client.getBalance({ address: wallet });
|
|
450
700
|
balances[NATIVE_ETH.toLowerCase()] = nativeBalance;
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
701
|
+
if (tokenAddresses.length > 0) {
|
|
702
|
+
const BALANCE_BATCH_SIZE = 25;
|
|
703
|
+
for (let i = 0; i < tokenAddresses.length; i += BALANCE_BATCH_SIZE) {
|
|
704
|
+
const batch = tokenAddresses.slice(i, i + BALANCE_BATCH_SIZE);
|
|
705
|
+
try {
|
|
706
|
+
const results = await this.client.multicall({
|
|
707
|
+
contracts: batch.map((addr) => ({
|
|
708
|
+
address: addr,
|
|
709
|
+
abi: import_viem2.erc20Abi,
|
|
710
|
+
functionName: "balanceOf",
|
|
711
|
+
args: [wallet]
|
|
712
|
+
})),
|
|
713
|
+
allowFailure: true
|
|
714
|
+
});
|
|
715
|
+
for (let j = 0; j < batch.length; j++) {
|
|
716
|
+
const addr = batch[j].toLowerCase();
|
|
717
|
+
const r = results[j];
|
|
718
|
+
if (r.status === "success") {
|
|
719
|
+
balances[addr] = r.result;
|
|
720
|
+
} else {
|
|
721
|
+
const sym = this.resolver.getSymbol(addr) || addr.slice(0, 10);
|
|
722
|
+
console.warn(` balanceOf reverted for ${sym}`);
|
|
723
|
+
balances[addr] = 0n;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
} catch (e) {
|
|
727
|
+
console.warn(`Multicall3 balanceOf batch failed (${batch.length} tokens): ${e instanceof Error ? e.message : String(e)}`);
|
|
728
|
+
await this.fetchBalancesIndividual(wallet, batch, balances);
|
|
729
|
+
}
|
|
462
730
|
}
|
|
463
|
-
});
|
|
464
|
-
const results = await Promise.all(erc20Promises);
|
|
465
|
-
for (const { address, balance } of results) {
|
|
466
|
-
balances[address] = balance;
|
|
467
731
|
}
|
|
468
732
|
} catch (error) {
|
|
469
733
|
console.error("MarketData: Failed to fetch balances:", error instanceof Error ? error.message : error);
|
|
@@ -475,13 +739,44 @@ var MarketDataService = class {
|
|
|
475
739
|
return balances;
|
|
476
740
|
}
|
|
477
741
|
/**
|
|
478
|
-
*
|
|
742
|
+
* Fallback: fetch ERC-20 balances individually (if multicall fails).
|
|
743
|
+
*/
|
|
744
|
+
async fetchBalancesIndividual(wallet, tokenAddresses, balances) {
|
|
745
|
+
const promises = tokenAddresses.map(async (tokenAddress) => {
|
|
746
|
+
try {
|
|
747
|
+
const balance = await this.client.readContract({
|
|
748
|
+
address: tokenAddress,
|
|
749
|
+
abi: import_viem2.erc20Abi,
|
|
750
|
+
functionName: "balanceOf",
|
|
751
|
+
args: [wallet]
|
|
752
|
+
});
|
|
753
|
+
return { address: tokenAddress.toLowerCase(), balance };
|
|
754
|
+
} catch (e) {
|
|
755
|
+
const sym = this.resolver.getSymbol(tokenAddress.toLowerCase()) || tokenAddress.slice(0, 10);
|
|
756
|
+
console.warn(` balanceOf failed for ${sym}: ${e instanceof Error ? e.message : String(e)}`);
|
|
757
|
+
return { address: tokenAddress.toLowerCase(), balance: 0n };
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
const results = await Promise.all(promises);
|
|
761
|
+
for (const { address, balance } of results) {
|
|
762
|
+
balances[address] = balance;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Calculate total portfolio value in USD.
|
|
767
|
+
* Skips tokens with unresolvable decimals to avoid corrupted calculations.
|
|
479
768
|
*/
|
|
480
769
|
calculatePortfolioValue(balances, prices) {
|
|
481
770
|
let total = 0;
|
|
482
771
|
for (const [address, balance] of Object.entries(balances)) {
|
|
483
772
|
const price = prices[address.toLowerCase()] || 0;
|
|
484
|
-
|
|
773
|
+
if (price === 0) continue;
|
|
774
|
+
const key = address.toLowerCase();
|
|
775
|
+
const resolverDecimals = _globalResolver?.getDecimals(key);
|
|
776
|
+
if (resolverDecimals === void 0 && _globalResolver?.isUnresolvable(key)) {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
const decimals = resolverDecimals ?? getTokenDecimals(address);
|
|
485
780
|
const amount = Number(balance) / Math.pow(10, decimals);
|
|
486
781
|
total += amount * price;
|
|
487
782
|
}
|
|
@@ -2166,7 +2461,7 @@ var FileStore = class {
|
|
|
2166
2461
|
};
|
|
2167
2462
|
|
|
2168
2463
|
// src/vault/manager.ts
|
|
2169
|
-
var
|
|
2464
|
+
var import_viem3 = require("viem");
|
|
2170
2465
|
var import_accounts = require("viem/accounts");
|
|
2171
2466
|
var import_chains = require("viem/chains");
|
|
2172
2467
|
var ADDRESSES = {
|
|
@@ -2257,12 +2552,12 @@ var VaultManager = class {
|
|
|
2257
2552
|
this.account = (0, import_accounts.privateKeyToAccount)(config.walletKey);
|
|
2258
2553
|
this.chain = import_chains.base;
|
|
2259
2554
|
const rpcUrl = getRpcUrl();
|
|
2260
|
-
const transport = (0,
|
|
2261
|
-
this.publicClient = (0,
|
|
2555
|
+
const transport = (0, import_viem3.http)(rpcUrl, { timeout: 6e4 });
|
|
2556
|
+
this.publicClient = (0, import_viem3.createPublicClient)({
|
|
2262
2557
|
chain: this.chain,
|
|
2263
2558
|
transport
|
|
2264
2559
|
});
|
|
2265
|
-
this.walletClient = (0,
|
|
2560
|
+
this.walletClient = (0, import_viem3.createWalletClient)({
|
|
2266
2561
|
account: this.account,
|
|
2267
2562
|
chain: this.chain,
|
|
2268
2563
|
transport
|
|
@@ -2648,7 +2943,7 @@ var HyperliquidClient = class {
|
|
|
2648
2943
|
};
|
|
2649
2944
|
|
|
2650
2945
|
// src/perp/signer.ts
|
|
2651
|
-
var
|
|
2946
|
+
var import_viem4 = require("viem");
|
|
2652
2947
|
var HYPERLIQUID_DOMAIN = {
|
|
2653
2948
|
name: "HyperliquidSignTransaction",
|
|
2654
2949
|
version: "1",
|
|
@@ -2722,7 +3017,7 @@ function fillHashToBytes32(fillHash) {
|
|
|
2722
3017
|
if (fillHash.startsWith("0x") && fillHash.length === 66) {
|
|
2723
3018
|
return fillHash;
|
|
2724
3019
|
}
|
|
2725
|
-
return (0,
|
|
3020
|
+
return (0, import_viem4.keccak256)((0, import_viem4.encodePacked)(["string"], [fillHash]));
|
|
2726
3021
|
}
|
|
2727
3022
|
|
|
2728
3023
|
// src/perp/orders.ts
|
|
@@ -3324,7 +3619,7 @@ var HyperliquidWebSocket = class {
|
|
|
3324
3619
|
};
|
|
3325
3620
|
|
|
3326
3621
|
// src/perp/recorder.ts
|
|
3327
|
-
var
|
|
3622
|
+
var import_viem5 = require("viem");
|
|
3328
3623
|
var import_chains2 = require("viem/chains");
|
|
3329
3624
|
var import_accounts2 = require("viem/accounts");
|
|
3330
3625
|
var ROUTER_ADDRESS = "0x1BCFa13f677fDCf697D8b7d5120f544817F1de1A";
|
|
@@ -3367,12 +3662,12 @@ var PerpTradeRecorder = class {
|
|
|
3367
3662
|
this.configHash = opts.configHash;
|
|
3368
3663
|
this.account = (0, import_accounts2.privateKeyToAccount)(opts.privateKey);
|
|
3369
3664
|
const rpcUrl = opts.rpcUrl || "https://mainnet.base.org";
|
|
3370
|
-
const transport = (0,
|
|
3371
|
-
this.publicClient = (0,
|
|
3665
|
+
const transport = (0, import_viem5.http)(rpcUrl, { timeout: 6e4 });
|
|
3666
|
+
this.publicClient = (0, import_viem5.createPublicClient)({
|
|
3372
3667
|
chain: import_chains2.base,
|
|
3373
3668
|
transport
|
|
3374
3669
|
});
|
|
3375
|
-
this.walletClient = (0,
|
|
3670
|
+
this.walletClient = (0, import_viem5.createWalletClient)({
|
|
3376
3671
|
chain: import_chains2.base,
|
|
3377
3672
|
transport,
|
|
3378
3673
|
account: this.account
|
|
@@ -3680,23 +3975,23 @@ var PerpOnboarding = class {
|
|
|
3680
3975
|
};
|
|
3681
3976
|
|
|
3682
3977
|
// src/perp/funding.ts
|
|
3683
|
-
var
|
|
3978
|
+
var import_viem6 = require("viem");
|
|
3684
3979
|
var import_chains3 = require("viem/chains");
|
|
3685
3980
|
var import_accounts3 = require("viem/accounts");
|
|
3686
|
-
var ERC20_ABI = (0,
|
|
3981
|
+
var ERC20_ABI = (0, import_viem6.parseAbi)([
|
|
3687
3982
|
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
3688
3983
|
"function balanceOf(address account) external view returns (uint256)",
|
|
3689
3984
|
"function allowance(address owner, address spender) external view returns (uint256)"
|
|
3690
3985
|
]);
|
|
3691
|
-
var TOKEN_MESSENGER_V2_ABI = (0,
|
|
3986
|
+
var TOKEN_MESSENGER_V2_ABI = (0, import_viem6.parseAbi)([
|
|
3692
3987
|
"function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken) external returns (uint64 nonce)",
|
|
3693
3988
|
"event DepositForBurn(uint64 indexed nonce, address indexed burnToken, uint256 amount, address indexed depositor, bytes32 mintRecipient, uint32 destinationDomain, bytes32 destinationTokenMessenger, bytes32 destinationCaller)"
|
|
3694
3989
|
]);
|
|
3695
|
-
var MESSAGE_TRANSMITTER_V2_ABI = (0,
|
|
3990
|
+
var MESSAGE_TRANSMITTER_V2_ABI = (0, import_viem6.parseAbi)([
|
|
3696
3991
|
"function receiveMessage(bytes message, bytes attestation) external returns (bool success)",
|
|
3697
3992
|
"event MessageSent(bytes message)"
|
|
3698
3993
|
]);
|
|
3699
|
-
var CORE_DEPOSIT_WALLET_ABI = (0,
|
|
3994
|
+
var CORE_DEPOSIT_WALLET_ABI = (0, import_viem6.parseAbi)([
|
|
3700
3995
|
"function deposit(uint256 amount, uint32 destinationDex) external"
|
|
3701
3996
|
]);
|
|
3702
3997
|
|
|
@@ -3885,7 +4180,7 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
3885
4180
|
}
|
|
3886
4181
|
|
|
3887
4182
|
// src/index.ts
|
|
3888
|
-
var AGENT_VERSION = "0.1.
|
|
4183
|
+
var AGENT_VERSION = "0.1.34";
|
|
3889
4184
|
|
|
3890
4185
|
// src/relay.ts
|
|
3891
4186
|
var RelayClient = class {
|
|
@@ -4237,7 +4532,8 @@ var AgentRuntime = class {
|
|
|
4237
4532
|
this.positionTracker = new PositionTracker(store, { maxTradeHistory: 50 });
|
|
4238
4533
|
this.executor = new TradeExecutor(this.client, this.config, () => this.getConfigHash());
|
|
4239
4534
|
this.riskManager = new RiskManager(this.config.trading);
|
|
4240
|
-
this.marketData = new MarketDataService(this.getRpcUrl());
|
|
4535
|
+
this.marketData = new MarketDataService(this.getRpcUrl(), store);
|
|
4536
|
+
setGlobalResolver(this.marketData.getResolver());
|
|
4241
4537
|
const savedRisk = this.positionTracker.getRiskState();
|
|
4242
4538
|
if (savedRisk.lastResetDate) {
|
|
4243
4539
|
this.riskManager.restoreState(savedRisk);
|
|
@@ -4334,9 +4630,9 @@ var AgentRuntime = class {
|
|
|
4334
4630
|
this.perpClient = new HyperliquidClient(config);
|
|
4335
4631
|
const perpKey = perpConfig.perpRelayerKey || this.config.privateKey;
|
|
4336
4632
|
const account = (0, import_accounts5.privateKeyToAccount)(perpKey);
|
|
4337
|
-
const walletClient = (0,
|
|
4633
|
+
const walletClient = (0, import_viem7.createWalletClient)({
|
|
4338
4634
|
chain: { id: 42161, name: "Arbitrum", nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, rpcUrls: { default: { http: ["https://arb1.arbitrum.io/rpc"] } } },
|
|
4339
|
-
transport: (0,
|
|
4635
|
+
transport: (0, import_viem7.http)("https://arb1.arbitrum.io/rpc", { timeout: 6e4 }),
|
|
4340
4636
|
account
|
|
4341
4637
|
});
|
|
4342
4638
|
this.perpSigner = new HyperliquidSigner(walletClient);
|
|
@@ -4576,9 +4872,9 @@ var AgentRuntime = class {
|
|
|
4576
4872
|
const message = error instanceof Error ? error.message : String(error);
|
|
4577
4873
|
if (message.includes("insufficient funds") || message.includes("intrinsic gas too low") || message.includes("exceeds the balance")) {
|
|
4578
4874
|
const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
|
|
4579
|
-
const publicClientInstance = (0,
|
|
4875
|
+
const publicClientInstance = (0, import_viem7.createPublicClient)({
|
|
4580
4876
|
chain: import_chains4.base,
|
|
4581
|
-
transport: (0,
|
|
4877
|
+
transport: (0, import_viem7.http)(this.getRpcUrl(), { timeout: 6e4 })
|
|
4582
4878
|
});
|
|
4583
4879
|
console.log("");
|
|
4584
4880
|
console.log("=== ETH NEEDED FOR GAS ===");
|
|
@@ -5330,7 +5626,14 @@ var AgentRuntime = class {
|
|
|
5330
5626
|
if (extras.length > 0) {
|
|
5331
5627
|
console.log(`Auto-tracking ${extras.length} additional token(s) from position history`);
|
|
5332
5628
|
}
|
|
5333
|
-
|
|
5629
|
+
const resolver = this.marketData.getResolver();
|
|
5630
|
+
const allTokens = [...base5, ...extras];
|
|
5631
|
+
const filtered = allTokens.filter((t) => !resolver.isUnresolvable(t.toLowerCase()));
|
|
5632
|
+
const dropped = allTokens.length - filtered.length;
|
|
5633
|
+
if (dropped > 0) {
|
|
5634
|
+
console.log(`Skipping ${dropped} unresolvable token(s)`);
|
|
5635
|
+
}
|
|
5636
|
+
return filtered;
|
|
5334
5637
|
}
|
|
5335
5638
|
/**
|
|
5336
5639
|
* Default tokens to track.
|