@agether/sdk 2.13.0 → 2.14.0

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/index.js CHANGED
@@ -1155,7 +1155,7 @@ var MorphoClient = class {
1155
1155
  const chainId = this.config.chainId;
1156
1156
  const query = `{
1157
1157
  markets(
1158
- first: 50
1158
+ first: 500
1159
1159
  orderBy: SupplyAssetsUsd
1160
1160
  orderDirection: Desc
1161
1161
  where: { chainId_in: [${chainId}] }
@@ -1277,6 +1277,14 @@ var MorphoClient = class {
1277
1277
  lltv: m.lltv
1278
1278
  };
1279
1279
  }
1280
+ if (!collateralSymbolOrAddress.startsWith("0x")) {
1281
+ const searched = await this.searchMarkets(collateralSymbolOrAddress, { asCollateral: true });
1282
+ for (const m of searched) {
1283
+ if (loanAddr && m.loanAddress.toLowerCase() !== loanAddr) continue;
1284
+ if (loanTokenSymbolOrAddress && !loanTokenSymbolOrAddress.startsWith("0x") && m.loanToken.toUpperCase() !== loanTokenSymbolOrAddress.toUpperCase()) continue;
1285
+ return this.getMarketParams(m.marketId);
1286
+ }
1287
+ }
1280
1288
  throw new AgetherError(
1281
1289
  `No Morpho market found for collateral ${collateralSymbolOrAddress}` + (loanTokenSymbolOrAddress ? ` with loan token ${loanTokenSymbolOrAddress}` : ""),
1282
1290
  "MARKET_NOT_FOUND"
@@ -1447,41 +1455,39 @@ var MorphoClient = class {
1447
1455
  async getMarketRates(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
1448
1456
  const chainId = this.config.chainId;
1449
1457
  let collateralFilter = "";
1458
+ let loanFilter = "";
1459
+ let searchTerm = "";
1450
1460
  if (collateralSymbolOrAddress) {
1451
- let colAddr;
1452
1461
  if (collateralSymbolOrAddress.startsWith("0x")) {
1453
- colAddr = collateralSymbolOrAddress.toLowerCase();
1462
+ collateralFilter = `, collateralAssetAddress_in: ["${collateralSymbolOrAddress.toLowerCase()}"]`;
1454
1463
  } else {
1455
- try {
1456
- const resolved = await this._resolveToken(collateralSymbolOrAddress);
1457
- colAddr = resolved.address.toLowerCase();
1458
- } catch {
1459
- colAddr = collateralSymbolOrAddress.toLowerCase();
1464
+ const cached = this._tokenCache.get(collateralSymbolOrAddress.toUpperCase());
1465
+ if (cached) {
1466
+ collateralFilter = `, collateralAssetAddress_in: ["${cached.address.toLowerCase()}"]`;
1467
+ } else {
1468
+ searchTerm = collateralSymbolOrAddress;
1460
1469
  }
1461
1470
  }
1462
- collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
1463
1471
  }
1464
- let loanFilter = "";
1465
1472
  if (loanTokenSymbolOrAddress) {
1466
- let loanAddr;
1467
1473
  if (loanTokenSymbolOrAddress.startsWith("0x")) {
1468
- loanAddr = loanTokenSymbolOrAddress.toLowerCase();
1474
+ loanFilter = `, loanAssetAddress_in: ["${loanTokenSymbolOrAddress.toLowerCase()}"]`;
1469
1475
  } else {
1470
- try {
1471
- const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
1472
- loanAddr = resolved.address.toLowerCase();
1473
- } catch {
1474
- loanAddr = loanTokenSymbolOrAddress.toLowerCase();
1476
+ const cached = this._tokenCache.get(loanTokenSymbolOrAddress.toUpperCase());
1477
+ if (cached) {
1478
+ loanFilter = `, loanAssetAddress_in: ["${cached.address.toLowerCase()}"]`;
1479
+ } else {
1480
+ searchTerm = searchTerm || loanTokenSymbolOrAddress;
1475
1481
  }
1476
1482
  }
1477
- loanFilter = `, loanAssetAddress_in: ["${loanAddr}"]`;
1478
1483
  }
1484
+ const searchClause = searchTerm ? `, search: "${searchTerm}"` : "";
1479
1485
  const query = `{
1480
1486
  markets(
1481
- first: 50
1487
+ first: 100
1482
1488
  orderBy: SupplyAssetsUsd
1483
1489
  orderDirection: Desc
1484
- where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter} }
1490
+ where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter}${searchClause} }
1485
1491
  ) {
1486
1492
  items {
1487
1493
  uniqueKey
@@ -1500,7 +1506,15 @@ var MorphoClient = class {
1500
1506
  }`;
1501
1507
  try {
1502
1508
  const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1503
- const items = resp.data?.data?.markets?.items ?? [];
1509
+ let items = resp.data?.data?.markets?.items ?? [];
1510
+ if (searchTerm && collateralSymbolOrAddress && !collateralSymbolOrAddress.startsWith("0x")) {
1511
+ const sym = collateralSymbolOrAddress.toUpperCase();
1512
+ items = items.filter((m) => m.collateralAsset?.symbol?.toUpperCase() === sym);
1513
+ }
1514
+ if (searchTerm && loanTokenSymbolOrAddress && !loanTokenSymbolOrAddress.startsWith("0x")) {
1515
+ const sym = loanTokenSymbolOrAddress.toUpperCase();
1516
+ items = items.filter((m) => m.loanAsset?.symbol?.toUpperCase() === sym);
1517
+ }
1504
1518
  return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers2.ethers.ZeroAddress).map((m) => {
1505
1519
  const loanDecimals = m.loanAsset?.decimals ?? 18;
1506
1520
  return {
@@ -1521,6 +1535,205 @@ var MorphoClient = class {
1521
1535
  return [];
1522
1536
  }
1523
1537
  }
1538
+ // ════════════════════════════════════════════════════════
1539
+ // Market Search & Wallet Discovery
1540
+ // ════════════════════════════════════════════════════════
1541
+ /**
1542
+ * Search Morpho markets by token name using the Morpho GraphQL API `search` field.
1543
+ * No hardcoded token lists — uses Morpho's built-in fuzzy search.
1544
+ *
1545
+ * @param search - token name or symbol (e.g. 'WETH', 'staked ETH', 'ezETH')
1546
+ * @param options.asCollateral - only return markets where the searched token is collateral
1547
+ * @param options.asLoanToken - only return markets where the searched token is the loan asset
1548
+ */
1549
+ async searchMarkets(search, options) {
1550
+ const chainId = this.config.chainId;
1551
+ const query = `{
1552
+ markets(
1553
+ first: 100
1554
+ orderBy: SupplyAssetsUsd
1555
+ orderDirection: Desc
1556
+ where: { chainId_in: [${chainId}], search: "${search}" }
1557
+ ) {
1558
+ items {
1559
+ uniqueKey
1560
+ lltv
1561
+ loanAsset { address symbol decimals }
1562
+ collateralAsset { address symbol decimals }
1563
+ state {
1564
+ borrowAssets
1565
+ supplyAssets
1566
+ utilization
1567
+ supplyApy
1568
+ borrowApy
1569
+ }
1570
+ }
1571
+ }
1572
+ }`;
1573
+ try {
1574
+ const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1575
+ let items = resp.data?.data?.markets?.items ?? [];
1576
+ items = items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers2.ethers.ZeroAddress);
1577
+ const searchUpper = search.toUpperCase();
1578
+ if (options?.asCollateral) {
1579
+ items = items.filter((m) => m.collateralAsset?.symbol?.toUpperCase() === searchUpper);
1580
+ }
1581
+ if (options?.asLoanToken) {
1582
+ items = items.filter((m) => m.loanAsset?.symbol?.toUpperCase() === searchUpper);
1583
+ }
1584
+ return items.map((m) => {
1585
+ const loanDecimals = m.loanAsset?.decimals ?? 18;
1586
+ const collateralDecimals = m.collateralAsset?.decimals ?? 18;
1587
+ return {
1588
+ collateralToken: m.collateralAsset.symbol,
1589
+ loanToken: m.loanAsset.symbol,
1590
+ loanDecimals,
1591
+ collateralDecimals,
1592
+ supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
1593
+ borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
1594
+ utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
1595
+ totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 10 ** loanDecimals : 0,
1596
+ totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 10 ** loanDecimals : 0,
1597
+ lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
1598
+ marketId: m.uniqueKey,
1599
+ collateralAddress: m.collateralAsset.address,
1600
+ loanAddress: m.loanAsset.address
1601
+ };
1602
+ });
1603
+ } catch (e) {
1604
+ console.warn("[agether] searchMarkets failed:", e instanceof Error ? e.message : e);
1605
+ return [];
1606
+ }
1607
+ }
1608
+ /**
1609
+ * Scan the AgentAccount wallet for all ERC-20 tokens that appear in Morpho
1610
+ * markets on the current chain. Returns tokens where balance > 0.
1611
+ *
1612
+ * Uses the full market list (500 markets) to discover all relevant tokens,
1613
+ * then checks on-chain balance for each unique token address.
1614
+ *
1615
+ * @returns Array of tokens with non-zero balance, sorted by balance descending.
1616
+ */
1617
+ async getWalletTokenBalances() {
1618
+ const acctAddr = await this.getAccountAddress();
1619
+ await this.getMarkets();
1620
+ const uniqueTokens = /* @__PURE__ */ new Map();
1621
+ for (const [key, info] of this._tokenCache.entries()) {
1622
+ if (key.startsWith("0x") && !uniqueTokens.has(key)) {
1623
+ uniqueTokens.set(key, info);
1624
+ }
1625
+ }
1626
+ const results = [];
1627
+ try {
1628
+ const ethBalance = await this.provider.getBalance(acctAddr);
1629
+ if (ethBalance > 0n) {
1630
+ results.push({
1631
+ symbol: "ETH",
1632
+ address: import_ethers2.ethers.ZeroAddress,
1633
+ decimals: 18,
1634
+ balance: ethBalance,
1635
+ balanceFormatted: import_ethers2.ethers.formatEther(ethBalance)
1636
+ });
1637
+ }
1638
+ } catch {
1639
+ }
1640
+ const tokenEntries = Array.from(uniqueTokens.values());
1641
+ const batchSize = 20;
1642
+ for (let i = 0; i < tokenEntries.length; i += batchSize) {
1643
+ const batch = tokenEntries.slice(i, i + batchSize);
1644
+ const checks = batch.map(async (info) => {
1645
+ try {
1646
+ const token = new import_ethers2.Contract(info.address, ERC20_ABI, this.provider);
1647
+ const balance = await token.balanceOf(acctAddr);
1648
+ if (balance > 0n) {
1649
+ return {
1650
+ symbol: info.symbol,
1651
+ address: info.address,
1652
+ decimals: info.decimals,
1653
+ balance,
1654
+ balanceFormatted: import_ethers2.ethers.formatUnits(balance, info.decimals)
1655
+ };
1656
+ }
1657
+ } catch {
1658
+ }
1659
+ return null;
1660
+ });
1661
+ const batchResults = await Promise.all(checks);
1662
+ for (const r of batchResults) {
1663
+ if (r) results.push(r);
1664
+ }
1665
+ }
1666
+ results.sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
1667
+ return results;
1668
+ }
1669
+ /**
1670
+ * Find borrowing opportunities for the agent.
1671
+ *
1672
+ * - If `collateralSymbol` is provided: find all markets where that token is collateral.
1673
+ * - If omitted: scan wallet balances and find markets for each token the agent holds.
1674
+ *
1675
+ * Returns markets grouped by collateral token with APY, liquidity, and balance info.
1676
+ *
1677
+ * @param collateralSymbol - optional, e.g. 'WETH'. If omitted, scans wallet.
1678
+ */
1679
+ async findBorrowingOptions(collateralSymbol) {
1680
+ let tokensToCheck;
1681
+ if (collateralSymbol) {
1682
+ tokensToCheck = [{ symbol: collateralSymbol, balanceFormatted: "N/A" }];
1683
+ try {
1684
+ const balance = await this.getTokenBalance(collateralSymbol);
1685
+ const info = await this._resolveToken(collateralSymbol);
1686
+ tokensToCheck = [{ symbol: collateralSymbol, balanceFormatted: import_ethers2.ethers.formatUnits(balance, info.decimals) }];
1687
+ } catch {
1688
+ }
1689
+ } else {
1690
+ const walletTokens = await this.getWalletTokenBalances();
1691
+ tokensToCheck = walletTokens.filter((t) => t.symbol !== "ETH").map((t) => ({ symbol: t.symbol, balanceFormatted: t.balanceFormatted }));
1692
+ if (tokensToCheck.length === 0) {
1693
+ return [];
1694
+ }
1695
+ }
1696
+ const results = [];
1697
+ for (const token of tokensToCheck) {
1698
+ const markets = await this.searchMarkets(token.symbol, { asCollateral: true });
1699
+ if (markets.length === 0) continue;
1700
+ results.push({
1701
+ collateralToken: token.symbol,
1702
+ collateralBalance: token.balanceFormatted,
1703
+ markets: markets.map((m) => ({
1704
+ loanToken: m.loanToken,
1705
+ borrowApy: `${(m.borrowApy * 100).toFixed(2)}%`,
1706
+ supplyApy: `${(m.supplyApy * 100).toFixed(2)}%`,
1707
+ lltv: m.lltv,
1708
+ utilization: `${(m.utilization * 100).toFixed(1)}%`,
1709
+ availableLiquidity: `$${(m.totalSupplyUsd - m.totalBorrowUsd).toFixed(0)}`,
1710
+ marketId: m.marketId
1711
+ }))
1712
+ });
1713
+ }
1714
+ return results;
1715
+ }
1716
+ /**
1717
+ * Find supply/lending opportunities for a specific loan token.
1718
+ *
1719
+ * "What can I supply to earn WETH?" → shows all markets where WETH is the loan token
1720
+ * (user supplies WETH to earn yield from borrowers).
1721
+ *
1722
+ * @param loanTokenSymbol - e.g. 'USDC', 'WETH'
1723
+ */
1724
+ async findSupplyOptions(loanTokenSymbol) {
1725
+ const markets = await this.searchMarkets(loanTokenSymbol, { asLoanToken: true });
1726
+ return markets.map((m) => ({
1727
+ collateralToken: m.collateralToken,
1728
+ loanToken: m.loanToken,
1729
+ supplyApy: `${(m.supplyApy * 100).toFixed(2)}%`,
1730
+ borrowApy: `${(m.borrowApy * 100).toFixed(2)}%`,
1731
+ lltv: m.lltv,
1732
+ utilization: `${(m.utilization * 100).toFixed(1)}%`,
1733
+ totalSupply: `$${m.totalSupplyUsd.toFixed(0)}`,
1734
+ marketId: m.marketId
1735
+ }));
1736
+ }
1524
1737
  /**
1525
1738
  * Estimate theoretical yield for a given collateral amount over a period.
1526
1739
  *
@@ -2523,6 +2736,24 @@ var MorphoClient = class {
2523
2736
  await this.getMarkets();
2524
2737
  const fromApi = this._tokenCache.get(key);
2525
2738
  if (fromApi) return fromApi;
2739
+ if (!symbolOrAddress.startsWith("0x")) {
2740
+ const searchResults = await this.searchMarkets(symbolOrAddress);
2741
+ const sym = symbolOrAddress.toUpperCase();
2742
+ for (const m of searchResults) {
2743
+ if (m.collateralToken.toUpperCase() === sym) {
2744
+ const info = { address: m.collateralAddress, symbol: m.collateralToken, decimals: m.collateralDecimals };
2745
+ this._tokenCache.set(sym, info);
2746
+ this._tokenCache.set(m.collateralAddress.toLowerCase(), info);
2747
+ return info;
2748
+ }
2749
+ if (m.loanToken.toUpperCase() === sym) {
2750
+ const info = { address: m.loanAddress, symbol: m.loanToken, decimals: m.loanDecimals };
2751
+ this._tokenCache.set(sym, info);
2752
+ this._tokenCache.set(m.loanAddress.toLowerCase(), info);
2753
+ return info;
2754
+ }
2755
+ }
2756
+ }
2526
2757
  throw new AgetherError(
2527
2758
  `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
2528
2759
  "UNKNOWN_TOKEN"
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: 50
1083
+ first: 500
1084
1084
  orderBy: SupplyAssetsUsd
1085
1085
  orderDirection: Desc
1086
1086
  where: { chainId_in: [${chainId}] }
@@ -1202,6 +1202,14 @@ var MorphoClient = class {
1202
1202
  lltv: m.lltv
1203
1203
  };
1204
1204
  }
1205
+ if (!collateralSymbolOrAddress.startsWith("0x")) {
1206
+ const searched = await this.searchMarkets(collateralSymbolOrAddress, { asCollateral: true });
1207
+ for (const m of searched) {
1208
+ if (loanAddr && m.loanAddress.toLowerCase() !== loanAddr) continue;
1209
+ if (loanTokenSymbolOrAddress && !loanTokenSymbolOrAddress.startsWith("0x") && m.loanToken.toUpperCase() !== loanTokenSymbolOrAddress.toUpperCase()) continue;
1210
+ return this.getMarketParams(m.marketId);
1211
+ }
1212
+ }
1205
1213
  throw new AgetherError(
1206
1214
  `No Morpho market found for collateral ${collateralSymbolOrAddress}` + (loanTokenSymbolOrAddress ? ` with loan token ${loanTokenSymbolOrAddress}` : ""),
1207
1215
  "MARKET_NOT_FOUND"
@@ -1372,41 +1380,39 @@ var MorphoClient = class {
1372
1380
  async getMarketRates(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
1373
1381
  const chainId = this.config.chainId;
1374
1382
  let collateralFilter = "";
1383
+ let loanFilter = "";
1384
+ let searchTerm = "";
1375
1385
  if (collateralSymbolOrAddress) {
1376
- let colAddr;
1377
1386
  if (collateralSymbolOrAddress.startsWith("0x")) {
1378
- colAddr = collateralSymbolOrAddress.toLowerCase();
1387
+ collateralFilter = `, collateralAssetAddress_in: ["${collateralSymbolOrAddress.toLowerCase()}"]`;
1379
1388
  } else {
1380
- try {
1381
- const resolved = await this._resolveToken(collateralSymbolOrAddress);
1382
- colAddr = resolved.address.toLowerCase();
1383
- } catch {
1384
- colAddr = collateralSymbolOrAddress.toLowerCase();
1389
+ const cached = this._tokenCache.get(collateralSymbolOrAddress.toUpperCase());
1390
+ if (cached) {
1391
+ collateralFilter = `, collateralAssetAddress_in: ["${cached.address.toLowerCase()}"]`;
1392
+ } else {
1393
+ searchTerm = collateralSymbolOrAddress;
1385
1394
  }
1386
1395
  }
1387
- collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
1388
1396
  }
1389
- let loanFilter = "";
1390
1397
  if (loanTokenSymbolOrAddress) {
1391
- let loanAddr;
1392
1398
  if (loanTokenSymbolOrAddress.startsWith("0x")) {
1393
- loanAddr = loanTokenSymbolOrAddress.toLowerCase();
1399
+ loanFilter = `, loanAssetAddress_in: ["${loanTokenSymbolOrAddress.toLowerCase()}"]`;
1394
1400
  } else {
1395
- try {
1396
- const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
1397
- loanAddr = resolved.address.toLowerCase();
1398
- } catch {
1399
- loanAddr = loanTokenSymbolOrAddress.toLowerCase();
1401
+ const cached = this._tokenCache.get(loanTokenSymbolOrAddress.toUpperCase());
1402
+ if (cached) {
1403
+ loanFilter = `, loanAssetAddress_in: ["${cached.address.toLowerCase()}"]`;
1404
+ } else {
1405
+ searchTerm = searchTerm || loanTokenSymbolOrAddress;
1400
1406
  }
1401
1407
  }
1402
- loanFilter = `, loanAssetAddress_in: ["${loanAddr}"]`;
1403
1408
  }
1409
+ const searchClause = searchTerm ? `, search: "${searchTerm}"` : "";
1404
1410
  const query = `{
1405
1411
  markets(
1406
- first: 50
1412
+ first: 100
1407
1413
  orderBy: SupplyAssetsUsd
1408
1414
  orderDirection: Desc
1409
- where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter} }
1415
+ where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter}${searchClause} }
1410
1416
  ) {
1411
1417
  items {
1412
1418
  uniqueKey
@@ -1425,7 +1431,15 @@ var MorphoClient = class {
1425
1431
  }`;
1426
1432
  try {
1427
1433
  const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1428
- const items = resp.data?.data?.markets?.items ?? [];
1434
+ let items = resp.data?.data?.markets?.items ?? [];
1435
+ if (searchTerm && collateralSymbolOrAddress && !collateralSymbolOrAddress.startsWith("0x")) {
1436
+ const sym = collateralSymbolOrAddress.toUpperCase();
1437
+ items = items.filter((m) => m.collateralAsset?.symbol?.toUpperCase() === sym);
1438
+ }
1439
+ if (searchTerm && loanTokenSymbolOrAddress && !loanTokenSymbolOrAddress.startsWith("0x")) {
1440
+ const sym = loanTokenSymbolOrAddress.toUpperCase();
1441
+ items = items.filter((m) => m.loanAsset?.symbol?.toUpperCase() === sym);
1442
+ }
1429
1443
  return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress).map((m) => {
1430
1444
  const loanDecimals = m.loanAsset?.decimals ?? 18;
1431
1445
  return {
@@ -1446,6 +1460,205 @@ var MorphoClient = class {
1446
1460
  return [];
1447
1461
  }
1448
1462
  }
1463
+ // ════════════════════════════════════════════════════════
1464
+ // Market Search & Wallet Discovery
1465
+ // ════════════════════════════════════════════════════════
1466
+ /**
1467
+ * Search Morpho markets by token name using the Morpho GraphQL API `search` field.
1468
+ * No hardcoded token lists — uses Morpho's built-in fuzzy search.
1469
+ *
1470
+ * @param search - token name or symbol (e.g. 'WETH', 'staked ETH', 'ezETH')
1471
+ * @param options.asCollateral - only return markets where the searched token is collateral
1472
+ * @param options.asLoanToken - only return markets where the searched token is the loan asset
1473
+ */
1474
+ async searchMarkets(search, options) {
1475
+ const chainId = this.config.chainId;
1476
+ const query = `{
1477
+ markets(
1478
+ first: 100
1479
+ orderBy: SupplyAssetsUsd
1480
+ orderDirection: Desc
1481
+ where: { chainId_in: [${chainId}], search: "${search}" }
1482
+ ) {
1483
+ items {
1484
+ uniqueKey
1485
+ lltv
1486
+ loanAsset { address symbol decimals }
1487
+ collateralAsset { address symbol decimals }
1488
+ state {
1489
+ borrowAssets
1490
+ supplyAssets
1491
+ utilization
1492
+ supplyApy
1493
+ borrowApy
1494
+ }
1495
+ }
1496
+ }
1497
+ }`;
1498
+ try {
1499
+ const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1500
+ let items = resp.data?.data?.markets?.items ?? [];
1501
+ items = items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress);
1502
+ const searchUpper = search.toUpperCase();
1503
+ if (options?.asCollateral) {
1504
+ items = items.filter((m) => m.collateralAsset?.symbol?.toUpperCase() === searchUpper);
1505
+ }
1506
+ if (options?.asLoanToken) {
1507
+ items = items.filter((m) => m.loanAsset?.symbol?.toUpperCase() === searchUpper);
1508
+ }
1509
+ return items.map((m) => {
1510
+ const loanDecimals = m.loanAsset?.decimals ?? 18;
1511
+ const collateralDecimals = m.collateralAsset?.decimals ?? 18;
1512
+ return {
1513
+ collateralToken: m.collateralAsset.symbol,
1514
+ loanToken: m.loanAsset.symbol,
1515
+ loanDecimals,
1516
+ collateralDecimals,
1517
+ supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
1518
+ borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
1519
+ utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
1520
+ totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 10 ** loanDecimals : 0,
1521
+ totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 10 ** loanDecimals : 0,
1522
+ lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
1523
+ marketId: m.uniqueKey,
1524
+ collateralAddress: m.collateralAsset.address,
1525
+ loanAddress: m.loanAsset.address
1526
+ };
1527
+ });
1528
+ } catch (e) {
1529
+ console.warn("[agether] searchMarkets failed:", e instanceof Error ? e.message : e);
1530
+ return [];
1531
+ }
1532
+ }
1533
+ /**
1534
+ * Scan the AgentAccount wallet for all ERC-20 tokens that appear in Morpho
1535
+ * markets on the current chain. Returns tokens where balance > 0.
1536
+ *
1537
+ * Uses the full market list (500 markets) to discover all relevant tokens,
1538
+ * then checks on-chain balance for each unique token address.
1539
+ *
1540
+ * @returns Array of tokens with non-zero balance, sorted by balance descending.
1541
+ */
1542
+ async getWalletTokenBalances() {
1543
+ const acctAddr = await this.getAccountAddress();
1544
+ await this.getMarkets();
1545
+ const uniqueTokens = /* @__PURE__ */ new Map();
1546
+ for (const [key, info] of this._tokenCache.entries()) {
1547
+ if (key.startsWith("0x") && !uniqueTokens.has(key)) {
1548
+ uniqueTokens.set(key, info);
1549
+ }
1550
+ }
1551
+ const results = [];
1552
+ try {
1553
+ const ethBalance = await this.provider.getBalance(acctAddr);
1554
+ if (ethBalance > 0n) {
1555
+ results.push({
1556
+ symbol: "ETH",
1557
+ address: ethers2.ZeroAddress,
1558
+ decimals: 18,
1559
+ balance: ethBalance,
1560
+ balanceFormatted: ethers2.formatEther(ethBalance)
1561
+ });
1562
+ }
1563
+ } catch {
1564
+ }
1565
+ const tokenEntries = Array.from(uniqueTokens.values());
1566
+ const batchSize = 20;
1567
+ for (let i = 0; i < tokenEntries.length; i += batchSize) {
1568
+ const batch = tokenEntries.slice(i, i + batchSize);
1569
+ const checks = batch.map(async (info) => {
1570
+ try {
1571
+ const token = new Contract2(info.address, ERC20_ABI, this.provider);
1572
+ const balance = await token.balanceOf(acctAddr);
1573
+ if (balance > 0n) {
1574
+ return {
1575
+ symbol: info.symbol,
1576
+ address: info.address,
1577
+ decimals: info.decimals,
1578
+ balance,
1579
+ balanceFormatted: ethers2.formatUnits(balance, info.decimals)
1580
+ };
1581
+ }
1582
+ } catch {
1583
+ }
1584
+ return null;
1585
+ });
1586
+ const batchResults = await Promise.all(checks);
1587
+ for (const r of batchResults) {
1588
+ if (r) results.push(r);
1589
+ }
1590
+ }
1591
+ results.sort((a, b) => b.balance > a.balance ? 1 : b.balance < a.balance ? -1 : 0);
1592
+ return results;
1593
+ }
1594
+ /**
1595
+ * Find borrowing opportunities for the agent.
1596
+ *
1597
+ * - If `collateralSymbol` is provided: find all markets where that token is collateral.
1598
+ * - If omitted: scan wallet balances and find markets for each token the agent holds.
1599
+ *
1600
+ * Returns markets grouped by collateral token with APY, liquidity, and balance info.
1601
+ *
1602
+ * @param collateralSymbol - optional, e.g. 'WETH'. If omitted, scans wallet.
1603
+ */
1604
+ async findBorrowingOptions(collateralSymbol) {
1605
+ let tokensToCheck;
1606
+ if (collateralSymbol) {
1607
+ tokensToCheck = [{ symbol: collateralSymbol, balanceFormatted: "N/A" }];
1608
+ try {
1609
+ const balance = await this.getTokenBalance(collateralSymbol);
1610
+ const info = await this._resolveToken(collateralSymbol);
1611
+ tokensToCheck = [{ symbol: collateralSymbol, balanceFormatted: ethers2.formatUnits(balance, info.decimals) }];
1612
+ } catch {
1613
+ }
1614
+ } else {
1615
+ const walletTokens = await this.getWalletTokenBalances();
1616
+ tokensToCheck = walletTokens.filter((t) => t.symbol !== "ETH").map((t) => ({ symbol: t.symbol, balanceFormatted: t.balanceFormatted }));
1617
+ if (tokensToCheck.length === 0) {
1618
+ return [];
1619
+ }
1620
+ }
1621
+ const results = [];
1622
+ for (const token of tokensToCheck) {
1623
+ const markets = await this.searchMarkets(token.symbol, { asCollateral: true });
1624
+ if (markets.length === 0) continue;
1625
+ results.push({
1626
+ collateralToken: token.symbol,
1627
+ collateralBalance: token.balanceFormatted,
1628
+ markets: markets.map((m) => ({
1629
+ loanToken: m.loanToken,
1630
+ borrowApy: `${(m.borrowApy * 100).toFixed(2)}%`,
1631
+ supplyApy: `${(m.supplyApy * 100).toFixed(2)}%`,
1632
+ lltv: m.lltv,
1633
+ utilization: `${(m.utilization * 100).toFixed(1)}%`,
1634
+ availableLiquidity: `$${(m.totalSupplyUsd - m.totalBorrowUsd).toFixed(0)}`,
1635
+ marketId: m.marketId
1636
+ }))
1637
+ });
1638
+ }
1639
+ return results;
1640
+ }
1641
+ /**
1642
+ * Find supply/lending opportunities for a specific loan token.
1643
+ *
1644
+ * "What can I supply to earn WETH?" → shows all markets where WETH is the loan token
1645
+ * (user supplies WETH to earn yield from borrowers).
1646
+ *
1647
+ * @param loanTokenSymbol - e.g. 'USDC', 'WETH'
1648
+ */
1649
+ async findSupplyOptions(loanTokenSymbol) {
1650
+ const markets = await this.searchMarkets(loanTokenSymbol, { asLoanToken: true });
1651
+ return markets.map((m) => ({
1652
+ collateralToken: m.collateralToken,
1653
+ loanToken: m.loanToken,
1654
+ supplyApy: `${(m.supplyApy * 100).toFixed(2)}%`,
1655
+ borrowApy: `${(m.borrowApy * 100).toFixed(2)}%`,
1656
+ lltv: m.lltv,
1657
+ utilization: `${(m.utilization * 100).toFixed(1)}%`,
1658
+ totalSupply: `$${m.totalSupplyUsd.toFixed(0)}`,
1659
+ marketId: m.marketId
1660
+ }));
1661
+ }
1449
1662
  /**
1450
1663
  * Estimate theoretical yield for a given collateral amount over a period.
1451
1664
  *
@@ -2448,6 +2661,24 @@ var MorphoClient = class {
2448
2661
  await this.getMarkets();
2449
2662
  const fromApi = this._tokenCache.get(key);
2450
2663
  if (fromApi) return fromApi;
2664
+ if (!symbolOrAddress.startsWith("0x")) {
2665
+ const searchResults = await this.searchMarkets(symbolOrAddress);
2666
+ const sym = symbolOrAddress.toUpperCase();
2667
+ for (const m of searchResults) {
2668
+ if (m.collateralToken.toUpperCase() === sym) {
2669
+ const info = { address: m.collateralAddress, symbol: m.collateralToken, decimals: m.collateralDecimals };
2670
+ this._tokenCache.set(sym, info);
2671
+ this._tokenCache.set(m.collateralAddress.toLowerCase(), info);
2672
+ return info;
2673
+ }
2674
+ if (m.loanToken.toUpperCase() === sym) {
2675
+ const info = { address: m.loanAddress, symbol: m.loanToken, decimals: m.loanDecimals };
2676
+ this._tokenCache.set(sym, info);
2677
+ this._tokenCache.set(m.loanAddress.toLowerCase(), info);
2678
+ return info;
2679
+ }
2680
+ }
2681
+ }
2451
2682
  throw new AgetherError(
2452
2683
  `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
2453
2684
  "UNKNOWN_TOKEN"