@agether/sdk 2.13.0 → 2.14.1
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/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +422 -37
- package/dist/clients/AgentIdentityClient.d.ts +200 -0
- package/dist/clients/AgentIdentityClient.d.ts.map +1 -0
- package/dist/clients/AgentIdentityClient.js +351 -0
- package/dist/clients/AgetherClient.d.ts +242 -0
- package/dist/clients/AgetherClient.d.ts.map +1 -0
- package/dist/clients/AgetherClient.js +736 -0
- package/dist/clients/MorphoClient.d.ts +572 -0
- package/dist/clients/MorphoClient.d.ts.map +1 -0
- package/dist/clients/MorphoClient.js +1974 -0
- package/dist/clients/ScoringClient.d.ts +103 -0
- package/dist/clients/ScoringClient.d.ts.map +1 -0
- package/dist/clients/ScoringClient.js +112 -0
- package/dist/clients/X402Client.d.ts +198 -0
- package/dist/clients/X402Client.d.ts.map +1 -0
- package/dist/clients/X402Client.js +438 -0
- package/dist/index.d.mts +87 -1
- package/dist/index.d.ts +87 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +422 -37
- package/dist/index.mjs +422 -37
- package/dist/types/index.d.ts +132 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +46 -0
- package/dist/utils/abis.d.ts +29 -0
- package/dist/utils/abis.d.ts.map +1 -0
- package/dist/utils/abis.js +138 -0
- package/dist/utils/config.d.ts +36 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +168 -0
- package/dist/utils/format.d.ts +44 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +75 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1080,7 +1080,7 @@ var MorphoClient = class {
|
|
|
1080
1080
|
const chainId = this.config.chainId;
|
|
1081
1081
|
const query = `{
|
|
1082
1082
|
markets(
|
|
1083
|
-
first:
|
|
1083
|
+
first: 500
|
|
1084
1084
|
orderBy: SupplyAssetsUsd
|
|
1085
1085
|
orderDirection: Desc
|
|
1086
1086
|
where: { chainId_in: [${chainId}] }
|
|
@@ -1186,7 +1186,7 @@ var MorphoClient = class {
|
|
|
1186
1186
|
const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
|
|
1187
1187
|
loanAddr = resolved.address.toLowerCase();
|
|
1188
1188
|
} catch {
|
|
1189
|
-
loanAddr =
|
|
1189
|
+
loanAddr = void 0;
|
|
1190
1190
|
}
|
|
1191
1191
|
}
|
|
1192
1192
|
}
|
|
@@ -1194,6 +1194,7 @@ var MorphoClient = class {
|
|
|
1194
1194
|
for (const m of this._discoveredMarkets ?? []) {
|
|
1195
1195
|
if (m.collateralAsset.address.toLowerCase() !== colAddr) continue;
|
|
1196
1196
|
if (loanAddr && m.loanAsset.address.toLowerCase() !== loanAddr) continue;
|
|
1197
|
+
if (!loanAddr && loanTokenSymbolOrAddress && m.loanAsset.symbol.toUpperCase() !== loanTokenSymbolOrAddress.toUpperCase()) continue;
|
|
1197
1198
|
return {
|
|
1198
1199
|
loanToken: m.loanAsset.address,
|
|
1199
1200
|
collateralToken: m.collateralAsset.address,
|
|
@@ -1202,6 +1203,27 @@ var MorphoClient = class {
|
|
|
1202
1203
|
lltv: m.lltv
|
|
1203
1204
|
};
|
|
1204
1205
|
}
|
|
1206
|
+
if (!collateralSymbolOrAddress.startsWith("0x")) {
|
|
1207
|
+
const searched = await this.searchMarkets(collateralSymbolOrAddress, { asCollateral: true });
|
|
1208
|
+
const allResults = [...searched];
|
|
1209
|
+
if (loanTokenSymbolOrAddress && !loanTokenSymbolOrAddress.startsWith("0x")) {
|
|
1210
|
+
const loanSearched = await this.searchMarkets(loanTokenSymbolOrAddress, { asLoanToken: true });
|
|
1211
|
+
const seen = new Set(allResults.map((r) => r.marketId));
|
|
1212
|
+
for (const m of loanSearched) {
|
|
1213
|
+
if (!seen.has(m.marketId)) allResults.push(m);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
for (const m of allResults) {
|
|
1217
|
+
if (colAddr.startsWith("0x") && m.collateralAddress.toLowerCase() !== colAddr) {
|
|
1218
|
+
if (m.collateralToken.toUpperCase() !== collateralSymbolOrAddress.toUpperCase()) continue;
|
|
1219
|
+
} else if (m.collateralToken.toUpperCase() !== collateralSymbolOrAddress.toUpperCase()) {
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
if (loanAddr && m.loanAddress.toLowerCase() !== loanAddr) continue;
|
|
1223
|
+
if (!loanAddr && loanTokenSymbolOrAddress && m.loanToken.toUpperCase() !== loanTokenSymbolOrAddress.toUpperCase()) continue;
|
|
1224
|
+
return this.getMarketParams(m.marketId);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1205
1227
|
throw new AgetherError(
|
|
1206
1228
|
`No Morpho market found for collateral ${collateralSymbolOrAddress}` + (loanTokenSymbolOrAddress ? ` with loan token ${loanTokenSymbolOrAddress}` : ""),
|
|
1207
1229
|
"MARKET_NOT_FOUND"
|
|
@@ -1232,26 +1254,54 @@ var MorphoClient = class {
|
|
|
1232
1254
|
};
|
|
1233
1255
|
}
|
|
1234
1256
|
/**
|
|
1235
|
-
* Full status: positions across all
|
|
1257
|
+
* Full status: positions across all markets the user has interacted with.
|
|
1258
|
+
*
|
|
1259
|
+
* Uses Morpho GraphQL `marketPositions` to find ALL positions (not limited
|
|
1260
|
+
* to the top-500 markets), then reads onchain data for accurate debt.
|
|
1236
1261
|
*/
|
|
1237
1262
|
async getStatus() {
|
|
1238
1263
|
const acctAddr = await this.getAccountAddress();
|
|
1239
|
-
const
|
|
1264
|
+
const chainId = this.config.chainId;
|
|
1240
1265
|
const positions = [];
|
|
1241
1266
|
let totalDebtFloat = 0;
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1267
|
+
try {
|
|
1268
|
+
const posQuery = `{
|
|
1269
|
+
marketPositions(
|
|
1270
|
+
where: {
|
|
1271
|
+
userAddress_in: ["${acctAddr}"]
|
|
1272
|
+
chainId_in: [${chainId}]
|
|
1273
|
+
}
|
|
1274
|
+
first: 100
|
|
1275
|
+
) {
|
|
1276
|
+
items {
|
|
1277
|
+
supplyShares
|
|
1278
|
+
borrowShares
|
|
1279
|
+
collateral
|
|
1280
|
+
market {
|
|
1281
|
+
uniqueKey
|
|
1282
|
+
loanAsset { symbol address decimals }
|
|
1283
|
+
collateralAsset { symbol address decimals }
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}`;
|
|
1288
|
+
const resp = await axios.post(MORPHO_API_URL, { query: posQuery }, { timeout: 15e3 });
|
|
1289
|
+
const items = resp.data?.data?.marketPositions?.items ?? [];
|
|
1290
|
+
for (const item of items) {
|
|
1291
|
+
const supplyShares = BigInt(item.supplyShares ?? "0");
|
|
1292
|
+
const borrowShares = BigInt(item.borrowShares ?? "0");
|
|
1293
|
+
const collateral = BigInt(item.collateral ?? "0");
|
|
1294
|
+
if (collateral === 0n && borrowShares === 0n && supplyShares === 0n) continue;
|
|
1295
|
+
const m = item.market;
|
|
1296
|
+
if (!m?.collateralAsset || !m?.loanAsset) continue;
|
|
1247
1297
|
const loanDecimals = m.loanAsset.decimals;
|
|
1248
1298
|
let debt = 0n;
|
|
1249
|
-
if (
|
|
1299
|
+
if (borrowShares > 0n) {
|
|
1250
1300
|
try {
|
|
1251
1301
|
const mkt = await this.morphoBlue.market(m.uniqueKey);
|
|
1252
1302
|
const totalBorrowShares = BigInt(mkt.totalBorrowShares);
|
|
1253
1303
|
const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
|
|
1254
|
-
debt = totalBorrowShares > 0n ? (
|
|
1304
|
+
debt = totalBorrowShares > 0n ? (borrowShares * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1255
1305
|
totalDebtFloat += parseFloat(ethers2.formatUnits(debt, loanDecimals));
|
|
1256
1306
|
} catch (e) {
|
|
1257
1307
|
console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
|
|
@@ -1261,14 +1311,46 @@ var MorphoClient = class {
|
|
|
1261
1311
|
marketId: m.uniqueKey,
|
|
1262
1312
|
collateralToken: m.collateralAsset.symbol,
|
|
1263
1313
|
loanToken: m.loanAsset.symbol,
|
|
1264
|
-
collateral: ethers2.formatUnits(
|
|
1265
|
-
borrowShares:
|
|
1266
|
-
supplyShares:
|
|
1314
|
+
collateral: ethers2.formatUnits(collateral, m.collateralAsset.decimals),
|
|
1315
|
+
borrowShares: borrowShares.toString(),
|
|
1316
|
+
supplyShares: supplyShares.toString(),
|
|
1267
1317
|
debt: ethers2.formatUnits(debt, loanDecimals)
|
|
1268
1318
|
});
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1319
|
+
}
|
|
1320
|
+
} catch (e) {
|
|
1321
|
+
console.warn("[agether] marketPositions API failed, falling back to market scan:", e instanceof Error ? e.message : e);
|
|
1322
|
+
const markets = await this.getMarkets();
|
|
1323
|
+
for (const m of markets) {
|
|
1324
|
+
if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
|
|
1325
|
+
try {
|
|
1326
|
+
const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
|
|
1327
|
+
if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
|
|
1328
|
+
const loanDecimals = m.loanAsset.decimals;
|
|
1329
|
+
let debt = 0n;
|
|
1330
|
+
if (pos.borrowShares > 0n) {
|
|
1331
|
+
try {
|
|
1332
|
+
const mkt = await this.morphoBlue.market(m.uniqueKey);
|
|
1333
|
+
const totalBorrowShares = BigInt(mkt.totalBorrowShares);
|
|
1334
|
+
const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
|
|
1335
|
+
debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1336
|
+
totalDebtFloat += parseFloat(ethers2.formatUnits(debt, loanDecimals));
|
|
1337
|
+
} catch (e2) {
|
|
1338
|
+
console.warn(`[agether] debt calc failed:`, e2 instanceof Error ? e2.message : e2);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
positions.push({
|
|
1342
|
+
marketId: m.uniqueKey,
|
|
1343
|
+
collateralToken: m.collateralAsset.symbol,
|
|
1344
|
+
loanToken: m.loanAsset.symbol,
|
|
1345
|
+
collateral: ethers2.formatUnits(pos.collateral, m.collateralAsset.decimals),
|
|
1346
|
+
borrowShares: pos.borrowShares.toString(),
|
|
1347
|
+
supplyShares: pos.supplyShares.toString(),
|
|
1348
|
+
debt: ethers2.formatUnits(debt, loanDecimals)
|
|
1349
|
+
});
|
|
1350
|
+
} catch (e2) {
|
|
1351
|
+
console.warn(`[agether] position read failed:`, e2 instanceof Error ? e2.message : e2);
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1272
1354
|
}
|
|
1273
1355
|
}
|
|
1274
1356
|
return {
|
|
@@ -1372,41 +1454,39 @@ var MorphoClient = class {
|
|
|
1372
1454
|
async getMarketRates(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
|
|
1373
1455
|
const chainId = this.config.chainId;
|
|
1374
1456
|
let collateralFilter = "";
|
|
1457
|
+
let loanFilter = "";
|
|
1458
|
+
let searchTerm = "";
|
|
1375
1459
|
if (collateralSymbolOrAddress) {
|
|
1376
|
-
let colAddr;
|
|
1377
1460
|
if (collateralSymbolOrAddress.startsWith("0x")) {
|
|
1378
|
-
|
|
1461
|
+
collateralFilter = `, collateralAssetAddress_in: ["${collateralSymbolOrAddress.toLowerCase()}"]`;
|
|
1379
1462
|
} else {
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1463
|
+
const cached = this._tokenCache.get(collateralSymbolOrAddress.toUpperCase());
|
|
1464
|
+
if (cached) {
|
|
1465
|
+
collateralFilter = `, collateralAssetAddress_in: ["${cached.address.toLowerCase()}"]`;
|
|
1466
|
+
} else {
|
|
1467
|
+
searchTerm = collateralSymbolOrAddress;
|
|
1385
1468
|
}
|
|
1386
1469
|
}
|
|
1387
|
-
collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
|
|
1388
1470
|
}
|
|
1389
|
-
let loanFilter = "";
|
|
1390
1471
|
if (loanTokenSymbolOrAddress) {
|
|
1391
|
-
let loanAddr;
|
|
1392
1472
|
if (loanTokenSymbolOrAddress.startsWith("0x")) {
|
|
1393
|
-
|
|
1473
|
+
loanFilter = `, loanAssetAddress_in: ["${loanTokenSymbolOrAddress.toLowerCase()}"]`;
|
|
1394
1474
|
} else {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1475
|
+
const cached = this._tokenCache.get(loanTokenSymbolOrAddress.toUpperCase());
|
|
1476
|
+
if (cached) {
|
|
1477
|
+
loanFilter = `, loanAssetAddress_in: ["${cached.address.toLowerCase()}"]`;
|
|
1478
|
+
} else {
|
|
1479
|
+
searchTerm = searchTerm || loanTokenSymbolOrAddress;
|
|
1400
1480
|
}
|
|
1401
1481
|
}
|
|
1402
|
-
loanFilter = `, loanAssetAddress_in: ["${loanAddr}"]`;
|
|
1403
1482
|
}
|
|
1483
|
+
const searchClause = searchTerm ? `, search: "${searchTerm}"` : "";
|
|
1404
1484
|
const query = `{
|
|
1405
1485
|
markets(
|
|
1406
|
-
first:
|
|
1486
|
+
first: 100
|
|
1407
1487
|
orderBy: SupplyAssetsUsd
|
|
1408
1488
|
orderDirection: Desc
|
|
1409
|
-
where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter} }
|
|
1489
|
+
where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter}${searchClause} }
|
|
1410
1490
|
) {
|
|
1411
1491
|
items {
|
|
1412
1492
|
uniqueKey
|
|
@@ -1425,7 +1505,15 @@ var MorphoClient = class {
|
|
|
1425
1505
|
}`;
|
|
1426
1506
|
try {
|
|
1427
1507
|
const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
|
|
1428
|
-
|
|
1508
|
+
let items = resp.data?.data?.markets?.items ?? [];
|
|
1509
|
+
if (searchTerm && collateralSymbolOrAddress && !collateralSymbolOrAddress.startsWith("0x")) {
|
|
1510
|
+
const sym = collateralSymbolOrAddress.toUpperCase();
|
|
1511
|
+
items = items.filter((m) => m.collateralAsset?.symbol?.toUpperCase() === sym);
|
|
1512
|
+
}
|
|
1513
|
+
if (searchTerm && loanTokenSymbolOrAddress && !loanTokenSymbolOrAddress.startsWith("0x")) {
|
|
1514
|
+
const sym = loanTokenSymbolOrAddress.toUpperCase();
|
|
1515
|
+
items = items.filter((m) => m.loanAsset?.symbol?.toUpperCase() === sym);
|
|
1516
|
+
}
|
|
1429
1517
|
return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress).map((m) => {
|
|
1430
1518
|
const loanDecimals = m.loanAsset?.decimals ?? 18;
|
|
1431
1519
|
return {
|
|
@@ -1446,6 +1534,205 @@ var MorphoClient = class {
|
|
|
1446
1534
|
return [];
|
|
1447
1535
|
}
|
|
1448
1536
|
}
|
|
1537
|
+
// ════════════════════════════════════════════════════════
|
|
1538
|
+
// Market Search & Wallet Discovery
|
|
1539
|
+
// ════════════════════════════════════════════════════════
|
|
1540
|
+
/**
|
|
1541
|
+
* Search Morpho markets by token name using the Morpho GraphQL API `search` field.
|
|
1542
|
+
* No hardcoded token lists — uses Morpho's built-in fuzzy search.
|
|
1543
|
+
*
|
|
1544
|
+
* @param search - token name or symbol (e.g. 'WETH', 'staked ETH', 'ezETH')
|
|
1545
|
+
* @param options.asCollateral - only return markets where the searched token is collateral
|
|
1546
|
+
* @param options.asLoanToken - only return markets where the searched token is the loan asset
|
|
1547
|
+
*/
|
|
1548
|
+
async searchMarkets(search, options) {
|
|
1549
|
+
const chainId = this.config.chainId;
|
|
1550
|
+
const query = `{
|
|
1551
|
+
markets(
|
|
1552
|
+
first: 100
|
|
1553
|
+
orderBy: SupplyAssetsUsd
|
|
1554
|
+
orderDirection: Desc
|
|
1555
|
+
where: { chainId_in: [${chainId}], search: "${search}" }
|
|
1556
|
+
) {
|
|
1557
|
+
items {
|
|
1558
|
+
uniqueKey
|
|
1559
|
+
lltv
|
|
1560
|
+
loanAsset { address symbol decimals }
|
|
1561
|
+
collateralAsset { address symbol decimals }
|
|
1562
|
+
state {
|
|
1563
|
+
borrowAssets
|
|
1564
|
+
supplyAssets
|
|
1565
|
+
utilization
|
|
1566
|
+
supplyApy
|
|
1567
|
+
borrowApy
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}`;
|
|
1572
|
+
try {
|
|
1573
|
+
const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
|
|
1574
|
+
let items = resp.data?.data?.markets?.items ?? [];
|
|
1575
|
+
items = items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress);
|
|
1576
|
+
const searchUpper = search.toUpperCase();
|
|
1577
|
+
if (options?.asCollateral) {
|
|
1578
|
+
items = items.filter((m) => m.collateralAsset?.symbol?.toUpperCase() === searchUpper);
|
|
1579
|
+
}
|
|
1580
|
+
if (options?.asLoanToken) {
|
|
1581
|
+
items = items.filter((m) => m.loanAsset?.symbol?.toUpperCase() === searchUpper);
|
|
1582
|
+
}
|
|
1583
|
+
return items.map((m) => {
|
|
1584
|
+
const loanDecimals = m.loanAsset?.decimals ?? 18;
|
|
1585
|
+
const collateralDecimals = m.collateralAsset?.decimals ?? 18;
|
|
1586
|
+
return {
|
|
1587
|
+
collateralToken: m.collateralAsset.symbol,
|
|
1588
|
+
loanToken: m.loanAsset.symbol,
|
|
1589
|
+
loanDecimals,
|
|
1590
|
+
collateralDecimals,
|
|
1591
|
+
supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
|
|
1592
|
+
borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
|
|
1593
|
+
utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
|
|
1594
|
+
totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 10 ** loanDecimals : 0,
|
|
1595
|
+
totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 10 ** loanDecimals : 0,
|
|
1596
|
+
lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
|
|
1597
|
+
marketId: m.uniqueKey,
|
|
1598
|
+
collateralAddress: m.collateralAsset.address,
|
|
1599
|
+
loanAddress: m.loanAsset.address
|
|
1600
|
+
};
|
|
1601
|
+
});
|
|
1602
|
+
} catch (e) {
|
|
1603
|
+
console.warn("[agether] searchMarkets failed:", e instanceof Error ? e.message : e);
|
|
1604
|
+
return [];
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Scan the AgentAccount wallet for all ERC-20 tokens that appear in Morpho
|
|
1609
|
+
* markets on the current chain. Returns tokens where balance > 0.
|
|
1610
|
+
*
|
|
1611
|
+
* Uses the full market list (500 markets) to discover all relevant tokens,
|
|
1612
|
+
* then checks on-chain balance for each unique token address.
|
|
1613
|
+
*
|
|
1614
|
+
* @returns Array of tokens with non-zero balance, sorted by balance descending.
|
|
1615
|
+
*/
|
|
1616
|
+
async getWalletTokenBalances() {
|
|
1617
|
+
const acctAddr = await this.getAccountAddress();
|
|
1618
|
+
await this.getMarkets();
|
|
1619
|
+
const uniqueTokens = /* @__PURE__ */ new Map();
|
|
1620
|
+
for (const [key, info] of this._tokenCache.entries()) {
|
|
1621
|
+
if (key.startsWith("0x") && !uniqueTokens.has(key)) {
|
|
1622
|
+
uniqueTokens.set(key, info);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
const results = [];
|
|
1626
|
+
try {
|
|
1627
|
+
const ethBalance = await this.provider.getBalance(acctAddr);
|
|
1628
|
+
if (ethBalance > 0n) {
|
|
1629
|
+
results.push({
|
|
1630
|
+
symbol: "ETH",
|
|
1631
|
+
address: ethers2.ZeroAddress,
|
|
1632
|
+
decimals: 18,
|
|
1633
|
+
balance: ethBalance,
|
|
1634
|
+
balanceFormatted: ethers2.formatEther(ethBalance)
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
} catch {
|
|
1638
|
+
}
|
|
1639
|
+
const tokenEntries = Array.from(uniqueTokens.values());
|
|
1640
|
+
const batchSize = 20;
|
|
1641
|
+
for (let i = 0; i < tokenEntries.length; i += batchSize) {
|
|
1642
|
+
const batch = tokenEntries.slice(i, i + batchSize);
|
|
1643
|
+
const checks = batch.map(async (info) => {
|
|
1644
|
+
try {
|
|
1645
|
+
const token = new Contract2(info.address, ERC20_ABI, this.provider);
|
|
1646
|
+
const balance = await token.balanceOf(acctAddr);
|
|
1647
|
+
if (balance > 0n) {
|
|
1648
|
+
return {
|
|
1649
|
+
symbol: info.symbol,
|
|
1650
|
+
address: info.address,
|
|
1651
|
+
decimals: info.decimals,
|
|
1652
|
+
balance,
|
|
1653
|
+
balanceFormatted: ethers2.formatUnits(balance, info.decimals)
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
} catch {
|
|
1657
|
+
}
|
|
1658
|
+
return null;
|
|
1659
|
+
});
|
|
1660
|
+
const batchResults = await Promise.all(checks);
|
|
1661
|
+
for (const r of batchResults) {
|
|
1662
|
+
if (r) results.push(r);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
results.sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
|
|
1666
|
+
return results;
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Find borrowing opportunities for the agent.
|
|
1670
|
+
*
|
|
1671
|
+
* - If `collateralSymbol` is provided: find all markets where that token is collateral.
|
|
1672
|
+
* - If omitted: scan wallet balances and find markets for each token the agent holds.
|
|
1673
|
+
*
|
|
1674
|
+
* Returns markets grouped by collateral token with APY, liquidity, and balance info.
|
|
1675
|
+
*
|
|
1676
|
+
* @param collateralSymbol - optional, e.g. 'WETH'. If omitted, scans wallet.
|
|
1677
|
+
*/
|
|
1678
|
+
async findBorrowingOptions(collateralSymbol) {
|
|
1679
|
+
let tokensToCheck;
|
|
1680
|
+
if (collateralSymbol) {
|
|
1681
|
+
tokensToCheck = [{ symbol: collateralSymbol, balanceFormatted: "N/A" }];
|
|
1682
|
+
try {
|
|
1683
|
+
const balance = await this.getTokenBalance(collateralSymbol);
|
|
1684
|
+
const info = await this._resolveToken(collateralSymbol);
|
|
1685
|
+
tokensToCheck = [{ symbol: collateralSymbol, balanceFormatted: ethers2.formatUnits(balance, info.decimals) }];
|
|
1686
|
+
} catch {
|
|
1687
|
+
}
|
|
1688
|
+
} else {
|
|
1689
|
+
const walletTokens = await this.getWalletTokenBalances();
|
|
1690
|
+
tokensToCheck = walletTokens.filter((t) => t.symbol !== "ETH").map((t) => ({ symbol: t.symbol, balanceFormatted: t.balanceFormatted }));
|
|
1691
|
+
if (tokensToCheck.length === 0) {
|
|
1692
|
+
return [];
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
const results = [];
|
|
1696
|
+
for (const token of tokensToCheck) {
|
|
1697
|
+
const markets = await this.searchMarkets(token.symbol, { asCollateral: true });
|
|
1698
|
+
if (markets.length === 0) continue;
|
|
1699
|
+
results.push({
|
|
1700
|
+
collateralToken: token.symbol,
|
|
1701
|
+
collateralBalance: token.balanceFormatted,
|
|
1702
|
+
markets: markets.map((m) => ({
|
|
1703
|
+
loanToken: m.loanToken,
|
|
1704
|
+
borrowApy: `${(m.borrowApy * 100).toFixed(2)}%`,
|
|
1705
|
+
supplyApy: `${(m.supplyApy * 100).toFixed(2)}%`,
|
|
1706
|
+
lltv: m.lltv,
|
|
1707
|
+
utilization: `${(m.utilization * 100).toFixed(1)}%`,
|
|
1708
|
+
availableLiquidity: `$${(m.totalSupplyUsd - m.totalBorrowUsd).toFixed(0)}`,
|
|
1709
|
+
marketId: m.marketId
|
|
1710
|
+
}))
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
return results;
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
* Find supply/lending opportunities for a specific loan token.
|
|
1717
|
+
*
|
|
1718
|
+
* "What can I supply to earn WETH?" → shows all markets where WETH is the loan token
|
|
1719
|
+
* (user supplies WETH to earn yield from borrowers).
|
|
1720
|
+
*
|
|
1721
|
+
* @param loanTokenSymbol - e.g. 'USDC', 'WETH'
|
|
1722
|
+
*/
|
|
1723
|
+
async findSupplyOptions(loanTokenSymbol) {
|
|
1724
|
+
const markets = await this.searchMarkets(loanTokenSymbol, { asLoanToken: true });
|
|
1725
|
+
return markets.map((m) => ({
|
|
1726
|
+
collateralToken: m.collateralToken,
|
|
1727
|
+
loanToken: m.loanToken,
|
|
1728
|
+
supplyApy: `${(m.supplyApy * 100).toFixed(2)}%`,
|
|
1729
|
+
borrowApy: `${(m.borrowApy * 100).toFixed(2)}%`,
|
|
1730
|
+
lltv: m.lltv,
|
|
1731
|
+
utilization: `${(m.utilization * 100).toFixed(1)}%`,
|
|
1732
|
+
totalSupply: `$${m.totalSupplyUsd.toFixed(0)}`,
|
|
1733
|
+
marketId: m.marketId
|
|
1734
|
+
}));
|
|
1735
|
+
}
|
|
1449
1736
|
/**
|
|
1450
1737
|
* Estimate theoretical yield for a given collateral amount over a period.
|
|
1451
1738
|
*
|
|
@@ -2350,6 +2637,46 @@ var MorphoClient = class {
|
|
|
2350
2637
|
/** Find the first market where the agent has collateral deposited. */
|
|
2351
2638
|
async _findActiveMarket() {
|
|
2352
2639
|
const acctAddr = await this.getAccountAddress();
|
|
2640
|
+
const chainId = this.config.chainId;
|
|
2641
|
+
try {
|
|
2642
|
+
const posQuery = `{
|
|
2643
|
+
marketPositions(
|
|
2644
|
+
where: { userAddress_in: ["${acctAddr}"], chainId_in: [${chainId}] }
|
|
2645
|
+
first: 50
|
|
2646
|
+
) {
|
|
2647
|
+
items {
|
|
2648
|
+
collateral
|
|
2649
|
+
market {
|
|
2650
|
+
uniqueKey
|
|
2651
|
+
oracleAddress
|
|
2652
|
+
irmAddress
|
|
2653
|
+
lltv
|
|
2654
|
+
loanAsset { address symbol decimals }
|
|
2655
|
+
collateralAsset { address symbol decimals }
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
}`;
|
|
2660
|
+
const resp = await axios.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
|
|
2661
|
+
const items = resp.data?.data?.marketPositions?.items ?? [];
|
|
2662
|
+
for (const item of items) {
|
|
2663
|
+
if (BigInt(item.collateral ?? "0") > 0n && item.market?.collateralAsset) {
|
|
2664
|
+
const m = item.market;
|
|
2665
|
+
return {
|
|
2666
|
+
params: {
|
|
2667
|
+
loanToken: m.loanAsset.address,
|
|
2668
|
+
collateralToken: m.collateralAsset.address,
|
|
2669
|
+
oracle: m.oracleAddress,
|
|
2670
|
+
irm: m.irmAddress,
|
|
2671
|
+
lltv: BigInt(m.lltv)
|
|
2672
|
+
},
|
|
2673
|
+
symbol: m.collateralAsset.symbol
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
} catch (e) {
|
|
2678
|
+
console.warn("[agether] _findActiveMarket GraphQL failed, falling back:", e instanceof Error ? e.message : e);
|
|
2679
|
+
}
|
|
2353
2680
|
const markets = await this.getMarkets();
|
|
2354
2681
|
for (const m of markets) {
|
|
2355
2682
|
if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
|
|
@@ -2378,6 +2705,46 @@ var MorphoClient = class {
|
|
|
2378
2705
|
/** Find the first market where the agent has a supply (lending) position. */
|
|
2379
2706
|
async _findActiveSupplyMarket() {
|
|
2380
2707
|
const acctAddr = await this.getAccountAddress();
|
|
2708
|
+
const chainId = this.config.chainId;
|
|
2709
|
+
try {
|
|
2710
|
+
const posQuery = `{
|
|
2711
|
+
marketPositions(
|
|
2712
|
+
where: { userAddress_in: ["${acctAddr}"], chainId_in: [${chainId}] }
|
|
2713
|
+
first: 50
|
|
2714
|
+
) {
|
|
2715
|
+
items {
|
|
2716
|
+
supplyShares
|
|
2717
|
+
market {
|
|
2718
|
+
uniqueKey
|
|
2719
|
+
oracleAddress
|
|
2720
|
+
irmAddress
|
|
2721
|
+
lltv
|
|
2722
|
+
loanAsset { address symbol decimals }
|
|
2723
|
+
collateralAsset { address symbol decimals }
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}`;
|
|
2728
|
+
const resp = await axios.post(MORPHO_API_URL, { query: posQuery }, { timeout: 1e4 });
|
|
2729
|
+
const items = resp.data?.data?.marketPositions?.items ?? [];
|
|
2730
|
+
for (const item of items) {
|
|
2731
|
+
if (BigInt(item.supplyShares ?? "0") > 0n && item.market?.collateralAsset) {
|
|
2732
|
+
const m = item.market;
|
|
2733
|
+
return {
|
|
2734
|
+
params: {
|
|
2735
|
+
loanToken: m.loanAsset.address,
|
|
2736
|
+
collateralToken: m.collateralAsset.address,
|
|
2737
|
+
oracle: m.oracleAddress,
|
|
2738
|
+
irm: m.irmAddress,
|
|
2739
|
+
lltv: BigInt(m.lltv)
|
|
2740
|
+
},
|
|
2741
|
+
symbol: m.collateralAsset.symbol
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
} catch (e) {
|
|
2746
|
+
console.warn("[agether] _findActiveSupplyMarket GraphQL failed, falling back:", e instanceof Error ? e.message : e);
|
|
2747
|
+
}
|
|
2381
2748
|
const markets = await this.getMarkets();
|
|
2382
2749
|
for (const m of markets) {
|
|
2383
2750
|
if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
|
|
@@ -2448,6 +2815,24 @@ var MorphoClient = class {
|
|
|
2448
2815
|
await this.getMarkets();
|
|
2449
2816
|
const fromApi = this._tokenCache.get(key);
|
|
2450
2817
|
if (fromApi) return fromApi;
|
|
2818
|
+
if (!symbolOrAddress.startsWith("0x")) {
|
|
2819
|
+
const searchResults = await this.searchMarkets(symbolOrAddress);
|
|
2820
|
+
const sym = symbolOrAddress.toUpperCase();
|
|
2821
|
+
for (const m of searchResults) {
|
|
2822
|
+
if (m.collateralToken.toUpperCase() === sym) {
|
|
2823
|
+
const info = { address: m.collateralAddress, symbol: m.collateralToken, decimals: m.collateralDecimals };
|
|
2824
|
+
this._tokenCache.set(sym, info);
|
|
2825
|
+
this._tokenCache.set(m.collateralAddress.toLowerCase(), info);
|
|
2826
|
+
return info;
|
|
2827
|
+
}
|
|
2828
|
+
if (m.loanToken.toUpperCase() === sym) {
|
|
2829
|
+
const info = { address: m.loanAddress, symbol: m.loanToken, decimals: m.loanDecimals };
|
|
2830
|
+
this._tokenCache.set(sym, info);
|
|
2831
|
+
this._tokenCache.set(m.loanAddress.toLowerCase(), info);
|
|
2832
|
+
return info;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2451
2836
|
throw new AgetherError(
|
|
2452
2837
|
`Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
|
|
2453
2838
|
"UNKNOWN_TOKEN"
|