@exagent/agent 0.1.31 → 0.1.33
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-BAOLHULW.mjs +5551 -0
- package/dist/chunk-Q23UPFHO.mjs +5842 -0
- package/dist/cli.js +402 -96
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +88 -11
- package/dist/index.d.ts +88 -11
- package/dist/index.js +403 -97
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -85,13 +85,296 @@ module.exports = __toCommonJS(index_exports);
|
|
|
85
85
|
|
|
86
86
|
// src/runtime.ts
|
|
87
87
|
var import_sdk = require("@exagent/sdk");
|
|
88
|
-
var
|
|
88
|
+
var import_viem7 = require("viem");
|
|
89
89
|
var import_chains4 = require("viem/chains");
|
|
90
90
|
var import_accounts5 = require("viem/accounts");
|
|
91
91
|
|
|
92
92
|
// src/trading/market.ts
|
|
93
|
+
var import_viem2 = require("viem");
|
|
94
|
+
|
|
95
|
+
// src/trading/token-resolver.ts
|
|
93
96
|
var import_viem = require("viem");
|
|
97
|
+
var STORE_KEY = "__token_metadata";
|
|
98
|
+
var UNRESOLVABLE_KEY = "__unresolvable_tokens";
|
|
99
|
+
var HARDCODED = {
|
|
100
|
+
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee": { decimals: 18, symbol: "ETH" },
|
|
101
|
+
// Core (0)
|
|
102
|
+
"0x4200000000000000000000000000000000000006": { decimals: 18, symbol: "WETH" },
|
|
103
|
+
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { decimals: 6, symbol: "USDC" },
|
|
104
|
+
"0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22": { decimals: 18, symbol: "cbETH" },
|
|
105
|
+
"0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf": { decimals: 8, symbol: "cbBTC" },
|
|
106
|
+
"0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452": { decimals: 18, symbol: "wstETH" },
|
|
107
|
+
"0x50c5725949a6f0c72e6c4a641f24049a917db0cb": { decimals: 18, symbol: "DAI" },
|
|
108
|
+
"0xfde4c96c8593536e31f229ea8f37b2ada2699bb2": { decimals: 6, symbol: "USDT" },
|
|
109
|
+
"0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca": { decimals: 6, symbol: "USDbC" },
|
|
110
|
+
"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42": { decimals: 6, symbol: "EURC" },
|
|
111
|
+
"0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c": { decimals: 18, symbol: "rETH" },
|
|
112
|
+
"0x0555e30da8f98308edb960aa94c0db47230d2b9c": { decimals: 8, symbol: "WBTC" },
|
|
113
|
+
// Established (1)
|
|
114
|
+
"0x940181a94a35a4569e4529a3cdfb74e38fd98631": { decimals: 18, symbol: "AERO" },
|
|
115
|
+
"0x04c0599ae5a44757c0af6f9ec3b93da8976c150a": { decimals: 18, symbol: "weETH" },
|
|
116
|
+
"0x2416092f143378750bb29b79ed961ab195cceea5": { decimals: 18, symbol: "ezETH" },
|
|
117
|
+
"0xa88594d404727625a9437c3f886c7643872296ae": { decimals: 18, symbol: "WELL" },
|
|
118
|
+
"0xbaa5cc21fd487b8fcc2f632f3f4e8d37262a0842": { decimals: 18, symbol: "MORPHO" },
|
|
119
|
+
"0x88fb150bdc53a65fe94dea0c9ba0a6daf8c6e196": { decimals: 18, symbol: "LINK" },
|
|
120
|
+
"0xc3de830ea07524a0761646a6a4e4be0e114a3c83": { decimals: 18, symbol: "UNI" },
|
|
121
|
+
"0x63706e401c06ac8513145b7687a14804d17f814b": { decimals: 18, symbol: "AAVE" },
|
|
122
|
+
"0x9e1028f5f1d5ede59748ffcee5532509976840e0": { decimals: 18, symbol: "COMP" },
|
|
123
|
+
"0x4158734d47fc9692176b5085e0f52ee0da5d47f1": { decimals: 18, symbol: "BAL" },
|
|
124
|
+
"0x8ee73c484a26e0a5df2ee2a4960b789967dd0415": { decimals: 18, symbol: "CRV" },
|
|
125
|
+
"0x22e6966b799c4d5b13be962e1d117b56327fda66": { decimals: 18, symbol: "SNX" },
|
|
126
|
+
// Derivatives (2)
|
|
127
|
+
"0x0b3e328455c4059eeb9e3f84b5543f74e24e7e1b": { decimals: 18, symbol: "VIRTUAL" },
|
|
128
|
+
"0x4f9fd6be4a90f2620860d680c0d4d5fb53d1a825": { decimals: 18, symbol: "AIXBT" },
|
|
129
|
+
"0x4ed4e862860bed51a9570b96d89af5e1b0efefed": { decimals: 18, symbol: "DEGEN" },
|
|
130
|
+
"0x0578d8a44db98b23bf096a382e016e29a5ce0ffe": { decimals: 18, symbol: "HIGHER" },
|
|
131
|
+
"0x1bc0c42215582d5a085795f4badbaac3ff36d1bcb": { decimals: 18, symbol: "CLANKER" },
|
|
132
|
+
// Emerging (3)
|
|
133
|
+
"0x532f27101965dd16442e59d40670faf5ebb142e4": { decimals: 18, symbol: "BRETT" },
|
|
134
|
+
"0xac1bd2486aaf3b5c0fc3fd868558b082a531b2b4": { decimals: 18, symbol: "TOSHI" },
|
|
135
|
+
"0x6921b130d297cc43754afba22e5eac0fbf8db75b": { decimals: 18, symbol: "DOGINME" },
|
|
136
|
+
"0xb1a03eda10342529bbf8eb700a06c60441fef25d": { decimals: 18, symbol: "MIGGLES" },
|
|
137
|
+
"0x7f12d13b34f5f4f0a9449c16bcd42f0da47af200": { decimals: 9, symbol: "NORMIE" },
|
|
138
|
+
"0x27d2decb4bfc9c76f0309b8e88dec3a601fe25a8": { decimals: 18, symbol: "BALD" },
|
|
139
|
+
"0x768be13e1680b5ebe0024c42c896e3db59ec0149": { decimals: 9, symbol: "SKI" }
|
|
140
|
+
};
|
|
141
|
+
var TokenResolver = class {
|
|
142
|
+
client;
|
|
143
|
+
store;
|
|
144
|
+
cache = {};
|
|
145
|
+
unresolvable = /* @__PURE__ */ new Set();
|
|
146
|
+
constructor(client, store) {
|
|
147
|
+
this.client = client;
|
|
148
|
+
this.store = store || null;
|
|
149
|
+
for (const [addr, info] of Object.entries(HARDCODED)) {
|
|
150
|
+
this.cache[addr] = {
|
|
151
|
+
decimals: info.decimals,
|
|
152
|
+
symbol: info.symbol,
|
|
153
|
+
source: "hardcoded",
|
|
154
|
+
resolvedAt: 0
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (this.store) {
|
|
158
|
+
const saved = this.store.get(STORE_KEY);
|
|
159
|
+
if (saved) {
|
|
160
|
+
for (const [addr, meta] of Object.entries(saved)) {
|
|
161
|
+
const key = addr.toLowerCase();
|
|
162
|
+
if (!this.cache[key]) {
|
|
163
|
+
this.cache[key] = meta;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const savedUnresolvable = this.store.get(UNRESOLVABLE_KEY);
|
|
168
|
+
if (savedUnresolvable) {
|
|
169
|
+
for (const addr of savedUnresolvable) {
|
|
170
|
+
this.unresolvable.add(addr.toLowerCase());
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get decimals for a token (synchronous).
|
|
177
|
+
* Returns undefined if the token has never been resolved and is not hardcoded.
|
|
178
|
+
* Call resolve() first for unknown tokens.
|
|
179
|
+
*/
|
|
180
|
+
getDecimals(address) {
|
|
181
|
+
const meta = this.cache[address.toLowerCase()];
|
|
182
|
+
return meta?.decimals;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get symbol for a token (synchronous).
|
|
186
|
+
*/
|
|
187
|
+
getSymbol(address) {
|
|
188
|
+
return this.cache[address.toLowerCase()]?.symbol;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Set symbol for a token (used when DexScreener returns symbol data).
|
|
192
|
+
*/
|
|
193
|
+
setSymbol(address, symbol) {
|
|
194
|
+
const key = address.toLowerCase();
|
|
195
|
+
if (this.cache[key]) {
|
|
196
|
+
if (!this.cache[key].symbol) {
|
|
197
|
+
this.cache[key].symbol = symbol;
|
|
198
|
+
this.persist();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Check if a token has been marked as unresolvable by all tiers.
|
|
204
|
+
*/
|
|
205
|
+
isUnresolvable(address) {
|
|
206
|
+
return this.unresolvable.has(address.toLowerCase());
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Batch-resolve metadata for a list of token addresses.
|
|
210
|
+
*
|
|
211
|
+
* Skips already-resolved tokens. For unknown tokens:
|
|
212
|
+
* 1. Multicall3 on-chain batch (decimals + symbol)
|
|
213
|
+
* 2. Blockscout API for any multicall failures
|
|
214
|
+
* 3. Mark remaining as unresolvable
|
|
215
|
+
*/
|
|
216
|
+
async resolve(addresses) {
|
|
217
|
+
const unknown = addresses.filter((addr) => {
|
|
218
|
+
const key = addr.toLowerCase();
|
|
219
|
+
return !this.cache[key] && !this.unresolvable.has(key);
|
|
220
|
+
});
|
|
221
|
+
if (unknown.length === 0) return;
|
|
222
|
+
const unique = [...new Set(unknown.map((a) => a.toLowerCase()))];
|
|
223
|
+
const stillUnresolved = await this.resolveViaMulticall(unique);
|
|
224
|
+
if (stillUnresolved.length > 0) {
|
|
225
|
+
const afterBlockscout = await this.resolveViaBlockscout(stillUnresolved);
|
|
226
|
+
if (afterBlockscout.length > 0) {
|
|
227
|
+
for (const addr of afterBlockscout) {
|
|
228
|
+
this.unresolvable.add(addr);
|
|
229
|
+
}
|
|
230
|
+
console.warn(
|
|
231
|
+
`TokenResolver: ${afterBlockscout.length} token(s) unresolvable after all tiers \u2014 skipping from portfolio: ${afterBlockscout.map((a) => a.slice(0, 10) + "...").join(", ")}`
|
|
232
|
+
);
|
|
233
|
+
this.persistUnresolvable();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Tier 2: Batch-fetch decimals + symbol via Multicall3.
|
|
239
|
+
* Returns addresses that failed (for next tier).
|
|
240
|
+
*/
|
|
241
|
+
async resolveViaMulticall(addresses) {
|
|
242
|
+
const failed = [];
|
|
243
|
+
try {
|
|
244
|
+
const contracts = addresses.flatMap((addr) => [
|
|
245
|
+
{
|
|
246
|
+
address: addr,
|
|
247
|
+
abi: import_viem.erc20Abi,
|
|
248
|
+
functionName: "decimals"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
address: addr,
|
|
252
|
+
abi: import_viem.erc20Abi,
|
|
253
|
+
functionName: "symbol"
|
|
254
|
+
}
|
|
255
|
+
]);
|
|
256
|
+
const BATCH_SIZE = 100;
|
|
257
|
+
const allResults = [];
|
|
258
|
+
for (let i = 0; i < contracts.length; i += BATCH_SIZE) {
|
|
259
|
+
const batch = contracts.slice(i, i + BATCH_SIZE);
|
|
260
|
+
try {
|
|
261
|
+
const results = await this.client.multicall({
|
|
262
|
+
contracts: batch,
|
|
263
|
+
allowFailure: true
|
|
264
|
+
});
|
|
265
|
+
allResults.push(...results);
|
|
266
|
+
} catch {
|
|
267
|
+
for (let j = 0; j < batch.length; j += 2) {
|
|
268
|
+
allResults.push({ status: "failure" }, { status: "failure" });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
273
|
+
const decimalsResult = allResults[i * 2];
|
|
274
|
+
const symbolResult = allResults[i * 2 + 1];
|
|
275
|
+
const addr = addresses[i];
|
|
276
|
+
if (decimalsResult?.status === "success" && decimalsResult.result !== void 0) {
|
|
277
|
+
const decimals = Number(decimalsResult.result);
|
|
278
|
+
const symbol = symbolResult?.status === "success" && symbolResult.result ? String(symbolResult.result) : void 0;
|
|
279
|
+
this.cache[addr] = {
|
|
280
|
+
decimals,
|
|
281
|
+
symbol,
|
|
282
|
+
source: "onchain",
|
|
283
|
+
resolvedAt: Date.now()
|
|
284
|
+
};
|
|
285
|
+
if (symbol) {
|
|
286
|
+
console.log(`TokenResolver: ${symbol} (${addr.slice(0, 10)}...) \u2192 ${decimals} decimals [onchain]`);
|
|
287
|
+
} else {
|
|
288
|
+
console.log(`TokenResolver: ${addr.slice(0, 10)}... \u2192 ${decimals} decimals [onchain]`);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
failed.push(addr);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
this.persist();
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.warn("TokenResolver: Multicall3 failed entirely, trying Blockscout:", error instanceof Error ? error.message : error);
|
|
297
|
+
return addresses;
|
|
298
|
+
}
|
|
299
|
+
return failed;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Tier 3: Fetch token metadata from Blockscout (Base's block explorer API).
|
|
303
|
+
* Free, no API key, near-universal coverage for any token indexed by the explorer.
|
|
304
|
+
* Returns addresses that failed (truly unresolvable).
|
|
305
|
+
*/
|
|
306
|
+
async resolveViaBlockscout(addresses) {
|
|
307
|
+
const failed = [];
|
|
308
|
+
const CONCURRENCY = 5;
|
|
309
|
+
for (let i = 0; i < addresses.length; i += CONCURRENCY) {
|
|
310
|
+
const batch = addresses.slice(i, i + CONCURRENCY);
|
|
311
|
+
const results = await Promise.allSettled(
|
|
312
|
+
batch.map(async (addr) => {
|
|
313
|
+
const response = await fetch(
|
|
314
|
+
`https://base.blockscout.com/api/v2/tokens/${addr}`,
|
|
315
|
+
{ signal: AbortSignal.timeout(8e3) }
|
|
316
|
+
);
|
|
317
|
+
if (!response.ok) return null;
|
|
318
|
+
const data = await response.json();
|
|
319
|
+
if (data.decimals === null || data.decimals === void 0) return null;
|
|
320
|
+
const decimals = parseInt(data.decimals, 10);
|
|
321
|
+
if (isNaN(decimals)) return null;
|
|
322
|
+
return {
|
|
323
|
+
decimals,
|
|
324
|
+
symbol: data.symbol || data.name || void 0
|
|
325
|
+
};
|
|
326
|
+
})
|
|
327
|
+
);
|
|
328
|
+
for (let j = 0; j < batch.length; j++) {
|
|
329
|
+
const addr = batch[j];
|
|
330
|
+
const result = results[j];
|
|
331
|
+
if (result.status === "fulfilled" && result.value) {
|
|
332
|
+
this.cache[addr] = {
|
|
333
|
+
decimals: result.value.decimals,
|
|
334
|
+
symbol: result.value.symbol,
|
|
335
|
+
source: "blockscout",
|
|
336
|
+
resolvedAt: Date.now()
|
|
337
|
+
};
|
|
338
|
+
const sym = result.value.symbol || addr.slice(0, 10) + "...";
|
|
339
|
+
console.log(`TokenResolver: ${sym} \u2192 ${result.value.decimals} decimals [blockscout]`);
|
|
340
|
+
} else {
|
|
341
|
+
failed.push(addr);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (failed.length === 0 || addresses.length > failed.length) {
|
|
346
|
+
this.persist();
|
|
347
|
+
}
|
|
348
|
+
return failed;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Persist resolved metadata to FileStore.
|
|
352
|
+
*/
|
|
353
|
+
persist() {
|
|
354
|
+
if (!this.store) return;
|
|
355
|
+
const toSave = {};
|
|
356
|
+
for (const [addr, meta] of Object.entries(this.cache)) {
|
|
357
|
+
if (meta.source !== "hardcoded") {
|
|
358
|
+
toSave[addr] = meta;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
this.store.set(STORE_KEY, toSave);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Persist unresolvable token list to FileStore.
|
|
365
|
+
*/
|
|
366
|
+
persistUnresolvable() {
|
|
367
|
+
if (!this.store) return;
|
|
368
|
+
this.store.set(UNRESOLVABLE_KEY, [...this.unresolvable]);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// src/trading/market.ts
|
|
94
373
|
var NATIVE_ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
374
|
+
var _globalResolver = null;
|
|
375
|
+
function setGlobalResolver(resolver) {
|
|
376
|
+
_globalResolver = resolver;
|
|
377
|
+
}
|
|
95
378
|
var TOKEN_DECIMALS = {
|
|
96
379
|
[NATIVE_ETH.toLowerCase()]: 18,
|
|
97
380
|
// Native ETH
|
|
@@ -170,54 +453,19 @@ var TOKEN_DECIMALS = {
|
|
|
170
453
|
"0x768be13e1680b5ebe0024c42c896e3db59ec0149": 9
|
|
171
454
|
// SKI
|
|
172
455
|
};
|
|
173
|
-
var decimalsCache = {};
|
|
174
|
-
var symbolCache = {};
|
|
175
456
|
function getTokenDecimals(address) {
|
|
176
457
|
const key = address.toLowerCase();
|
|
458
|
+
if (_globalResolver) {
|
|
459
|
+
const d = _globalResolver.getDecimals(key);
|
|
460
|
+
if (d !== void 0) return d;
|
|
461
|
+
if (_globalResolver.isUnresolvable(key)) return 18;
|
|
462
|
+
}
|
|
177
463
|
const known = TOKEN_DECIMALS[key];
|
|
178
464
|
if (known !== void 0) return known;
|
|
179
|
-
const cached = decimalsCache[key];
|
|
180
|
-
if (cached !== void 0) return cached;
|
|
181
|
-
console.warn(`Unknown token decimals for ${address}, defaulting to 18. Call fetchTokenDecimals() first for accuracy.`);
|
|
182
465
|
return 18;
|
|
183
466
|
}
|
|
184
467
|
function getTokenSymbol(address) {
|
|
185
|
-
return
|
|
186
|
-
}
|
|
187
|
-
async function fetchTokenDecimals(client, address) {
|
|
188
|
-
const key = address.toLowerCase();
|
|
189
|
-
const known = TOKEN_DECIMALS[key];
|
|
190
|
-
if (known !== void 0) return known;
|
|
191
|
-
const cached = decimalsCache[key];
|
|
192
|
-
if (cached !== void 0) return cached;
|
|
193
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
194
|
-
try {
|
|
195
|
-
const [decimals, symbol] = await Promise.all([
|
|
196
|
-
client.readContract({
|
|
197
|
-
address,
|
|
198
|
-
abi: import_viem.erc20Abi,
|
|
199
|
-
functionName: "decimals"
|
|
200
|
-
}),
|
|
201
|
-
client.readContract({
|
|
202
|
-
address,
|
|
203
|
-
abi: import_viem.erc20Abi,
|
|
204
|
-
functionName: "symbol"
|
|
205
|
-
}).catch(() => void 0)
|
|
206
|
-
// symbol is optional — some tokens don't have it
|
|
207
|
-
]);
|
|
208
|
-
const result = Number(decimals);
|
|
209
|
-
decimalsCache[key] = result;
|
|
210
|
-
if (symbol) symbolCache[key] = String(symbol);
|
|
211
|
-
return result;
|
|
212
|
-
} catch {
|
|
213
|
-
if (attempt === 0) {
|
|
214
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
console.warn(`Failed to fetch decimals for ${address} after 2 attempts, defaulting to 18`);
|
|
219
|
-
decimalsCache[key] = 18;
|
|
220
|
-
return 18;
|
|
468
|
+
return _globalResolver?.getSymbol(address.toLowerCase());
|
|
221
469
|
}
|
|
222
470
|
var TOKEN_TO_COINGECKO = {
|
|
223
471
|
[NATIVE_ETH.toLowerCase()]: "ethereum",
|
|
@@ -301,24 +549,31 @@ var PRICE_STALENESS_MS = 6e4;
|
|
|
301
549
|
var MarketDataService = class {
|
|
302
550
|
rpcUrl;
|
|
303
551
|
client;
|
|
552
|
+
resolver;
|
|
304
553
|
/** Cached prices from last fetch */
|
|
305
554
|
cachedPrices = {};
|
|
306
555
|
/** Timestamp of last successful price fetch */
|
|
307
556
|
lastPriceFetchAt = 0;
|
|
308
|
-
constructor(rpcUrl) {
|
|
309
|
-
this.rpcUrl = rpcUrl;
|
|
310
|
-
this.client = (0, import_viem.createPublicClient)({
|
|
311
|
-
transport: (0, import_viem.http)(rpcUrl, { timeout: 6e4 })
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
557
|
/** Cached volume data */
|
|
315
558
|
cachedVolume24h = {};
|
|
316
559
|
/** Cached price change data */
|
|
317
560
|
cachedPriceChange24h = {};
|
|
561
|
+
constructor(rpcUrl, store) {
|
|
562
|
+
this.rpcUrl = rpcUrl;
|
|
563
|
+
this.client = (0, import_viem2.createPublicClient)({
|
|
564
|
+
transport: (0, import_viem2.http)(rpcUrl, { timeout: 6e4 })
|
|
565
|
+
});
|
|
566
|
+
this.resolver = new TokenResolver(this.client, store);
|
|
567
|
+
}
|
|
568
|
+
/** Get the underlying TokenResolver for direct access */
|
|
569
|
+
getResolver() {
|
|
570
|
+
return this.resolver;
|
|
571
|
+
}
|
|
318
572
|
/**
|
|
319
573
|
* Fetch current market data for the agent
|
|
320
574
|
*/
|
|
321
575
|
async fetchMarketData(walletAddress, tokenAddresses) {
|
|
576
|
+
await this.resolver.resolve(tokenAddresses);
|
|
322
577
|
const prices = await this.fetchPrices(tokenAddresses);
|
|
323
578
|
const balances = await this.fetchBalances(walletAddress, tokenAddresses);
|
|
324
579
|
const portfolioValue = this.calculatePortfolioValue(balances, prices);
|
|
@@ -455,14 +710,14 @@ var MarketDataService = class {
|
|
|
455
710
|
const liq = pair.liquidity?.usd || 0;
|
|
456
711
|
if (price > 0 && (!bestPrices[addr] || liq > bestPrices[addr].liquidity)) {
|
|
457
712
|
bestPrices[addr] = { price, liquidity: liq };
|
|
458
|
-
if (pair.baseToken.symbol
|
|
459
|
-
|
|
713
|
+
if (pair.baseToken.symbol) {
|
|
714
|
+
this.resolver.setSymbol(addr, pair.baseToken.symbol);
|
|
460
715
|
}
|
|
461
716
|
}
|
|
462
717
|
}
|
|
463
718
|
for (const [addr, { price }] of Object.entries(bestPrices)) {
|
|
464
719
|
prices[addr] = price;
|
|
465
|
-
const sym =
|
|
720
|
+
const sym = this.resolver.getSymbol(addr) || addr.slice(0, 10);
|
|
466
721
|
console.log(`DexScreener price for ${sym}: $${price}`);
|
|
467
722
|
}
|
|
468
723
|
}
|
|
@@ -479,7 +734,7 @@ var MarketDataService = class {
|
|
|
479
734
|
}
|
|
480
735
|
for (const addr of tokenAddresses) {
|
|
481
736
|
if (!prices[addr.toLowerCase()]) {
|
|
482
|
-
const sym =
|
|
737
|
+
const sym = this.resolver.getSymbol(addr.toLowerCase());
|
|
483
738
|
console.warn(`No price available for ${sym ? `${sym} (${addr})` : addr} \u2014 CoinGecko, DeFi Llama, and DexScreener all returned nothing`);
|
|
484
739
|
prices[addr.toLowerCase()] = 0;
|
|
485
740
|
}
|
|
@@ -487,36 +742,35 @@ var MarketDataService = class {
|
|
|
487
742
|
return prices;
|
|
488
743
|
}
|
|
489
744
|
/**
|
|
490
|
-
* Fetch real on-chain balances: native ETH + ERC-20 tokens
|
|
745
|
+
* Fetch real on-chain balances: native ETH + ERC-20 tokens.
|
|
746
|
+
* Uses Multicall3 to batch all balanceOf calls into a single RPC request.
|
|
491
747
|
*/
|
|
492
748
|
async fetchBalances(walletAddress, tokenAddresses) {
|
|
493
749
|
const balances = {};
|
|
494
750
|
const wallet = walletAddress;
|
|
495
751
|
try {
|
|
496
|
-
const unknownTokens = tokenAddresses.filter(
|
|
497
|
-
(addr) => TOKEN_DECIMALS[addr.toLowerCase()] === void 0 && !decimalsCache[addr.toLowerCase()]
|
|
498
|
-
);
|
|
499
|
-
if (unknownTokens.length > 0) {
|
|
500
|
-
await Promise.all(unknownTokens.map((addr) => fetchTokenDecimals(this.client, addr)));
|
|
501
|
-
}
|
|
502
752
|
const nativeBalance = await this.client.getBalance({ address: wallet });
|
|
503
753
|
balances[NATIVE_ETH.toLowerCase()] = nativeBalance;
|
|
504
|
-
|
|
754
|
+
if (tokenAddresses.length > 0) {
|
|
505
755
|
try {
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
756
|
+
const results = await this.client.multicall({
|
|
757
|
+
contracts: tokenAddresses.map((addr) => ({
|
|
758
|
+
address: addr,
|
|
759
|
+
abi: import_viem2.erc20Abi,
|
|
760
|
+
functionName: "balanceOf",
|
|
761
|
+
args: [wallet]
|
|
762
|
+
})),
|
|
763
|
+
allowFailure: true
|
|
511
764
|
});
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
765
|
+
for (let i = 0; i < tokenAddresses.length; i++) {
|
|
766
|
+
const addr = tokenAddresses[i].toLowerCase();
|
|
767
|
+
const r = results[i];
|
|
768
|
+
balances[addr] = r.status === "success" ? r.result : 0n;
|
|
769
|
+
}
|
|
770
|
+
} catch {
|
|
771
|
+
console.warn("Multicall3 balanceOf failed, falling back to individual queries");
|
|
772
|
+
await this.fetchBalancesIndividual(wallet, tokenAddresses, balances);
|
|
515
773
|
}
|
|
516
|
-
});
|
|
517
|
-
const results = await Promise.all(erc20Promises);
|
|
518
|
-
for (const { address, balance } of results) {
|
|
519
|
-
balances[address] = balance;
|
|
520
774
|
}
|
|
521
775
|
} catch (error) {
|
|
522
776
|
console.error("MarketData: Failed to fetch balances:", error instanceof Error ? error.message : error);
|
|
@@ -528,13 +782,42 @@ var MarketDataService = class {
|
|
|
528
782
|
return balances;
|
|
529
783
|
}
|
|
530
784
|
/**
|
|
531
|
-
*
|
|
785
|
+
* Fallback: fetch ERC-20 balances individually (if multicall fails).
|
|
786
|
+
*/
|
|
787
|
+
async fetchBalancesIndividual(wallet, tokenAddresses, balances) {
|
|
788
|
+
const promises = tokenAddresses.map(async (tokenAddress) => {
|
|
789
|
+
try {
|
|
790
|
+
const balance = await this.client.readContract({
|
|
791
|
+
address: tokenAddress,
|
|
792
|
+
abi: import_viem2.erc20Abi,
|
|
793
|
+
functionName: "balanceOf",
|
|
794
|
+
args: [wallet]
|
|
795
|
+
});
|
|
796
|
+
return { address: tokenAddress.toLowerCase(), balance };
|
|
797
|
+
} catch {
|
|
798
|
+
return { address: tokenAddress.toLowerCase(), balance: 0n };
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
const results = await Promise.all(promises);
|
|
802
|
+
for (const { address, balance } of results) {
|
|
803
|
+
balances[address] = balance;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Calculate total portfolio value in USD.
|
|
808
|
+
* Skips tokens with unresolvable decimals to avoid corrupted calculations.
|
|
532
809
|
*/
|
|
533
810
|
calculatePortfolioValue(balances, prices) {
|
|
534
811
|
let total = 0;
|
|
535
812
|
for (const [address, balance] of Object.entries(balances)) {
|
|
536
813
|
const price = prices[address.toLowerCase()] || 0;
|
|
537
|
-
|
|
814
|
+
if (price === 0) continue;
|
|
815
|
+
const key = address.toLowerCase();
|
|
816
|
+
const resolverDecimals = _globalResolver?.getDecimals(key);
|
|
817
|
+
if (resolverDecimals === void 0 && _globalResolver?.isUnresolvable(key)) {
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
const decimals = resolverDecimals ?? getTokenDecimals(address);
|
|
538
821
|
const amount = Number(balance) / Math.pow(10, decimals);
|
|
539
822
|
total += amount * price;
|
|
540
823
|
}
|
|
@@ -2254,7 +2537,7 @@ var FileStore = class {
|
|
|
2254
2537
|
};
|
|
2255
2538
|
|
|
2256
2539
|
// src/vault/manager.ts
|
|
2257
|
-
var
|
|
2540
|
+
var import_viem3 = require("viem");
|
|
2258
2541
|
var import_accounts = require("viem/accounts");
|
|
2259
2542
|
var import_chains = require("viem/chains");
|
|
2260
2543
|
var ADDRESSES = {
|
|
@@ -2345,12 +2628,12 @@ var VaultManager = class {
|
|
|
2345
2628
|
this.account = (0, import_accounts.privateKeyToAccount)(config.walletKey);
|
|
2346
2629
|
this.chain = import_chains.base;
|
|
2347
2630
|
const rpcUrl = getRpcUrl();
|
|
2348
|
-
const transport = (0,
|
|
2349
|
-
this.publicClient = (0,
|
|
2631
|
+
const transport = (0, import_viem3.http)(rpcUrl, { timeout: 6e4 });
|
|
2632
|
+
this.publicClient = (0, import_viem3.createPublicClient)({
|
|
2350
2633
|
chain: this.chain,
|
|
2351
2634
|
transport
|
|
2352
2635
|
});
|
|
2353
|
-
this.walletClient = (0,
|
|
2636
|
+
this.walletClient = (0, import_viem3.createWalletClient)({
|
|
2354
2637
|
account: this.account,
|
|
2355
2638
|
chain: this.chain,
|
|
2356
2639
|
transport
|
|
@@ -2998,7 +3281,7 @@ var HyperliquidClient = class {
|
|
|
2998
3281
|
};
|
|
2999
3282
|
|
|
3000
3283
|
// src/perp/signer.ts
|
|
3001
|
-
var
|
|
3284
|
+
var import_viem4 = require("viem");
|
|
3002
3285
|
var HYPERLIQUID_DOMAIN = {
|
|
3003
3286
|
name: "HyperliquidSignTransaction",
|
|
3004
3287
|
version: "1",
|
|
@@ -3072,10 +3355,10 @@ function fillHashToBytes32(fillHash) {
|
|
|
3072
3355
|
if (fillHash.startsWith("0x") && fillHash.length === 66) {
|
|
3073
3356
|
return fillHash;
|
|
3074
3357
|
}
|
|
3075
|
-
return (0,
|
|
3358
|
+
return (0, import_viem4.keccak256)((0, import_viem4.encodePacked)(["string"], [fillHash]));
|
|
3076
3359
|
}
|
|
3077
3360
|
function fillOidToBytes32(oid) {
|
|
3078
|
-
return (0,
|
|
3361
|
+
return (0, import_viem4.keccak256)((0, import_viem4.encodePacked)(["uint256"], [BigInt(oid)]));
|
|
3079
3362
|
}
|
|
3080
3363
|
|
|
3081
3364
|
// src/perp/orders.ts
|
|
@@ -3677,7 +3960,7 @@ var HyperliquidWebSocket = class {
|
|
|
3677
3960
|
};
|
|
3678
3961
|
|
|
3679
3962
|
// src/perp/recorder.ts
|
|
3680
|
-
var
|
|
3963
|
+
var import_viem5 = require("viem");
|
|
3681
3964
|
var import_chains2 = require("viem/chains");
|
|
3682
3965
|
var import_accounts3 = require("viem/accounts");
|
|
3683
3966
|
var ROUTER_ADDRESS = "0x1BCFa13f677fDCf697D8b7d5120f544817F1de1A";
|
|
@@ -3720,12 +4003,12 @@ var PerpTradeRecorder = class {
|
|
|
3720
4003
|
this.configHash = opts.configHash;
|
|
3721
4004
|
this.account = (0, import_accounts3.privateKeyToAccount)(opts.privateKey);
|
|
3722
4005
|
const rpcUrl = opts.rpcUrl || "https://mainnet.base.org";
|
|
3723
|
-
const transport = (0,
|
|
3724
|
-
this.publicClient = (0,
|
|
4006
|
+
const transport = (0, import_viem5.http)(rpcUrl, { timeout: 6e4 });
|
|
4007
|
+
this.publicClient = (0, import_viem5.createPublicClient)({
|
|
3725
4008
|
chain: import_chains2.base,
|
|
3726
4009
|
transport
|
|
3727
4010
|
});
|
|
3728
|
-
this.walletClient = (0,
|
|
4011
|
+
this.walletClient = (0, import_viem5.createWalletClient)({
|
|
3729
4012
|
chain: import_chains2.base,
|
|
3730
4013
|
transport,
|
|
3731
4014
|
account: this.account
|
|
@@ -4033,23 +4316,23 @@ var PerpOnboarding = class {
|
|
|
4033
4316
|
};
|
|
4034
4317
|
|
|
4035
4318
|
// src/perp/funding.ts
|
|
4036
|
-
var
|
|
4319
|
+
var import_viem6 = require("viem");
|
|
4037
4320
|
var import_chains3 = require("viem/chains");
|
|
4038
4321
|
var import_accounts4 = require("viem/accounts");
|
|
4039
|
-
var ERC20_ABI = (0,
|
|
4322
|
+
var ERC20_ABI = (0, import_viem6.parseAbi)([
|
|
4040
4323
|
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
4041
4324
|
"function balanceOf(address account) external view returns (uint256)",
|
|
4042
4325
|
"function allowance(address owner, address spender) external view returns (uint256)"
|
|
4043
4326
|
]);
|
|
4044
|
-
var TOKEN_MESSENGER_V2_ABI = (0,
|
|
4327
|
+
var TOKEN_MESSENGER_V2_ABI = (0, import_viem6.parseAbi)([
|
|
4045
4328
|
"function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken) external returns (uint64 nonce)",
|
|
4046
4329
|
"event DepositForBurn(uint64 indexed nonce, address indexed burnToken, uint256 amount, address indexed depositor, bytes32 mintRecipient, uint32 destinationDomain, bytes32 destinationTokenMessenger, bytes32 destinationCaller)"
|
|
4047
4330
|
]);
|
|
4048
|
-
var MESSAGE_TRANSMITTER_V2_ABI = (0,
|
|
4331
|
+
var MESSAGE_TRANSMITTER_V2_ABI = (0, import_viem6.parseAbi)([
|
|
4049
4332
|
"function receiveMessage(bytes message, bytes attestation) external returns (bool success)",
|
|
4050
4333
|
"event MessageSent(bytes message)"
|
|
4051
4334
|
]);
|
|
4052
|
-
var CORE_DEPOSIT_WALLET_ABI = (0,
|
|
4335
|
+
var CORE_DEPOSIT_WALLET_ABI = (0, import_viem6.parseAbi)([
|
|
4053
4336
|
"function deposit(uint256 amount, uint32 destinationDex) external"
|
|
4054
4337
|
]);
|
|
4055
4338
|
|
|
@@ -4139,7 +4422,8 @@ var AgentRuntime = class {
|
|
|
4139
4422
|
this.positionTracker = new PositionTracker(store, { maxTradeHistory: 50 });
|
|
4140
4423
|
this.executor = new TradeExecutor(this.client, this.config, () => this.getConfigHash());
|
|
4141
4424
|
this.riskManager = new RiskManager(this.config.trading);
|
|
4142
|
-
this.marketData = new MarketDataService(this.getRpcUrl());
|
|
4425
|
+
this.marketData = new MarketDataService(this.getRpcUrl(), store);
|
|
4426
|
+
setGlobalResolver(this.marketData.getResolver());
|
|
4143
4427
|
const savedRisk = this.positionTracker.getRiskState();
|
|
4144
4428
|
if (savedRisk.lastResetDate) {
|
|
4145
4429
|
this.riskManager.restoreState(savedRisk);
|
|
@@ -4236,9 +4520,9 @@ var AgentRuntime = class {
|
|
|
4236
4520
|
this.perpClient = new HyperliquidClient(config);
|
|
4237
4521
|
const perpKey = perpConfig.perpRelayerKey || this.config.privateKey;
|
|
4238
4522
|
const account = (0, import_accounts5.privateKeyToAccount)(perpKey);
|
|
4239
|
-
const walletClient = (0,
|
|
4523
|
+
const walletClient = (0, import_viem7.createWalletClient)({
|
|
4240
4524
|
chain: { id: 42161, name: "Arbitrum", nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, rpcUrls: { default: { http: ["https://arb1.arbitrum.io/rpc"] } } },
|
|
4241
|
-
transport: (0,
|
|
4525
|
+
transport: (0, import_viem7.http)("https://arb1.arbitrum.io/rpc", { timeout: 6e4 }),
|
|
4242
4526
|
account
|
|
4243
4527
|
});
|
|
4244
4528
|
this.perpSigner = new HyperliquidSigner(walletClient);
|
|
@@ -4478,9 +4762,9 @@ var AgentRuntime = class {
|
|
|
4478
4762
|
const message = error instanceof Error ? error.message : String(error);
|
|
4479
4763
|
if (message.includes("insufficient funds") || message.includes("intrinsic gas too low") || message.includes("exceeds the balance")) {
|
|
4480
4764
|
const ccUrl = `https://exagent.io/agents/${encodeURIComponent(this.config.name)}/command-center`;
|
|
4481
|
-
const publicClientInstance = (0,
|
|
4765
|
+
const publicClientInstance = (0, import_viem7.createPublicClient)({
|
|
4482
4766
|
chain: import_chains4.base,
|
|
4483
|
-
transport: (0,
|
|
4767
|
+
transport: (0, import_viem7.http)(this.getRpcUrl(), { timeout: 6e4 })
|
|
4484
4768
|
});
|
|
4485
4769
|
console.log("");
|
|
4486
4770
|
console.log("=== ETH NEEDED FOR GAS ===");
|
|
@@ -4543,6 +4827,21 @@ var AgentRuntime = class {
|
|
|
4543
4827
|
console.log("Visit https://exagent.io to start trading from the dashboard.");
|
|
4544
4828
|
console.log("");
|
|
4545
4829
|
this.mode = "idle";
|
|
4830
|
+
try {
|
|
4831
|
+
const tokens = this.getTokensToTrack();
|
|
4832
|
+
const initData = await this.marketData.fetchMarketData(this.client.address, tokens);
|
|
4833
|
+
this.positionTracker.syncBalances(initData.balances, initData.prices);
|
|
4834
|
+
this.lastPortfolioValue = initData.portfolioValue;
|
|
4835
|
+
this.lastPrices = initData.prices;
|
|
4836
|
+
const nativeEthBal = initData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
4837
|
+
this.lastEthBalance = (Number(nativeEthBal) / 1e18).toFixed(6);
|
|
4838
|
+
const posCount = this.positionTracker.getPositions().length;
|
|
4839
|
+
if (posCount > 0) {
|
|
4840
|
+
console.log(`Initial sync: ${posCount} position(s) rebuilt from on-chain balances`);
|
|
4841
|
+
}
|
|
4842
|
+
} catch (error) {
|
|
4843
|
+
console.warn("Initial balance sync failed (non-fatal):", error instanceof Error ? error.message : error);
|
|
4844
|
+
}
|
|
4546
4845
|
this.sendRelayStatus();
|
|
4547
4846
|
this.relay.sendMessage(
|
|
4548
4847
|
"system",
|
|
@@ -5217,7 +5516,14 @@ var AgentRuntime = class {
|
|
|
5217
5516
|
if (extras.length > 0) {
|
|
5218
5517
|
console.log(`Auto-tracking ${extras.length} additional token(s) from position history`);
|
|
5219
5518
|
}
|
|
5220
|
-
|
|
5519
|
+
const resolver = this.marketData.getResolver();
|
|
5520
|
+
const allTokens = [...base5, ...extras];
|
|
5521
|
+
const filtered = allTokens.filter((t) => !resolver.isUnresolvable(t.toLowerCase()));
|
|
5522
|
+
const dropped = allTokens.length - filtered.length;
|
|
5523
|
+
if (dropped > 0) {
|
|
5524
|
+
console.log(`Skipping ${dropped} unresolvable token(s)`);
|
|
5525
|
+
}
|
|
5526
|
+
return filtered;
|
|
5221
5527
|
}
|
|
5222
5528
|
/**
|
|
5223
5529
|
* Default tokens to track.
|
|
@@ -5548,7 +5854,7 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
5548
5854
|
}
|
|
5549
5855
|
|
|
5550
5856
|
// src/index.ts
|
|
5551
|
-
var AGENT_VERSION = "0.1.
|
|
5857
|
+
var AGENT_VERSION = "0.1.32";
|
|
5552
5858
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5553
5859
|
0 && (module.exports = {
|
|
5554
5860
|
AGENT_VERSION,
|