@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/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +252 -21
- 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 +83 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +252 -21
- package/dist/index.mjs +252 -21
- 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.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:
|
|
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
|
-
|
|
1462
|
+
collateralFilter = `, collateralAssetAddress_in: ["${collateralSymbolOrAddress.toLowerCase()}"]`;
|
|
1454
1463
|
} else {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
}
|
|
1459
|
-
|
|
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
|
-
|
|
1474
|
+
loanFilter = `, loanAssetAddress_in: ["${loanTokenSymbolOrAddress.toLowerCase()}"]`;
|
|
1469
1475
|
} else {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
}
|
|
1474
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
1387
|
+
collateralFilter = `, collateralAssetAddress_in: ["${collateralSymbolOrAddress.toLowerCase()}"]`;
|
|
1379
1388
|
} else {
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1384
|
-
|
|
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
|
-
|
|
1399
|
+
loanFilter = `, loanAssetAddress_in: ["${loanTokenSymbolOrAddress.toLowerCase()}"]`;
|
|
1394
1400
|
} else {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
}
|
|
1399
|
-
|
|
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:
|
|
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
|
-
|
|
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"
|