@agether/sdk 2.12.2 → 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
@@ -494,25 +494,21 @@ var AgetherClient = class _AgetherClient {
494
494
  const acctAddr = await this.agether4337Factory.getAccount(agentId);
495
495
  this.accountAddress = acctAddr;
496
496
  if (!acctExists) {
497
- try {
498
- const updatedMeta = JSON.stringify({
499
- type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
500
- name: options?.name || "Unnamed Agent",
501
- description: options?.description || "AI agent registered via @agether/sdk",
502
- active: true,
503
- wallet: `eip155:${this.config.chainId}:${acctAddr}`,
504
- registrations: [{
505
- agentId: Number(agentId),
506
- agentRegistry: `eip155:${this.config.chainId}:${this.config.contracts.identityRegistry}`
507
- }]
508
- });
509
- const finalURI = `data:application/json;base64,${Buffer.from(updatedMeta).toString("base64")}`;
510
- const uriTx = await this.identityRegistry.setAgentURI(agentId, finalURI);
511
- await uriTx.wait();
512
- this._refreshSigner();
513
- } catch (e) {
514
- console.warn("[agether] setAgentURI failed (non-fatal):", e instanceof Error ? e.message : e);
515
- }
497
+ const updatedMeta = JSON.stringify({
498
+ type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
499
+ name: options?.name || "Unnamed Agent",
500
+ description: options?.description || "AI agent registered via @agether/sdk",
501
+ active: true,
502
+ wallet: `eip155:${this.config.chainId}:${acctAddr}`,
503
+ registrations: [{
504
+ agentId: Number(agentId),
505
+ agentRegistry: `eip155:${this.config.chainId}:${this.config.contracts.identityRegistry}`
506
+ }]
507
+ });
508
+ const finalURI = `data:application/json;base64,${Buffer.from(updatedMeta).toString("base64")}`;
509
+ const uriTx = await this.identityRegistry.setAgentURI(agentId, finalURI);
510
+ await uriTx.wait();
511
+ this._refreshSigner();
516
512
  }
517
513
  const kyaRequired = await this.isKyaRequired();
518
514
  return {
@@ -530,7 +526,7 @@ var AgetherClient = class _AgetherClient {
530
526
  async _mintNewIdentity(name, description) {
531
527
  const registrationFile = JSON.stringify({
532
528
  type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
533
- name: name || "Unnamed Agent",
529
+ name: name || "Agether Agent",
534
530
  description: description || "AI agent registered via @agether/sdk",
535
531
  active: true,
536
532
  registrations: [{
@@ -1041,8 +1037,9 @@ var morphoIface = new import_ethers2.ethers.Interface(MORPHO_BLUE_ABI);
1041
1037
  var erc20Iface2 = new import_ethers2.ethers.Interface(ERC20_ABI);
1042
1038
  var MorphoClient = class {
1043
1039
  constructor(config) {
1040
+ /** Market params cache: keyed by market uniqueKey (bytes32 hash) */
1044
1041
  this._marketCache = /* @__PURE__ */ new Map();
1045
- /** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
1042
+ /** Dynamic token registry: symbol (uppercase) or address (lowercase) → { address, symbol, decimals } */
1046
1043
  this._tokenCache = /* @__PURE__ */ new Map();
1047
1044
  this._discoveredAt = 0;
1048
1045
  if (!config.agentId) {
@@ -1145,21 +1142,23 @@ var MorphoClient = class {
1145
1142
  // Market Discovery (Morpho GraphQL API)
1146
1143
  // ════════════════════════════════════════════════════════
1147
1144
  /**
1148
- * Fetch USDC borrow markets on Base from Morpho API.
1149
- * Caches results for 5 minutes.
1145
+ * Fetch available markets on the current chain from Morpho API.
1146
+ * Caches results for 5 minutes. Supports all loan tokens (not just USDC).
1147
+ *
1148
+ * @param forceRefresh - bypass cache TTL
1149
+ * @param filter - optional filter by loan token and/or collateral token
1150
1150
  */
1151
- async getMarkets(forceRefresh = false) {
1151
+ async getMarkets(forceRefresh = false, filter) {
1152
1152
  if (!forceRefresh && this._discoveredMarkets && Date.now() - this._discoveredAt < 3e5) {
1153
- return this._discoveredMarkets;
1153
+ return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
1154
1154
  }
1155
1155
  const chainId = this.config.chainId;
1156
- const usdcAddr = this.config.contracts.usdc.toLowerCase();
1157
1156
  const query = `{
1158
1157
  markets(
1159
- first: 50
1158
+ first: 500
1160
1159
  orderBy: SupplyAssetsUsd
1161
1160
  orderDirection: Desc
1162
- where: { chainId_in: [${chainId}], loanAssetAddress_in: ["${usdcAddr}"] }
1161
+ where: { chainId_in: [${chainId}] }
1163
1162
  ) {
1164
1163
  items {
1165
1164
  uniqueKey
@@ -1192,14 +1191,14 @@ var MorphoClient = class {
1192
1191
  }));
1193
1192
  this._discoveredAt = Date.now();
1194
1193
  for (const mi of this._discoveredMarkets) {
1194
+ this._marketCache.set(mi.uniqueKey.toLowerCase(), {
1195
+ loanToken: mi.loanAsset.address,
1196
+ collateralToken: mi.collateralAsset.address,
1197
+ oracle: mi.oracle,
1198
+ irm: mi.irm,
1199
+ lltv: mi.lltv
1200
+ });
1195
1201
  if (mi.collateralAsset.address !== import_ethers2.ethers.ZeroAddress) {
1196
- this._marketCache.set(mi.collateralAsset.address.toLowerCase(), {
1197
- loanToken: mi.loanAsset.address,
1198
- collateralToken: mi.collateralAsset.address,
1199
- oracle: mi.oracle,
1200
- irm: mi.irm,
1201
- lltv: mi.lltv
1202
- });
1203
1202
  this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
1204
1203
  address: mi.collateralAsset.address,
1205
1204
  symbol: mi.collateralAsset.symbol,
@@ -1224,17 +1223,24 @@ var MorphoClient = class {
1224
1223
  });
1225
1224
  }
1226
1225
  }
1227
- return this._discoveredMarkets;
1226
+ return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
1228
1227
  } catch (e) {
1229
1228
  console.warn("[agether] getMarkets failed, using cache:", e instanceof Error ? e.message : e);
1230
- return this._discoveredMarkets ?? [];
1229
+ const cached = this._discoveredMarkets ?? [];
1230
+ return filter ? this._applyMarketFilter(cached, filter) : cached;
1231
1231
  }
1232
1232
  }
1233
1233
  /**
1234
- * Get MarketParams for a collateral token.
1235
- * Tries cache → API → onchain idToMarketParams.
1234
+ * Get MarketParams for a collateral token (and optionally a specific loan token).
1235
+ * Tries cache → API discovery.
1236
+ *
1237
+ * When `loanTokenSymbolOrAddress` is omitted, returns the most liquid market
1238
+ * for that collateral (sorted by supply, typically the USDC market).
1239
+ *
1240
+ * @param collateralSymbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
1241
+ * @param loanTokenSymbolOrAddress - e.g. 'USDC', 'WETH', or '0x833589...' (optional)
1236
1242
  */
1237
- async findMarketForCollateral(collateralSymbolOrAddress) {
1243
+ async findMarketForCollateral(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
1238
1244
  let colAddr;
1239
1245
  if (collateralSymbolOrAddress.startsWith("0x")) {
1240
1246
  colAddr = collateralSymbolOrAddress.toLowerCase();
@@ -1246,13 +1252,41 @@ var MorphoClient = class {
1246
1252
  colAddr = collateralSymbolOrAddress.toLowerCase();
1247
1253
  }
1248
1254
  }
1249
- const cached = this._marketCache.get(colAddr);
1250
- if (cached) return cached;
1251
- await this.getMarkets();
1252
- const fromApi = this._marketCache.get(colAddr);
1253
- if (fromApi) return fromApi;
1255
+ let loanAddr;
1256
+ if (loanTokenSymbolOrAddress) {
1257
+ if (loanTokenSymbolOrAddress.startsWith("0x")) {
1258
+ loanAddr = loanTokenSymbolOrAddress.toLowerCase();
1259
+ } else {
1260
+ try {
1261
+ const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
1262
+ loanAddr = resolved.address.toLowerCase();
1263
+ } catch {
1264
+ loanAddr = loanTokenSymbolOrAddress.toLowerCase();
1265
+ }
1266
+ }
1267
+ }
1268
+ if (!this._discoveredMarkets) await this.getMarkets();
1269
+ for (const m of this._discoveredMarkets ?? []) {
1270
+ if (m.collateralAsset.address.toLowerCase() !== colAddr) continue;
1271
+ if (loanAddr && m.loanAsset.address.toLowerCase() !== loanAddr) continue;
1272
+ return {
1273
+ loanToken: m.loanAsset.address,
1274
+ collateralToken: m.collateralAsset.address,
1275
+ oracle: m.oracle,
1276
+ irm: m.irm,
1277
+ lltv: m.lltv
1278
+ };
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
+ }
1254
1288
  throw new AgetherError(
1255
- `No Morpho market found for collateral ${collateralSymbolOrAddress}`,
1289
+ `No Morpho market found for collateral ${collateralSymbolOrAddress}` + (loanTokenSymbolOrAddress ? ` with loan token ${loanTokenSymbolOrAddress}` : ""),
1256
1290
  "MARKET_NOT_FOUND"
1257
1291
  );
1258
1292
  }
@@ -1287,12 +1321,13 @@ var MorphoClient = class {
1287
1321
  const acctAddr = await this.getAccountAddress();
1288
1322
  const markets = await this.getMarkets();
1289
1323
  const positions = [];
1290
- let totalDebt = 0n;
1324
+ let totalDebtFloat = 0;
1291
1325
  for (const m of markets) {
1292
1326
  if (!m.collateralAsset || m.collateralAsset.address === import_ethers2.ethers.ZeroAddress) continue;
1293
1327
  try {
1294
1328
  const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
1295
1329
  if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
1330
+ const loanDecimals = m.loanAsset.decimals;
1296
1331
  let debt = 0n;
1297
1332
  if (pos.borrowShares > 0n) {
1298
1333
  try {
@@ -1300,7 +1335,7 @@ var MorphoClient = class {
1300
1335
  const totalBorrowShares = BigInt(mkt.totalBorrowShares);
1301
1336
  const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
1302
1337
  debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
1303
- totalDebt += debt;
1338
+ totalDebtFloat += parseFloat(import_ethers2.ethers.formatUnits(debt, loanDecimals));
1304
1339
  } catch (e) {
1305
1340
  console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
1306
1341
  }
@@ -1308,10 +1343,11 @@ var MorphoClient = class {
1308
1343
  positions.push({
1309
1344
  marketId: m.uniqueKey,
1310
1345
  collateralToken: m.collateralAsset.symbol,
1346
+ loanToken: m.loanAsset.symbol,
1311
1347
  collateral: import_ethers2.ethers.formatUnits(pos.collateral, m.collateralAsset.decimals),
1312
1348
  borrowShares: pos.borrowShares.toString(),
1313
1349
  supplyShares: pos.supplyShares.toString(),
1314
- debt: import_ethers2.ethers.formatUnits(debt, 6)
1350
+ debt: import_ethers2.ethers.formatUnits(debt, loanDecimals)
1315
1351
  });
1316
1352
  } catch (e) {
1317
1353
  console.warn(`[agether] position read failed for market:`, e instanceof Error ? e.message : e);
@@ -1321,16 +1357,28 @@ var MorphoClient = class {
1321
1357
  return {
1322
1358
  agentId: this.agentId,
1323
1359
  agentAccount: acctAddr,
1324
- totalDebt: import_ethers2.ethers.formatUnits(totalDebt, 6),
1360
+ totalDebt: totalDebtFloat.toFixed(6),
1325
1361
  positions
1326
1362
  };
1327
1363
  }
1328
1364
  // ════════════════════════════════════════════════════════
1329
1365
  // Balance & Borrowing Capacity
1330
1366
  // ════════════════════════════════════════════════════════
1367
+ /**
1368
+ * Get the balance of any ERC-20 token in the AgentAccount.
1369
+ * @param symbolOrAddress - token symbol (e.g. 'USDC', 'WETH') or address
1370
+ * @returns balance in raw units
1371
+ */
1372
+ async getTokenBalance(symbolOrAddress) {
1373
+ const acctAddr = await this.getAccountAddress();
1374
+ const tokenInfo = await this._resolveToken(symbolOrAddress);
1375
+ const token = new import_ethers2.Contract(tokenInfo.address, ERC20_ABI, this.provider);
1376
+ return token.balanceOf(acctAddr);
1377
+ }
1331
1378
  /**
1332
1379
  * Get the USDC balance of the AgentAccount.
1333
1380
  * @returns USDC balance in raw units (6 decimals)
1381
+ * @deprecated Use `getTokenBalance('USDC')` instead.
1334
1382
  */
1335
1383
  async getUsdcBalance() {
1336
1384
  const acctAddr = await this.getAccountAddress();
@@ -1338,7 +1386,7 @@ var MorphoClient = class {
1338
1386
  return usdc.balanceOf(acctAddr);
1339
1387
  }
1340
1388
  /**
1341
- * Calculate the maximum additional USDC that can be borrowed
1389
+ * Calculate the maximum additional loan token that can be borrowed
1342
1390
  * given the agent's current collateral and debt across all markets.
1343
1391
  *
1344
1392
  * For each market with collateral deposited:
@@ -1346,7 +1394,7 @@ var MorphoClient = class {
1346
1394
  *
1347
1395
  * Uses the Morpho oracle to price collateral → loan token.
1348
1396
  *
1349
- * @returns Maximum additional USDC borrowable (6 decimals)
1397
+ * @returns Maximum additional borrowable per market (raw units in each market's loan token)
1350
1398
  */
1351
1399
  async getMaxBorrowable() {
1352
1400
  const acctAddr = await this.getAccountAddress();
@@ -1379,6 +1427,8 @@ var MorphoClient = class {
1379
1427
  totalAdditional += maxAdditional;
1380
1428
  byMarket.push({
1381
1429
  collateralToken: m.collateralAsset.symbol,
1430
+ loanToken: m.loanAsset.symbol,
1431
+ loanDecimals: m.loanAsset.decimals,
1382
1432
  maxAdditional,
1383
1433
  currentDebt,
1384
1434
  collateralValue: collateralValueInLoan
@@ -1394,35 +1444,50 @@ var MorphoClient = class {
1394
1444
  // Market Rates & Yield Estimation
1395
1445
  // ════════════════════════════════════════════════════════
1396
1446
  /**
1397
- * Fetch current supply/borrow APY for a collateral market from Morpho GraphQL API.
1447
+ * Fetch current supply/borrow APY for markets from Morpho GraphQL API.
1398
1448
  *
1399
1449
  * Note: On Morpho Blue, collateral does NOT earn yield directly. Supply APY
1400
1450
  * is what lenders earn; borrow APY is what borrowers pay.
1451
+ *
1452
+ * @param collateralSymbolOrAddress - filter by collateral token (optional)
1453
+ * @param loanTokenSymbolOrAddress - filter by loan token (optional). Omit for all loan tokens.
1401
1454
  */
1402
- async getMarketRates(collateralSymbolOrAddress) {
1455
+ async getMarketRates(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
1403
1456
  const chainId = this.config.chainId;
1404
- const usdcAddr = this.config.contracts.usdc.toLowerCase();
1405
1457
  let collateralFilter = "";
1458
+ let loanFilter = "";
1459
+ let searchTerm = "";
1406
1460
  if (collateralSymbolOrAddress) {
1407
- let colAddr;
1408
1461
  if (collateralSymbolOrAddress.startsWith("0x")) {
1409
- colAddr = collateralSymbolOrAddress.toLowerCase();
1462
+ collateralFilter = `, collateralAssetAddress_in: ["${collateralSymbolOrAddress.toLowerCase()}"]`;
1410
1463
  } else {
1411
- try {
1412
- const resolved = await this._resolveToken(collateralSymbolOrAddress);
1413
- colAddr = resolved.address.toLowerCase();
1414
- } catch {
1415
- 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;
1469
+ }
1470
+ }
1471
+ }
1472
+ if (loanTokenSymbolOrAddress) {
1473
+ if (loanTokenSymbolOrAddress.startsWith("0x")) {
1474
+ loanFilter = `, loanAssetAddress_in: ["${loanTokenSymbolOrAddress.toLowerCase()}"]`;
1475
+ } else {
1476
+ const cached = this._tokenCache.get(loanTokenSymbolOrAddress.toUpperCase());
1477
+ if (cached) {
1478
+ loanFilter = `, loanAssetAddress_in: ["${cached.address.toLowerCase()}"]`;
1479
+ } else {
1480
+ searchTerm = searchTerm || loanTokenSymbolOrAddress;
1416
1481
  }
1417
1482
  }
1418
- collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
1419
1483
  }
1484
+ const searchClause = searchTerm ? `, search: "${searchTerm}"` : "";
1420
1485
  const query = `{
1421
1486
  markets(
1422
- first: 50
1487
+ first: 100
1423
1488
  orderBy: SupplyAssetsUsd
1424
1489
  orderDirection: Desc
1425
- where: { chainId_in: [${chainId}], loanAssetAddress_in: ["${usdcAddr}"]${collateralFilter} }
1490
+ where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter}${searchClause} }
1426
1491
  ) {
1427
1492
  items {
1428
1493
  uniqueKey
@@ -1441,23 +1506,234 @@ var MorphoClient = class {
1441
1506
  }`;
1442
1507
  try {
1443
1508
  const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1444
- const items = resp.data?.data?.markets?.items ?? [];
1445
- return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers2.ethers.ZeroAddress).map((m) => ({
1446
- collateralToken: m.collateralAsset.symbol,
1447
- loanToken: m.loanAsset.symbol,
1448
- supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
1449
- borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
1450
- utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
1451
- totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 1e6 : 0,
1452
- totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 1e6 : 0,
1453
- lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
1454
- marketId: m.uniqueKey
1455
- }));
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
+ }
1518
+ return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers2.ethers.ZeroAddress).map((m) => {
1519
+ const loanDecimals = m.loanAsset?.decimals ?? 18;
1520
+ return {
1521
+ collateralToken: m.collateralAsset.symbol,
1522
+ loanToken: m.loanAsset.symbol,
1523
+ loanDecimals,
1524
+ supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
1525
+ borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
1526
+ utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
1527
+ totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 10 ** loanDecimals : 0,
1528
+ totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 10 ** loanDecimals : 0,
1529
+ lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
1530
+ marketId: m.uniqueKey
1531
+ };
1532
+ });
1456
1533
  } catch (e) {
1457
1534
  console.warn("[agether] getMarketRates failed:", e instanceof Error ? e.message : e);
1458
1535
  return [];
1459
1536
  }
1460
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
+ }
1461
1737
  /**
1462
1738
  * Estimate theoretical yield for a given collateral amount over a period.
1463
1739
  *
@@ -1486,14 +1762,15 @@ var MorphoClient = class {
1486
1762
  } else {
1487
1763
  try {
1488
1764
  const params = await this.findMarketForCollateral(collateralSymbol);
1765
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1489
1766
  const oracleContract = new import_ethers2.Contract(params.oracle, [
1490
1767
  "function price() view returns (uint256)"
1491
1768
  ], this.provider);
1492
1769
  const oraclePrice = await oracleContract.price();
1493
1770
  const ORACLE_PRICE_SCALE = 10n ** 36n;
1494
1771
  const amountWei = import_ethers2.ethers.parseUnits(amount, colInfo.decimals);
1495
- const valueInUsdc = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
1496
- collateralValueUsd = Number(valueInUsdc) / 1e6;
1772
+ const valueInLoan = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
1773
+ collateralValueUsd = Number(valueInLoan) / 10 ** loanDecimals;
1497
1774
  } catch (e) {
1498
1775
  console.warn("[agether] oracle price fetch for yield estimation failed:", e instanceof Error ? e.message : e);
1499
1776
  throw new AgetherError("Cannot determine collateral value. Provide ethPriceUsd.", "PRICE_UNAVAILABLE");
@@ -1514,61 +1791,65 @@ var MorphoClient = class {
1514
1791
  // Supply-Side (Lending) — earn yield by supplying USDC
1515
1792
  // ════════════════════════════════════════════════════════
1516
1793
  /**
1517
- * Supply USDC to a Morpho Blue market as a lender (earn yield).
1794
+ * Supply loan token to a Morpho Blue market as a lender (earn yield).
1518
1795
  *
1519
1796
  * Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
1520
- * you deposit the loanToken (USDC) into the market's supply pool and earn
1797
+ * you deposit the loanToken into the market's supply pool and earn
1521
1798
  * interest paid by borrowers.
1522
1799
  *
1523
- * @param usdcAmount - Amount of USDC to supply (e.g. '500')
1800
+ * @param amount - Amount of loan token to supply (e.g. '500' for 500 USDC, '0.5' for 0.5 WETH)
1524
1801
  * @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
1525
1802
  * Optional — defaults to highest-APY market
1803
+ * @param loanTokenSymbol - Loan token to filter market (e.g. 'USDC', 'WETH'). Optional.
1526
1804
  */
1527
- async supplyAsset(usdcAmount, collateralSymbol) {
1805
+ async supplyAsset(amount, collateralSymbol, loanTokenSymbol) {
1528
1806
  const acctAddr = await this.getAccountAddress();
1529
- const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
1530
1807
  const morphoAddr = this.config.contracts.morphoBlue;
1531
- const usdcAddr = this.config.contracts.usdc;
1532
1808
  let params;
1533
1809
  let usedCollateral;
1534
1810
  if (collateralSymbol) {
1535
- params = await this.findMarketForCollateral(collateralSymbol);
1811
+ params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
1536
1812
  usedCollateral = collateralSymbol;
1537
1813
  } else {
1538
- const rates = await this.getMarketRates();
1814
+ const rates = await this.getMarketRates(void 0, loanTokenSymbol);
1539
1815
  if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
1540
1816
  const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
1541
- params = await this.findMarketForCollateral(best.collateralToken);
1817
+ params = await this.findMarketForCollateral(best.collateralToken, loanTokenSymbol);
1542
1818
  usedCollateral = best.collateralToken;
1543
1819
  }
1820
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1821
+ const loanTokenAddr = params.loanToken;
1822
+ const parsedAmount = import_ethers2.ethers.parseUnits(amount, loanDecimals);
1544
1823
  const marketId = import_ethers2.ethers.keccak256(
1545
1824
  import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
1546
1825
  ["address", "address", "address", "address", "uint256"],
1547
1826
  [params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
1548
1827
  )
1549
1828
  );
1550
- const usdcContract = new import_ethers2.Contract(usdcAddr, ERC20_ABI, this._signer);
1551
- const acctBalance = await usdcContract.balanceOf(acctAddr);
1552
- if (acctBalance < amount) {
1553
- const shortfall = amount - acctBalance;
1554
- const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
1829
+ const loanContract = new import_ethers2.Contract(loanTokenAddr, ERC20_ABI, this._signer);
1830
+ const acctBalance = await loanContract.balanceOf(acctAddr);
1831
+ if (acctBalance < parsedAmount) {
1832
+ const shortfall = parsedAmount - acctBalance;
1833
+ const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
1555
1834
  if (eoaBalance < shortfall) {
1835
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
1836
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
1556
1837
  throw new AgetherError(
1557
- `Insufficient USDC. Need ${usdcAmount}, AgentAccount has ${import_ethers2.ethers.formatUnits(acctBalance, 6)}, EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, 6)}.`,
1838
+ `Insufficient ${loanSymbol}. Need ${amount}, AgentAccount has ${import_ethers2.ethers.formatUnits(acctBalance, loanDecimals)}, EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, loanDecimals)}.`,
1558
1839
  "INSUFFICIENT_BALANCE"
1559
1840
  );
1560
1841
  }
1561
- const transferTx = await usdcContract.transfer(acctAddr, shortfall);
1842
+ const transferTx = await loanContract.transfer(acctAddr, shortfall);
1562
1843
  await transferTx.wait();
1563
1844
  this._refreshSigner();
1564
1845
  }
1565
- const targets = [usdcAddr, morphoAddr];
1846
+ const targets = [loanTokenAddr, morphoAddr];
1566
1847
  const values = [0n, 0n];
1567
1848
  const datas = [
1568
- erc20Iface2.encodeFunctionData("approve", [morphoAddr, amount]),
1849
+ erc20Iface2.encodeFunctionData("approve", [morphoAddr, parsedAmount]),
1569
1850
  morphoIface.encodeFunctionData("supply", [
1570
1851
  this._toTuple(params),
1571
- amount,
1852
+ parsedAmount,
1572
1853
  0n,
1573
1854
  acctAddr,
1574
1855
  "0x"
@@ -1577,7 +1858,7 @@ var MorphoClient = class {
1577
1858
  const receipt = await this.batch(targets, values, datas);
1578
1859
  return {
1579
1860
  tx: receipt.hash,
1580
- amount: usdcAmount,
1861
+ amount,
1581
1862
  marketId,
1582
1863
  collateralToken: usedCollateral,
1583
1864
  agentAccount: acctAddr
@@ -1590,17 +1871,26 @@ var MorphoClient = class {
1590
1871
  * @param collateralSymbol - Market collateral to identify which market
1591
1872
  * @param receiver - Destination address (defaults to EOA)
1592
1873
  */
1593
- async withdrawSupply(usdcAmount, collateralSymbol, receiver) {
1874
+ /**
1875
+ * Withdraw supplied loan token (+ earned interest) from a Morpho Blue market.
1876
+ *
1877
+ * @param amount - Amount to withdraw (e.g. '100' or 'all' for full position)
1878
+ * @param collateralSymbol - Market collateral to identify which market
1879
+ * @param receiver - Destination address (defaults to EOA)
1880
+ * @param loanTokenSymbol - Loan token to filter market (optional)
1881
+ */
1882
+ async withdrawSupply(amount, collateralSymbol, receiver, loanTokenSymbol) {
1594
1883
  const acctAddr = await this.getAccountAddress();
1595
1884
  const morphoAddr = this.config.contracts.morphoBlue;
1596
1885
  const dest = receiver || await this.getSignerAddress();
1597
1886
  let params;
1598
1887
  if (collateralSymbol) {
1599
- params = await this.findMarketForCollateral(collateralSymbol);
1888
+ params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
1600
1889
  } else {
1601
1890
  const { params: p } = await this._findActiveSupplyMarket();
1602
1891
  params = p;
1603
1892
  }
1893
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1604
1894
  const marketId = import_ethers2.ethers.keccak256(
1605
1895
  import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
1606
1896
  ["address", "address", "address", "address", "uint256"],
@@ -1609,13 +1899,13 @@ var MorphoClient = class {
1609
1899
  );
1610
1900
  let withdrawAssets;
1611
1901
  let withdrawShares;
1612
- if (usdcAmount === "all") {
1902
+ if (amount === "all") {
1613
1903
  const pos = await this.morphoBlue.position(marketId, acctAddr);
1614
1904
  withdrawShares = BigInt(pos.supplyShares);
1615
1905
  withdrawAssets = 0n;
1616
1906
  if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
1617
1907
  } else {
1618
- withdrawAssets = import_ethers2.ethers.parseUnits(usdcAmount, 6);
1908
+ withdrawAssets = import_ethers2.ethers.parseUnits(amount, loanDecimals);
1619
1909
  withdrawShares = 0n;
1620
1910
  }
1621
1911
  const data = morphoIface.encodeFunctionData("withdraw", [
@@ -1633,13 +1923,13 @@ var MorphoClient = class {
1633
1923
  const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
1634
1924
  const totalSupplyShares = BigInt(mkt.totalSupplyShares);
1635
1925
  const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
1636
- remainingSupply = import_ethers2.ethers.formatUnits(currentAssets, 6);
1926
+ remainingSupply = import_ethers2.ethers.formatUnits(currentAssets, loanDecimals);
1637
1927
  } catch (e) {
1638
1928
  console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
1639
1929
  }
1640
1930
  return {
1641
1931
  tx: receipt.hash,
1642
- amount: usdcAmount,
1932
+ amount,
1643
1933
  remainingSupply,
1644
1934
  destination: dest
1645
1935
  };
@@ -1712,14 +2002,13 @@ var MorphoClient = class {
1712
2002
  * Computes available yield, verifies the requested amount doesn't exceed it,
1713
2003
  * then withdraws from the supply position and sends directly to the recipient.
1714
2004
  *
1715
- * @param recipient - Address to receive the USDC
1716
- * @param usdcAmount - Amount to pay from yield (e.g. '5.50')
2005
+ * @param recipient - Address to receive the loan token
2006
+ * @param amount - Amount to pay from yield (e.g. '5.50')
1717
2007
  * @param collateralSymbol - Market collateral to identify which supply position
1718
2008
  */
1719
- async payFromYield(recipient, usdcAmount, collateralSymbol) {
2009
+ async payFromYield(recipient, amount, collateralSymbol) {
1720
2010
  const acctAddr = await this.getAccountAddress();
1721
2011
  const morphoAddr = this.config.contracts.morphoBlue;
1722
- const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
1723
2012
  const positions = await this.getSupplyPositions(collateralSymbol);
1724
2013
  if (positions.length === 0) {
1725
2014
  throw new AgetherError("No supply position found", "NO_SUPPLY");
@@ -1727,17 +2016,20 @@ var MorphoClient = class {
1727
2016
  const pos = positions.reduce(
1728
2017
  (a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
1729
2018
  );
1730
- const availableYield = import_ethers2.ethers.parseUnits(pos.earnedYield, 6);
1731
- if (amount > availableYield) {
2019
+ const params = await this.findMarketForCollateral(pos.collateralToken, pos.loanToken);
2020
+ const loanDecimals = await this._getLoanTokenDecimals(params);
2021
+ const parsedAmount = import_ethers2.ethers.parseUnits(amount, loanDecimals);
2022
+ const availableYield = import_ethers2.ethers.parseUnits(pos.earnedYield, loanDecimals);
2023
+ if (parsedAmount > availableYield) {
2024
+ const loanSymbol = pos.loanToken;
1732
2025
  throw new AgetherError(
1733
- `Requested ${usdcAmount} USDC exceeds available yield of ${pos.earnedYield} USDC. Use withdrawSupply to withdraw principal.`,
2026
+ `Requested ${amount} ${loanSymbol} exceeds available yield of ${pos.earnedYield} ${loanSymbol}. Use withdrawSupply to withdraw principal.`,
1734
2027
  "EXCEEDS_YIELD"
1735
2028
  );
1736
2029
  }
1737
- const params = await this.findMarketForCollateral(pos.collateralToken);
1738
2030
  const data = morphoIface.encodeFunctionData("withdraw", [
1739
2031
  this._toTuple(params),
1740
- amount,
2032
+ parsedAmount,
1741
2033
  0n,
1742
2034
  acctAddr,
1743
2035
  recipient
@@ -1756,7 +2048,7 @@ var MorphoClient = class {
1756
2048
  }
1757
2049
  return {
1758
2050
  tx: receipt.hash,
1759
- yieldWithdrawn: usdcAmount,
2051
+ yieldWithdrawn: amount,
1760
2052
  recipient,
1761
2053
  remainingYield,
1762
2054
  remainingSupply
@@ -1814,28 +2106,31 @@ var MorphoClient = class {
1814
2106
  };
1815
2107
  }
1816
2108
  /**
1817
- * Borrow USDC against existing collateral.
2109
+ * Borrow loan token against existing collateral.
1818
2110
  *
1819
2111
  * AgentAccount.execute: Morpho.borrow(params, amount, 0, account, account)
1820
2112
  *
1821
- * @param usdcAmount - USDC amount (e.g. '100')
2113
+ * @param amount - Loan token amount (e.g. '100' for 100 USDC, '0.5' for 0.5 WETH)
1822
2114
  * @param tokenSymbol - collateral symbol to identify which market (default: first with collateral)
2115
+ * @param marketParams - explicit market params (optional)
2116
+ * @param loanTokenSymbol - loan token to filter market (optional, e.g. 'USDC', 'WETH')
1823
2117
  */
1824
- async borrow(usdcAmount, tokenSymbol, marketParams) {
2118
+ async borrow(amount, tokenSymbol, marketParams, loanTokenSymbol) {
1825
2119
  const acctAddr = await this.getAccountAddress();
1826
- const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
1827
2120
  const morphoAddr = this.config.contracts.morphoBlue;
1828
2121
  let params;
1829
2122
  let usedToken = tokenSymbol || "WETH";
1830
2123
  if (marketParams) {
1831
2124
  params = marketParams;
1832
2125
  } else if (tokenSymbol) {
1833
- params = await this.findMarketForCollateral(tokenSymbol);
2126
+ params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
1834
2127
  } else {
1835
2128
  const { params: p, symbol } = await this._findActiveMarket();
1836
2129
  params = p;
1837
2130
  usedToken = symbol;
1838
2131
  }
2132
+ const loanDecimals = await this._getLoanTokenDecimals(params);
2133
+ const parsedAmount = import_ethers2.ethers.parseUnits(amount, loanDecimals);
1839
2134
  try {
1840
2135
  const marketId = import_ethers2.ethers.keccak256(
1841
2136
  import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
@@ -1859,11 +2154,14 @@ var MorphoClient = class {
1859
2154
  const totalBorrowAssets = BigInt(mktState.totalBorrowAssets);
1860
2155
  const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
1861
2156
  const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
1862
- if (amount > maxAdditional) {
1863
- const maxUsd = import_ethers2.ethers.formatUnits(maxAdditional, 6);
1864
- const colFormatted = import_ethers2.ethers.formatUnits(pos.collateral, 18);
2157
+ if (parsedAmount > maxAdditional) {
2158
+ const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
2159
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
2160
+ const colInfo = await this._resolveToken(usedToken);
2161
+ const maxFormatted = import_ethers2.ethers.formatUnits(maxAdditional, loanDecimals);
2162
+ const colFormatted = import_ethers2.ethers.formatUnits(pos.collateral, colInfo.decimals);
1865
2163
  throw new AgetherError(
1866
- `Borrow of $${usdcAmount} USDC exceeds max borrowable $${maxUsd} USDC (collateral: ${colFormatted} ${usedToken}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Deposit more collateral or reduce borrow amount.`,
2164
+ `Borrow of ${amount} ${loanSymbol} exceeds max borrowable ${maxFormatted} ${loanSymbol} (collateral: ${colFormatted} ${usedToken}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Deposit more collateral or reduce borrow amount.`,
1867
2165
  "EXCEEDS_MAX_LTV"
1868
2166
  );
1869
2167
  }
@@ -1873,7 +2171,7 @@ var MorphoClient = class {
1873
2171
  }
1874
2172
  const data = morphoIface.encodeFunctionData("borrow", [
1875
2173
  this._toTuple(params),
1876
- amount,
2174
+ parsedAmount,
1877
2175
  0n,
1878
2176
  acctAddr,
1879
2177
  acctAddr
@@ -1881,7 +2179,7 @@ var MorphoClient = class {
1881
2179
  const receipt = await this.exec(morphoAddr, data);
1882
2180
  return {
1883
2181
  tx: receipt.hash,
1884
- amount: usdcAmount,
2182
+ amount,
1885
2183
  collateralToken: usedToken,
1886
2184
  agentAccount: acctAddr
1887
2185
  };
@@ -1889,17 +2187,27 @@ var MorphoClient = class {
1889
2187
  /**
1890
2188
  * Deposit collateral AND borrow USDC in one batched transaction.
1891
2189
  *
2190
+ /**
2191
+ * Deposit collateral AND borrow loan token in one batched transaction.
2192
+ *
1892
2193
  * AgentAccount.executeBatch:
1893
2194
  * [collateral.approve, Morpho.supplyCollateral, Morpho.borrow]
1894
2195
  *
1895
2196
  * The collateral must be transferred to AgentAccount first.
2197
+ *
2198
+ * @param tokenSymbol - collateral token symbol (e.g. 'WETH')
2199
+ * @param collateralAmount - amount of collateral (e.g. '0.05')
2200
+ * @param borrowAmount - amount of loan token to borrow (e.g. '100')
2201
+ * @param marketParams - explicit market params (optional)
2202
+ * @param loanTokenSymbol - loan token to filter market (optional)
1896
2203
  */
1897
- async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
2204
+ async depositAndBorrow(tokenSymbol, collateralAmount, borrowAmount, marketParams, loanTokenSymbol) {
1898
2205
  const acctAddr = await this.getAccountAddress();
1899
2206
  const colInfo = await this._resolveToken(tokenSymbol);
1900
- const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
2207
+ const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
2208
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1901
2209
  const colWei = import_ethers2.ethers.parseUnits(collateralAmount, colInfo.decimals);
1902
- const borrowWei = import_ethers2.ethers.parseUnits(borrowUsdcAmount, 6);
2210
+ const borrowWei = import_ethers2.ethers.parseUnits(borrowAmount, loanDecimals);
1903
2211
  const morphoAddr = this.config.contracts.morphoBlue;
1904
2212
  try {
1905
2213
  const marketId = import_ethers2.ethers.keccak256(
@@ -1920,9 +2228,11 @@ var MorphoClient = class {
1920
2228
  const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
1921
2229
  const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
1922
2230
  if (borrowWei > maxAdditional) {
1923
- const maxUsd = import_ethers2.ethers.formatUnits(maxAdditional, 6);
2231
+ const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
2232
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
2233
+ const maxFormatted = import_ethers2.ethers.formatUnits(maxAdditional, loanDecimals);
1924
2234
  throw new AgetherError(
1925
- `Borrow of $${borrowUsdcAmount} USDC exceeds max borrowable $${maxUsd} USDC (total collateral: ${import_ethers2.ethers.formatUnits(totalCollateral, colInfo.decimals)} ${tokenSymbol}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Reduce borrow or increase collateral.`,
2235
+ `Borrow of ${borrowAmount} ${loanSymbol} exceeds max borrowable ${maxFormatted} ${loanSymbol} (total collateral: ${import_ethers2.ethers.formatUnits(totalCollateral, colInfo.decimals)} ${tokenSymbol}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Reduce borrow or increase collateral.`,
1926
2236
  "EXCEEDS_MAX_LTV"
1927
2237
  );
1928
2238
  }
@@ -1968,36 +2278,42 @@ var MorphoClient = class {
1968
2278
  tx: receipt.hash,
1969
2279
  collateralToken: tokenSymbol,
1970
2280
  collateralAmount,
1971
- borrowAmount: borrowUsdcAmount,
2281
+ borrowAmount,
1972
2282
  agentAccount: acctAddr
1973
2283
  };
1974
2284
  }
1975
2285
  /**
1976
- * Repay borrowed USDC from AgentAccount.
2286
+ * Repay borrowed loan token from AgentAccount.
1977
2287
  *
1978
2288
  * AgentAccount.executeBatch:
1979
- * [USDC.approve(MorphoBlue), Morpho.repay(params)]
2289
+ * [loanToken.approve(MorphoBlue), Morpho.repay(params)]
2290
+ *
2291
+ * @param amount - loan token amount to repay (e.g. '50' or 'all' for full repayment)
2292
+ * @param tokenSymbol - collateral symbol to identify which market (optional)
2293
+ * @param marketParams - explicit market params (optional)
2294
+ * @param loanTokenSymbol - loan token to filter market (optional)
1980
2295
  */
1981
- async repay(usdcAmount, tokenSymbol, marketParams) {
2296
+ async repay(amount, tokenSymbol, marketParams, loanTokenSymbol) {
1982
2297
  const acctAddr = await this.getAccountAddress();
1983
2298
  const morphoAddr = this.config.contracts.morphoBlue;
1984
- const usdcAddr = this.config.contracts.usdc;
1985
2299
  let params;
1986
2300
  if (marketParams) {
1987
2301
  params = marketParams;
1988
2302
  } else if (tokenSymbol) {
1989
- params = await this.findMarketForCollateral(tokenSymbol);
2303
+ params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
1990
2304
  } else {
1991
2305
  const { params: p } = await this._findActiveMarket();
1992
2306
  params = p;
1993
2307
  }
2308
+ const loanTokenAddr = params.loanToken;
2309
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1994
2310
  let repayAssets;
1995
2311
  let repayShares;
1996
2312
  let approveAmount;
1997
- if (usdcAmount === "all") {
2313
+ if (amount === "all") {
1998
2314
  const markets = await this.getMarkets();
1999
2315
  const mkt = markets.find(
2000
- (m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase()
2316
+ (m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase() && m.loanAsset?.address.toLowerCase() === params.loanToken.toLowerCase()
2001
2317
  );
2002
2318
  if (mkt) {
2003
2319
  const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
@@ -2007,33 +2323,35 @@ var MorphoClient = class {
2007
2323
  const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
2008
2324
  const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
2009
2325
  const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
2010
- approveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1", 6);
2326
+ approveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1", loanDecimals);
2011
2327
  } else {
2012
- repayAssets = import_ethers2.ethers.parseUnits("999999", 6);
2328
+ repayAssets = import_ethers2.ethers.parseUnits("999999", loanDecimals);
2013
2329
  repayShares = 0n;
2014
2330
  approveAmount = repayAssets;
2015
2331
  }
2016
2332
  } else {
2017
- repayAssets = import_ethers2.ethers.parseUnits(usdcAmount, 6);
2333
+ repayAssets = import_ethers2.ethers.parseUnits(amount, loanDecimals);
2018
2334
  repayShares = 0n;
2019
2335
  approveAmount = repayAssets;
2020
2336
  }
2021
- const usdcContract = new import_ethers2.Contract(usdcAddr, ERC20_ABI, this._signer);
2022
- const acctBalance = await usdcContract.balanceOf(acctAddr);
2337
+ const loanContract = new import_ethers2.Contract(loanTokenAddr, ERC20_ABI, this._signer);
2338
+ const acctBalance = await loanContract.balanceOf(acctAddr);
2023
2339
  if (acctBalance < approveAmount) {
2024
2340
  const shortfall = approveAmount - acctBalance;
2025
- const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
2341
+ const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
2026
2342
  if (eoaBalance < shortfall) {
2343
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
2344
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
2027
2345
  throw new AgetherError(
2028
- `Insufficient USDC for repay. Need ${import_ethers2.ethers.formatUnits(approveAmount, 6)} USDC, AgentAccount has ${import_ethers2.ethers.formatUnits(acctBalance, 6)}, EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, 6)}.`,
2346
+ `Insufficient ${loanSymbol} for repay. Need ${import_ethers2.ethers.formatUnits(approveAmount, loanDecimals)}, AgentAccount has ${import_ethers2.ethers.formatUnits(acctBalance, loanDecimals)}, EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, loanDecimals)}.`,
2029
2347
  "INSUFFICIENT_BALANCE"
2030
2348
  );
2031
2349
  }
2032
- const transferTx = await usdcContract.transfer(acctAddr, shortfall);
2350
+ const transferTx = await loanContract.transfer(acctAddr, shortfall);
2033
2351
  await transferTx.wait();
2034
2352
  this._refreshSigner();
2035
2353
  }
2036
- const targets = [usdcAddr, morphoAddr];
2354
+ const targets = [loanTokenAddr, morphoAddr];
2037
2355
  const values = [0n, 0n];
2038
2356
  const datas = [
2039
2357
  erc20Iface2.encodeFunctionData("approve", [morphoAddr, approveAmount]),
@@ -2053,7 +2371,7 @@ var MorphoClient = class {
2053
2371
  } catch (e) {
2054
2372
  console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
2055
2373
  }
2056
- return { tx: receipt.hash, amount: usdcAmount, remainingDebt };
2374
+ return { tx: receipt.hash, amount, remainingDebt };
2057
2375
  }
2058
2376
  /**
2059
2377
  * Withdraw collateral from Morpho Blue.
@@ -2067,12 +2385,13 @@ var MorphoClient = class {
2067
2385
  const colInfo = await this._resolveToken(tokenSymbol);
2068
2386
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
2069
2387
  const morphoAddr = this.config.contracts.morphoBlue;
2070
- const usdcAddr = this.config.contracts.usdc;
2388
+ const loanTokenAddr = params.loanToken;
2389
+ const loanDecimals = await this._getLoanTokenDecimals(params);
2071
2390
  const dest = receiver || await this.getSignerAddress();
2072
2391
  let weiAmount;
2073
2392
  const markets = await this.getMarkets();
2074
2393
  const market = markets.find(
2075
- (m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase()
2394
+ (m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase() && m.loanAsset?.address.toLowerCase() === loanTokenAddr.toLowerCase()
2076
2395
  );
2077
2396
  if (amount === "all") {
2078
2397
  if (!market) throw new AgetherError("Market not found", "MARKET_NOT_FOUND");
@@ -2095,8 +2414,10 @@ var MorphoClient = class {
2095
2414
  const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
2096
2415
  const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
2097
2416
  const estimated = totalBorrowShares > 0n ? dustBorrowShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
2098
- dustApproveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1", 6);
2099
- console.log(`[agether] dust borrow shares detected: ${dustBorrowShares} shares \u2248 ${import_ethers2.ethers.formatUnits(dustApproveAmount, 6)} USDC \u2014 auto-repaying before withdraw`);
2417
+ dustApproveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1", loanDecimals);
2418
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
2419
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
2420
+ console.log(`[agether] dust borrow shares detected: ${dustBorrowShares} shares \u2248 ${import_ethers2.ethers.formatUnits(dustApproveAmount, loanDecimals)} ${loanSymbol} \u2014 auto-repaying before withdraw`);
2100
2421
  }
2101
2422
  } catch (e) {
2102
2423
  console.warn("[agether] failed to check borrow shares before withdraw:", e instanceof Error ? e.message : e);
@@ -2110,19 +2431,21 @@ var MorphoClient = class {
2110
2431
  ]);
2111
2432
  let receipt;
2112
2433
  if (hasDustDebt) {
2113
- const usdcContract = new import_ethers2.Contract(usdcAddr, ERC20_ABI, this._signer);
2114
- const acctBalance = await usdcContract.balanceOf(acctAddr);
2434
+ const loanContract = new import_ethers2.Contract(loanTokenAddr, ERC20_ABI, this._signer);
2435
+ const acctBalance = await loanContract.balanceOf(acctAddr);
2115
2436
  if (acctBalance < dustApproveAmount) {
2116
2437
  const shortfall = dustApproveAmount - acctBalance;
2117
- const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
2438
+ const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
2118
2439
  if (eoaBalance >= shortfall) {
2119
- console.log(`[agether] transferring ${import_ethers2.ethers.formatUnits(shortfall, 6)} USDC from EOA \u2192 AgentAccount for dust repay`);
2120
- const transferTx = await usdcContract.transfer(acctAddr, shortfall);
2440
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
2441
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
2442
+ console.log(`[agether] transferring ${import_ethers2.ethers.formatUnits(shortfall, loanDecimals)} ${loanSymbol} from EOA \u2192 AgentAccount for dust repay`);
2443
+ const transferTx = await loanContract.transfer(acctAddr, shortfall);
2121
2444
  await transferTx.wait();
2122
2445
  this._refreshSigner();
2123
2446
  }
2124
2447
  }
2125
- const targets = [usdcAddr, morphoAddr, morphoAddr];
2448
+ const targets = [loanTokenAddr, morphoAddr, morphoAddr];
2126
2449
  const values = [0n, 0n, 0n];
2127
2450
  const datas = [
2128
2451
  erc20Iface2.encodeFunctionData("approve", [morphoAddr, dustApproveAmount]),
@@ -2367,6 +2690,37 @@ var MorphoClient = class {
2367
2690
  }
2368
2691
  throw new AgetherError("No active supply position found", "NO_SUPPLY");
2369
2692
  }
2693
+ /**
2694
+ * Resolve loan token decimals from market params.
2695
+ * Uses the `_tokenCache` populated by `getMarkets()`.
2696
+ */
2697
+ async _getLoanTokenDecimals(params) {
2698
+ const tokenInfo = this._tokenCache.get(params.loanToken.toLowerCase());
2699
+ if (tokenInfo) return tokenInfo.decimals;
2700
+ await this.getMarkets();
2701
+ const fromApi = this._tokenCache.get(params.loanToken.toLowerCase());
2702
+ return fromApi?.decimals ?? 18;
2703
+ }
2704
+ /**
2705
+ * Apply client-side filter to discovered markets.
2706
+ */
2707
+ _applyMarketFilter(markets, filter) {
2708
+ return markets.filter((m) => {
2709
+ if (filter.loanToken) {
2710
+ const loanAddr = filter.loanToken.toLowerCase();
2711
+ if (m.loanAsset.address.toLowerCase() !== loanAddr && m.loanAsset.symbol.toUpperCase() !== filter.loanToken.toUpperCase()) {
2712
+ return false;
2713
+ }
2714
+ }
2715
+ if (filter.collateralToken) {
2716
+ const colAddr = filter.collateralToken.toLowerCase();
2717
+ if (m.collateralAsset.address.toLowerCase() !== colAddr && m.collateralAsset.symbol.toUpperCase() !== filter.collateralToken.toUpperCase()) {
2718
+ return false;
2719
+ }
2720
+ }
2721
+ return true;
2722
+ });
2723
+ }
2370
2724
  /**
2371
2725
  * Resolve a token symbol or address to { address, symbol, decimals }.
2372
2726
  *
@@ -2382,9 +2736,27 @@ var MorphoClient = class {
2382
2736
  await this.getMarkets();
2383
2737
  const fromApi = this._tokenCache.get(key);
2384
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
+ }
2385
2757
  throw new AgetherError(
2386
- `Unknown token: ${symbolOrAddress}. No Morpho market found with this collateral.`,
2387
- "UNKNOWN_COLLATERAL"
2758
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
2759
+ "UNKNOWN_TOKEN"
2388
2760
  );
2389
2761
  }
2390
2762
  /**