@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.mjs CHANGED
@@ -419,25 +419,21 @@ var AgetherClient = class _AgetherClient {
419
419
  const acctAddr = await this.agether4337Factory.getAccount(agentId);
420
420
  this.accountAddress = acctAddr;
421
421
  if (!acctExists) {
422
- try {
423
- const updatedMeta = JSON.stringify({
424
- type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
425
- name: options?.name || "Unnamed Agent",
426
- description: options?.description || "AI agent registered via @agether/sdk",
427
- active: true,
428
- wallet: `eip155:${this.config.chainId}:${acctAddr}`,
429
- registrations: [{
430
- agentId: Number(agentId),
431
- agentRegistry: `eip155:${this.config.chainId}:${this.config.contracts.identityRegistry}`
432
- }]
433
- });
434
- const finalURI = `data:application/json;base64,${Buffer.from(updatedMeta).toString("base64")}`;
435
- const uriTx = await this.identityRegistry.setAgentURI(agentId, finalURI);
436
- await uriTx.wait();
437
- this._refreshSigner();
438
- } catch (e) {
439
- console.warn("[agether] setAgentURI failed (non-fatal):", e instanceof Error ? e.message : e);
440
- }
422
+ const updatedMeta = JSON.stringify({
423
+ type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
424
+ name: options?.name || "Unnamed Agent",
425
+ description: options?.description || "AI agent registered via @agether/sdk",
426
+ active: true,
427
+ wallet: `eip155:${this.config.chainId}:${acctAddr}`,
428
+ registrations: [{
429
+ agentId: Number(agentId),
430
+ agentRegistry: `eip155:${this.config.chainId}:${this.config.contracts.identityRegistry}`
431
+ }]
432
+ });
433
+ const finalURI = `data:application/json;base64,${Buffer.from(updatedMeta).toString("base64")}`;
434
+ const uriTx = await this.identityRegistry.setAgentURI(agentId, finalURI);
435
+ await uriTx.wait();
436
+ this._refreshSigner();
441
437
  }
442
438
  const kyaRequired = await this.isKyaRequired();
443
439
  return {
@@ -455,7 +451,7 @@ var AgetherClient = class _AgetherClient {
455
451
  async _mintNewIdentity(name, description) {
456
452
  const registrationFile = JSON.stringify({
457
453
  type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
458
- name: name || "Unnamed Agent",
454
+ name: name || "Agether Agent",
459
455
  description: description || "AI agent registered via @agether/sdk",
460
456
  active: true,
461
457
  registrations: [{
@@ -966,8 +962,9 @@ var morphoIface = new ethers2.Interface(MORPHO_BLUE_ABI);
966
962
  var erc20Iface2 = new ethers2.Interface(ERC20_ABI);
967
963
  var MorphoClient = class {
968
964
  constructor(config) {
965
+ /** Market params cache: keyed by market uniqueKey (bytes32 hash) */
969
966
  this._marketCache = /* @__PURE__ */ new Map();
970
- /** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
967
+ /** Dynamic token registry: symbol (uppercase) or address (lowercase) → { address, symbol, decimals } */
971
968
  this._tokenCache = /* @__PURE__ */ new Map();
972
969
  this._discoveredAt = 0;
973
970
  if (!config.agentId) {
@@ -1070,21 +1067,23 @@ var MorphoClient = class {
1070
1067
  // Market Discovery (Morpho GraphQL API)
1071
1068
  // ════════════════════════════════════════════════════════
1072
1069
  /**
1073
- * Fetch USDC borrow markets on Base from Morpho API.
1074
- * Caches results for 5 minutes.
1070
+ * Fetch available markets on the current chain from Morpho API.
1071
+ * Caches results for 5 minutes. Supports all loan tokens (not just USDC).
1072
+ *
1073
+ * @param forceRefresh - bypass cache TTL
1074
+ * @param filter - optional filter by loan token and/or collateral token
1075
1075
  */
1076
- async getMarkets(forceRefresh = false) {
1076
+ async getMarkets(forceRefresh = false, filter) {
1077
1077
  if (!forceRefresh && this._discoveredMarkets && Date.now() - this._discoveredAt < 3e5) {
1078
- return this._discoveredMarkets;
1078
+ return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
1079
1079
  }
1080
1080
  const chainId = this.config.chainId;
1081
- const usdcAddr = this.config.contracts.usdc.toLowerCase();
1082
1081
  const query = `{
1083
1082
  markets(
1084
- first: 50
1083
+ first: 500
1085
1084
  orderBy: SupplyAssetsUsd
1086
1085
  orderDirection: Desc
1087
- where: { chainId_in: [${chainId}], loanAssetAddress_in: ["${usdcAddr}"] }
1086
+ where: { chainId_in: [${chainId}] }
1088
1087
  ) {
1089
1088
  items {
1090
1089
  uniqueKey
@@ -1117,14 +1116,14 @@ var MorphoClient = class {
1117
1116
  }));
1118
1117
  this._discoveredAt = Date.now();
1119
1118
  for (const mi of this._discoveredMarkets) {
1119
+ this._marketCache.set(mi.uniqueKey.toLowerCase(), {
1120
+ loanToken: mi.loanAsset.address,
1121
+ collateralToken: mi.collateralAsset.address,
1122
+ oracle: mi.oracle,
1123
+ irm: mi.irm,
1124
+ lltv: mi.lltv
1125
+ });
1120
1126
  if (mi.collateralAsset.address !== ethers2.ZeroAddress) {
1121
- this._marketCache.set(mi.collateralAsset.address.toLowerCase(), {
1122
- loanToken: mi.loanAsset.address,
1123
- collateralToken: mi.collateralAsset.address,
1124
- oracle: mi.oracle,
1125
- irm: mi.irm,
1126
- lltv: mi.lltv
1127
- });
1128
1127
  this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
1129
1128
  address: mi.collateralAsset.address,
1130
1129
  symbol: mi.collateralAsset.symbol,
@@ -1149,17 +1148,24 @@ var MorphoClient = class {
1149
1148
  });
1150
1149
  }
1151
1150
  }
1152
- return this._discoveredMarkets;
1151
+ return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
1153
1152
  } catch (e) {
1154
1153
  console.warn("[agether] getMarkets failed, using cache:", e instanceof Error ? e.message : e);
1155
- return this._discoveredMarkets ?? [];
1154
+ const cached = this._discoveredMarkets ?? [];
1155
+ return filter ? this._applyMarketFilter(cached, filter) : cached;
1156
1156
  }
1157
1157
  }
1158
1158
  /**
1159
- * Get MarketParams for a collateral token.
1160
- * Tries cache → API → onchain idToMarketParams.
1159
+ * Get MarketParams for a collateral token (and optionally a specific loan token).
1160
+ * Tries cache → API discovery.
1161
+ *
1162
+ * When `loanTokenSymbolOrAddress` is omitted, returns the most liquid market
1163
+ * for that collateral (sorted by supply, typically the USDC market).
1164
+ *
1165
+ * @param collateralSymbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
1166
+ * @param loanTokenSymbolOrAddress - e.g. 'USDC', 'WETH', or '0x833589...' (optional)
1161
1167
  */
1162
- async findMarketForCollateral(collateralSymbolOrAddress) {
1168
+ async findMarketForCollateral(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
1163
1169
  let colAddr;
1164
1170
  if (collateralSymbolOrAddress.startsWith("0x")) {
1165
1171
  colAddr = collateralSymbolOrAddress.toLowerCase();
@@ -1171,13 +1177,41 @@ var MorphoClient = class {
1171
1177
  colAddr = collateralSymbolOrAddress.toLowerCase();
1172
1178
  }
1173
1179
  }
1174
- const cached = this._marketCache.get(colAddr);
1175
- if (cached) return cached;
1176
- await this.getMarkets();
1177
- const fromApi = this._marketCache.get(colAddr);
1178
- if (fromApi) return fromApi;
1180
+ let loanAddr;
1181
+ if (loanTokenSymbolOrAddress) {
1182
+ if (loanTokenSymbolOrAddress.startsWith("0x")) {
1183
+ loanAddr = loanTokenSymbolOrAddress.toLowerCase();
1184
+ } else {
1185
+ try {
1186
+ const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
1187
+ loanAddr = resolved.address.toLowerCase();
1188
+ } catch {
1189
+ loanAddr = loanTokenSymbolOrAddress.toLowerCase();
1190
+ }
1191
+ }
1192
+ }
1193
+ if (!this._discoveredMarkets) await this.getMarkets();
1194
+ for (const m of this._discoveredMarkets ?? []) {
1195
+ if (m.collateralAsset.address.toLowerCase() !== colAddr) continue;
1196
+ if (loanAddr && m.loanAsset.address.toLowerCase() !== loanAddr) continue;
1197
+ return {
1198
+ loanToken: m.loanAsset.address,
1199
+ collateralToken: m.collateralAsset.address,
1200
+ oracle: m.oracle,
1201
+ irm: m.irm,
1202
+ lltv: m.lltv
1203
+ };
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
+ }
1179
1213
  throw new AgetherError(
1180
- `No Morpho market found for collateral ${collateralSymbolOrAddress}`,
1214
+ `No Morpho market found for collateral ${collateralSymbolOrAddress}` + (loanTokenSymbolOrAddress ? ` with loan token ${loanTokenSymbolOrAddress}` : ""),
1181
1215
  "MARKET_NOT_FOUND"
1182
1216
  );
1183
1217
  }
@@ -1212,12 +1246,13 @@ var MorphoClient = class {
1212
1246
  const acctAddr = await this.getAccountAddress();
1213
1247
  const markets = await this.getMarkets();
1214
1248
  const positions = [];
1215
- let totalDebt = 0n;
1249
+ let totalDebtFloat = 0;
1216
1250
  for (const m of markets) {
1217
1251
  if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
1218
1252
  try {
1219
1253
  const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
1220
1254
  if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
1255
+ const loanDecimals = m.loanAsset.decimals;
1221
1256
  let debt = 0n;
1222
1257
  if (pos.borrowShares > 0n) {
1223
1258
  try {
@@ -1225,7 +1260,7 @@ var MorphoClient = class {
1225
1260
  const totalBorrowShares = BigInt(mkt.totalBorrowShares);
1226
1261
  const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
1227
1262
  debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
1228
- totalDebt += debt;
1263
+ totalDebtFloat += parseFloat(ethers2.formatUnits(debt, loanDecimals));
1229
1264
  } catch (e) {
1230
1265
  console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
1231
1266
  }
@@ -1233,10 +1268,11 @@ var MorphoClient = class {
1233
1268
  positions.push({
1234
1269
  marketId: m.uniqueKey,
1235
1270
  collateralToken: m.collateralAsset.symbol,
1271
+ loanToken: m.loanAsset.symbol,
1236
1272
  collateral: ethers2.formatUnits(pos.collateral, m.collateralAsset.decimals),
1237
1273
  borrowShares: pos.borrowShares.toString(),
1238
1274
  supplyShares: pos.supplyShares.toString(),
1239
- debt: ethers2.formatUnits(debt, 6)
1275
+ debt: ethers2.formatUnits(debt, loanDecimals)
1240
1276
  });
1241
1277
  } catch (e) {
1242
1278
  console.warn(`[agether] position read failed for market:`, e instanceof Error ? e.message : e);
@@ -1246,16 +1282,28 @@ var MorphoClient = class {
1246
1282
  return {
1247
1283
  agentId: this.agentId,
1248
1284
  agentAccount: acctAddr,
1249
- totalDebt: ethers2.formatUnits(totalDebt, 6),
1285
+ totalDebt: totalDebtFloat.toFixed(6),
1250
1286
  positions
1251
1287
  };
1252
1288
  }
1253
1289
  // ════════════════════════════════════════════════════════
1254
1290
  // Balance & Borrowing Capacity
1255
1291
  // ════════════════════════════════════════════════════════
1292
+ /**
1293
+ * Get the balance of any ERC-20 token in the AgentAccount.
1294
+ * @param symbolOrAddress - token symbol (e.g. 'USDC', 'WETH') or address
1295
+ * @returns balance in raw units
1296
+ */
1297
+ async getTokenBalance(symbolOrAddress) {
1298
+ const acctAddr = await this.getAccountAddress();
1299
+ const tokenInfo = await this._resolveToken(symbolOrAddress);
1300
+ const token = new Contract2(tokenInfo.address, ERC20_ABI, this.provider);
1301
+ return token.balanceOf(acctAddr);
1302
+ }
1256
1303
  /**
1257
1304
  * Get the USDC balance of the AgentAccount.
1258
1305
  * @returns USDC balance in raw units (6 decimals)
1306
+ * @deprecated Use `getTokenBalance('USDC')` instead.
1259
1307
  */
1260
1308
  async getUsdcBalance() {
1261
1309
  const acctAddr = await this.getAccountAddress();
@@ -1263,7 +1311,7 @@ var MorphoClient = class {
1263
1311
  return usdc.balanceOf(acctAddr);
1264
1312
  }
1265
1313
  /**
1266
- * Calculate the maximum additional USDC that can be borrowed
1314
+ * Calculate the maximum additional loan token that can be borrowed
1267
1315
  * given the agent's current collateral and debt across all markets.
1268
1316
  *
1269
1317
  * For each market with collateral deposited:
@@ -1271,7 +1319,7 @@ var MorphoClient = class {
1271
1319
  *
1272
1320
  * Uses the Morpho oracle to price collateral → loan token.
1273
1321
  *
1274
- * @returns Maximum additional USDC borrowable (6 decimals)
1322
+ * @returns Maximum additional borrowable per market (raw units in each market's loan token)
1275
1323
  */
1276
1324
  async getMaxBorrowable() {
1277
1325
  const acctAddr = await this.getAccountAddress();
@@ -1304,6 +1352,8 @@ var MorphoClient = class {
1304
1352
  totalAdditional += maxAdditional;
1305
1353
  byMarket.push({
1306
1354
  collateralToken: m.collateralAsset.symbol,
1355
+ loanToken: m.loanAsset.symbol,
1356
+ loanDecimals: m.loanAsset.decimals,
1307
1357
  maxAdditional,
1308
1358
  currentDebt,
1309
1359
  collateralValue: collateralValueInLoan
@@ -1319,35 +1369,50 @@ var MorphoClient = class {
1319
1369
  // Market Rates & Yield Estimation
1320
1370
  // ════════════════════════════════════════════════════════
1321
1371
  /**
1322
- * Fetch current supply/borrow APY for a collateral market from Morpho GraphQL API.
1372
+ * Fetch current supply/borrow APY for markets from Morpho GraphQL API.
1323
1373
  *
1324
1374
  * Note: On Morpho Blue, collateral does NOT earn yield directly. Supply APY
1325
1375
  * is what lenders earn; borrow APY is what borrowers pay.
1376
+ *
1377
+ * @param collateralSymbolOrAddress - filter by collateral token (optional)
1378
+ * @param loanTokenSymbolOrAddress - filter by loan token (optional). Omit for all loan tokens.
1326
1379
  */
1327
- async getMarketRates(collateralSymbolOrAddress) {
1380
+ async getMarketRates(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
1328
1381
  const chainId = this.config.chainId;
1329
- const usdcAddr = this.config.contracts.usdc.toLowerCase();
1330
1382
  let collateralFilter = "";
1383
+ let loanFilter = "";
1384
+ let searchTerm = "";
1331
1385
  if (collateralSymbolOrAddress) {
1332
- let colAddr;
1333
1386
  if (collateralSymbolOrAddress.startsWith("0x")) {
1334
- colAddr = collateralSymbolOrAddress.toLowerCase();
1387
+ collateralFilter = `, collateralAssetAddress_in: ["${collateralSymbolOrAddress.toLowerCase()}"]`;
1335
1388
  } else {
1336
- try {
1337
- const resolved = await this._resolveToken(collateralSymbolOrAddress);
1338
- colAddr = resolved.address.toLowerCase();
1339
- } catch {
1340
- 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;
1394
+ }
1395
+ }
1396
+ }
1397
+ if (loanTokenSymbolOrAddress) {
1398
+ if (loanTokenSymbolOrAddress.startsWith("0x")) {
1399
+ loanFilter = `, loanAssetAddress_in: ["${loanTokenSymbolOrAddress.toLowerCase()}"]`;
1400
+ } else {
1401
+ const cached = this._tokenCache.get(loanTokenSymbolOrAddress.toUpperCase());
1402
+ if (cached) {
1403
+ loanFilter = `, loanAssetAddress_in: ["${cached.address.toLowerCase()}"]`;
1404
+ } else {
1405
+ searchTerm = searchTerm || loanTokenSymbolOrAddress;
1341
1406
  }
1342
1407
  }
1343
- collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
1344
1408
  }
1409
+ const searchClause = searchTerm ? `, search: "${searchTerm}"` : "";
1345
1410
  const query = `{
1346
1411
  markets(
1347
- first: 50
1412
+ first: 100
1348
1413
  orderBy: SupplyAssetsUsd
1349
1414
  orderDirection: Desc
1350
- where: { chainId_in: [${chainId}], loanAssetAddress_in: ["${usdcAddr}"]${collateralFilter} }
1415
+ where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter}${searchClause} }
1351
1416
  ) {
1352
1417
  items {
1353
1418
  uniqueKey
@@ -1366,23 +1431,234 @@ var MorphoClient = class {
1366
1431
  }`;
1367
1432
  try {
1368
1433
  const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
1369
- const items = resp.data?.data?.markets?.items ?? [];
1370
- return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress).map((m) => ({
1371
- collateralToken: m.collateralAsset.symbol,
1372
- loanToken: m.loanAsset.symbol,
1373
- supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
1374
- borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
1375
- utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
1376
- totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 1e6 : 0,
1377
- totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 1e6 : 0,
1378
- lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
1379
- marketId: m.uniqueKey
1380
- }));
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
+ }
1443
+ return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress).map((m) => {
1444
+ const loanDecimals = m.loanAsset?.decimals ?? 18;
1445
+ return {
1446
+ collateralToken: m.collateralAsset.symbol,
1447
+ loanToken: m.loanAsset.symbol,
1448
+ loanDecimals,
1449
+ supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
1450
+ borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
1451
+ utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
1452
+ totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 10 ** loanDecimals : 0,
1453
+ totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 10 ** loanDecimals : 0,
1454
+ lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
1455
+ marketId: m.uniqueKey
1456
+ };
1457
+ });
1381
1458
  } catch (e) {
1382
1459
  console.warn("[agether] getMarketRates failed:", e instanceof Error ? e.message : e);
1383
1460
  return [];
1384
1461
  }
1385
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
+ }
1386
1662
  /**
1387
1663
  * Estimate theoretical yield for a given collateral amount over a period.
1388
1664
  *
@@ -1411,14 +1687,15 @@ var MorphoClient = class {
1411
1687
  } else {
1412
1688
  try {
1413
1689
  const params = await this.findMarketForCollateral(collateralSymbol);
1690
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1414
1691
  const oracleContract = new Contract2(params.oracle, [
1415
1692
  "function price() view returns (uint256)"
1416
1693
  ], this.provider);
1417
1694
  const oraclePrice = await oracleContract.price();
1418
1695
  const ORACLE_PRICE_SCALE = 10n ** 36n;
1419
1696
  const amountWei = ethers2.parseUnits(amount, colInfo.decimals);
1420
- const valueInUsdc = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
1421
- collateralValueUsd = Number(valueInUsdc) / 1e6;
1697
+ const valueInLoan = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
1698
+ collateralValueUsd = Number(valueInLoan) / 10 ** loanDecimals;
1422
1699
  } catch (e) {
1423
1700
  console.warn("[agether] oracle price fetch for yield estimation failed:", e instanceof Error ? e.message : e);
1424
1701
  throw new AgetherError("Cannot determine collateral value. Provide ethPriceUsd.", "PRICE_UNAVAILABLE");
@@ -1439,61 +1716,65 @@ var MorphoClient = class {
1439
1716
  // Supply-Side (Lending) — earn yield by supplying USDC
1440
1717
  // ════════════════════════════════════════════════════════
1441
1718
  /**
1442
- * Supply USDC to a Morpho Blue market as a lender (earn yield).
1719
+ * Supply loan token to a Morpho Blue market as a lender (earn yield).
1443
1720
  *
1444
1721
  * Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
1445
- * you deposit the loanToken (USDC) into the market's supply pool and earn
1722
+ * you deposit the loanToken into the market's supply pool and earn
1446
1723
  * interest paid by borrowers.
1447
1724
  *
1448
- * @param usdcAmount - Amount of USDC to supply (e.g. '500')
1725
+ * @param amount - Amount of loan token to supply (e.g. '500' for 500 USDC, '0.5' for 0.5 WETH)
1449
1726
  * @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
1450
1727
  * Optional — defaults to highest-APY market
1728
+ * @param loanTokenSymbol - Loan token to filter market (e.g. 'USDC', 'WETH'). Optional.
1451
1729
  */
1452
- async supplyAsset(usdcAmount, collateralSymbol) {
1730
+ async supplyAsset(amount, collateralSymbol, loanTokenSymbol) {
1453
1731
  const acctAddr = await this.getAccountAddress();
1454
- const amount = ethers2.parseUnits(usdcAmount, 6);
1455
1732
  const morphoAddr = this.config.contracts.morphoBlue;
1456
- const usdcAddr = this.config.contracts.usdc;
1457
1733
  let params;
1458
1734
  let usedCollateral;
1459
1735
  if (collateralSymbol) {
1460
- params = await this.findMarketForCollateral(collateralSymbol);
1736
+ params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
1461
1737
  usedCollateral = collateralSymbol;
1462
1738
  } else {
1463
- const rates = await this.getMarketRates();
1739
+ const rates = await this.getMarketRates(void 0, loanTokenSymbol);
1464
1740
  if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
1465
1741
  const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
1466
- params = await this.findMarketForCollateral(best.collateralToken);
1742
+ params = await this.findMarketForCollateral(best.collateralToken, loanTokenSymbol);
1467
1743
  usedCollateral = best.collateralToken;
1468
1744
  }
1745
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1746
+ const loanTokenAddr = params.loanToken;
1747
+ const parsedAmount = ethers2.parseUnits(amount, loanDecimals);
1469
1748
  const marketId = ethers2.keccak256(
1470
1749
  ethers2.AbiCoder.defaultAbiCoder().encode(
1471
1750
  ["address", "address", "address", "address", "uint256"],
1472
1751
  [params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
1473
1752
  )
1474
1753
  );
1475
- const usdcContract = new Contract2(usdcAddr, ERC20_ABI, this._signer);
1476
- const acctBalance = await usdcContract.balanceOf(acctAddr);
1477
- if (acctBalance < amount) {
1478
- const shortfall = amount - acctBalance;
1479
- const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
1754
+ const loanContract = new Contract2(loanTokenAddr, ERC20_ABI, this._signer);
1755
+ const acctBalance = await loanContract.balanceOf(acctAddr);
1756
+ if (acctBalance < parsedAmount) {
1757
+ const shortfall = parsedAmount - acctBalance;
1758
+ const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
1480
1759
  if (eoaBalance < shortfall) {
1760
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
1761
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
1481
1762
  throw new AgetherError(
1482
- `Insufficient USDC. Need ${usdcAmount}, AgentAccount has ${ethers2.formatUnits(acctBalance, 6)}, EOA has ${ethers2.formatUnits(eoaBalance, 6)}.`,
1763
+ `Insufficient ${loanSymbol}. Need ${amount}, AgentAccount has ${ethers2.formatUnits(acctBalance, loanDecimals)}, EOA has ${ethers2.formatUnits(eoaBalance, loanDecimals)}.`,
1483
1764
  "INSUFFICIENT_BALANCE"
1484
1765
  );
1485
1766
  }
1486
- const transferTx = await usdcContract.transfer(acctAddr, shortfall);
1767
+ const transferTx = await loanContract.transfer(acctAddr, shortfall);
1487
1768
  await transferTx.wait();
1488
1769
  this._refreshSigner();
1489
1770
  }
1490
- const targets = [usdcAddr, morphoAddr];
1771
+ const targets = [loanTokenAddr, morphoAddr];
1491
1772
  const values = [0n, 0n];
1492
1773
  const datas = [
1493
- erc20Iface2.encodeFunctionData("approve", [morphoAddr, amount]),
1774
+ erc20Iface2.encodeFunctionData("approve", [morphoAddr, parsedAmount]),
1494
1775
  morphoIface.encodeFunctionData("supply", [
1495
1776
  this._toTuple(params),
1496
- amount,
1777
+ parsedAmount,
1497
1778
  0n,
1498
1779
  acctAddr,
1499
1780
  "0x"
@@ -1502,7 +1783,7 @@ var MorphoClient = class {
1502
1783
  const receipt = await this.batch(targets, values, datas);
1503
1784
  return {
1504
1785
  tx: receipt.hash,
1505
- amount: usdcAmount,
1786
+ amount,
1506
1787
  marketId,
1507
1788
  collateralToken: usedCollateral,
1508
1789
  agentAccount: acctAddr
@@ -1515,17 +1796,26 @@ var MorphoClient = class {
1515
1796
  * @param collateralSymbol - Market collateral to identify which market
1516
1797
  * @param receiver - Destination address (defaults to EOA)
1517
1798
  */
1518
- async withdrawSupply(usdcAmount, collateralSymbol, receiver) {
1799
+ /**
1800
+ * Withdraw supplied loan token (+ earned interest) from a Morpho Blue market.
1801
+ *
1802
+ * @param amount - Amount to withdraw (e.g. '100' or 'all' for full position)
1803
+ * @param collateralSymbol - Market collateral to identify which market
1804
+ * @param receiver - Destination address (defaults to EOA)
1805
+ * @param loanTokenSymbol - Loan token to filter market (optional)
1806
+ */
1807
+ async withdrawSupply(amount, collateralSymbol, receiver, loanTokenSymbol) {
1519
1808
  const acctAddr = await this.getAccountAddress();
1520
1809
  const morphoAddr = this.config.contracts.morphoBlue;
1521
1810
  const dest = receiver || await this.getSignerAddress();
1522
1811
  let params;
1523
1812
  if (collateralSymbol) {
1524
- params = await this.findMarketForCollateral(collateralSymbol);
1813
+ params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
1525
1814
  } else {
1526
1815
  const { params: p } = await this._findActiveSupplyMarket();
1527
1816
  params = p;
1528
1817
  }
1818
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1529
1819
  const marketId = ethers2.keccak256(
1530
1820
  ethers2.AbiCoder.defaultAbiCoder().encode(
1531
1821
  ["address", "address", "address", "address", "uint256"],
@@ -1534,13 +1824,13 @@ var MorphoClient = class {
1534
1824
  );
1535
1825
  let withdrawAssets;
1536
1826
  let withdrawShares;
1537
- if (usdcAmount === "all") {
1827
+ if (amount === "all") {
1538
1828
  const pos = await this.morphoBlue.position(marketId, acctAddr);
1539
1829
  withdrawShares = BigInt(pos.supplyShares);
1540
1830
  withdrawAssets = 0n;
1541
1831
  if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
1542
1832
  } else {
1543
- withdrawAssets = ethers2.parseUnits(usdcAmount, 6);
1833
+ withdrawAssets = ethers2.parseUnits(amount, loanDecimals);
1544
1834
  withdrawShares = 0n;
1545
1835
  }
1546
1836
  const data = morphoIface.encodeFunctionData("withdraw", [
@@ -1558,13 +1848,13 @@ var MorphoClient = class {
1558
1848
  const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
1559
1849
  const totalSupplyShares = BigInt(mkt.totalSupplyShares);
1560
1850
  const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
1561
- remainingSupply = ethers2.formatUnits(currentAssets, 6);
1851
+ remainingSupply = ethers2.formatUnits(currentAssets, loanDecimals);
1562
1852
  } catch (e) {
1563
1853
  console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
1564
1854
  }
1565
1855
  return {
1566
1856
  tx: receipt.hash,
1567
- amount: usdcAmount,
1857
+ amount,
1568
1858
  remainingSupply,
1569
1859
  destination: dest
1570
1860
  };
@@ -1637,14 +1927,13 @@ var MorphoClient = class {
1637
1927
  * Computes available yield, verifies the requested amount doesn't exceed it,
1638
1928
  * then withdraws from the supply position and sends directly to the recipient.
1639
1929
  *
1640
- * @param recipient - Address to receive the USDC
1641
- * @param usdcAmount - Amount to pay from yield (e.g. '5.50')
1930
+ * @param recipient - Address to receive the loan token
1931
+ * @param amount - Amount to pay from yield (e.g. '5.50')
1642
1932
  * @param collateralSymbol - Market collateral to identify which supply position
1643
1933
  */
1644
- async payFromYield(recipient, usdcAmount, collateralSymbol) {
1934
+ async payFromYield(recipient, amount, collateralSymbol) {
1645
1935
  const acctAddr = await this.getAccountAddress();
1646
1936
  const morphoAddr = this.config.contracts.morphoBlue;
1647
- const amount = ethers2.parseUnits(usdcAmount, 6);
1648
1937
  const positions = await this.getSupplyPositions(collateralSymbol);
1649
1938
  if (positions.length === 0) {
1650
1939
  throw new AgetherError("No supply position found", "NO_SUPPLY");
@@ -1652,17 +1941,20 @@ var MorphoClient = class {
1652
1941
  const pos = positions.reduce(
1653
1942
  (a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
1654
1943
  );
1655
- const availableYield = ethers2.parseUnits(pos.earnedYield, 6);
1656
- if (amount > availableYield) {
1944
+ const params = await this.findMarketForCollateral(pos.collateralToken, pos.loanToken);
1945
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1946
+ const parsedAmount = ethers2.parseUnits(amount, loanDecimals);
1947
+ const availableYield = ethers2.parseUnits(pos.earnedYield, loanDecimals);
1948
+ if (parsedAmount > availableYield) {
1949
+ const loanSymbol = pos.loanToken;
1657
1950
  throw new AgetherError(
1658
- `Requested ${usdcAmount} USDC exceeds available yield of ${pos.earnedYield} USDC. Use withdrawSupply to withdraw principal.`,
1951
+ `Requested ${amount} ${loanSymbol} exceeds available yield of ${pos.earnedYield} ${loanSymbol}. Use withdrawSupply to withdraw principal.`,
1659
1952
  "EXCEEDS_YIELD"
1660
1953
  );
1661
1954
  }
1662
- const params = await this.findMarketForCollateral(pos.collateralToken);
1663
1955
  const data = morphoIface.encodeFunctionData("withdraw", [
1664
1956
  this._toTuple(params),
1665
- amount,
1957
+ parsedAmount,
1666
1958
  0n,
1667
1959
  acctAddr,
1668
1960
  recipient
@@ -1681,7 +1973,7 @@ var MorphoClient = class {
1681
1973
  }
1682
1974
  return {
1683
1975
  tx: receipt.hash,
1684
- yieldWithdrawn: usdcAmount,
1976
+ yieldWithdrawn: amount,
1685
1977
  recipient,
1686
1978
  remainingYield,
1687
1979
  remainingSupply
@@ -1739,28 +2031,31 @@ var MorphoClient = class {
1739
2031
  };
1740
2032
  }
1741
2033
  /**
1742
- * Borrow USDC against existing collateral.
2034
+ * Borrow loan token against existing collateral.
1743
2035
  *
1744
2036
  * AgentAccount.execute: Morpho.borrow(params, amount, 0, account, account)
1745
2037
  *
1746
- * @param usdcAmount - USDC amount (e.g. '100')
2038
+ * @param amount - Loan token amount (e.g. '100' for 100 USDC, '0.5' for 0.5 WETH)
1747
2039
  * @param tokenSymbol - collateral symbol to identify which market (default: first with collateral)
2040
+ * @param marketParams - explicit market params (optional)
2041
+ * @param loanTokenSymbol - loan token to filter market (optional, e.g. 'USDC', 'WETH')
1748
2042
  */
1749
- async borrow(usdcAmount, tokenSymbol, marketParams) {
2043
+ async borrow(amount, tokenSymbol, marketParams, loanTokenSymbol) {
1750
2044
  const acctAddr = await this.getAccountAddress();
1751
- const amount = ethers2.parseUnits(usdcAmount, 6);
1752
2045
  const morphoAddr = this.config.contracts.morphoBlue;
1753
2046
  let params;
1754
2047
  let usedToken = tokenSymbol || "WETH";
1755
2048
  if (marketParams) {
1756
2049
  params = marketParams;
1757
2050
  } else if (tokenSymbol) {
1758
- params = await this.findMarketForCollateral(tokenSymbol);
2051
+ params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
1759
2052
  } else {
1760
2053
  const { params: p, symbol } = await this._findActiveMarket();
1761
2054
  params = p;
1762
2055
  usedToken = symbol;
1763
2056
  }
2057
+ const loanDecimals = await this._getLoanTokenDecimals(params);
2058
+ const parsedAmount = ethers2.parseUnits(amount, loanDecimals);
1764
2059
  try {
1765
2060
  const marketId = ethers2.keccak256(
1766
2061
  ethers2.AbiCoder.defaultAbiCoder().encode(
@@ -1784,11 +2079,14 @@ var MorphoClient = class {
1784
2079
  const totalBorrowAssets = BigInt(mktState.totalBorrowAssets);
1785
2080
  const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
1786
2081
  const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
1787
- if (amount > maxAdditional) {
1788
- const maxUsd = ethers2.formatUnits(maxAdditional, 6);
1789
- const colFormatted = ethers2.formatUnits(pos.collateral, 18);
2082
+ if (parsedAmount > maxAdditional) {
2083
+ const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
2084
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
2085
+ const colInfo = await this._resolveToken(usedToken);
2086
+ const maxFormatted = ethers2.formatUnits(maxAdditional, loanDecimals);
2087
+ const colFormatted = ethers2.formatUnits(pos.collateral, colInfo.decimals);
1790
2088
  throw new AgetherError(
1791
- `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.`,
2089
+ `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.`,
1792
2090
  "EXCEEDS_MAX_LTV"
1793
2091
  );
1794
2092
  }
@@ -1798,7 +2096,7 @@ var MorphoClient = class {
1798
2096
  }
1799
2097
  const data = morphoIface.encodeFunctionData("borrow", [
1800
2098
  this._toTuple(params),
1801
- amount,
2099
+ parsedAmount,
1802
2100
  0n,
1803
2101
  acctAddr,
1804
2102
  acctAddr
@@ -1806,7 +2104,7 @@ var MorphoClient = class {
1806
2104
  const receipt = await this.exec(morphoAddr, data);
1807
2105
  return {
1808
2106
  tx: receipt.hash,
1809
- amount: usdcAmount,
2107
+ amount,
1810
2108
  collateralToken: usedToken,
1811
2109
  agentAccount: acctAddr
1812
2110
  };
@@ -1814,17 +2112,27 @@ var MorphoClient = class {
1814
2112
  /**
1815
2113
  * Deposit collateral AND borrow USDC in one batched transaction.
1816
2114
  *
2115
+ /**
2116
+ * Deposit collateral AND borrow loan token in one batched transaction.
2117
+ *
1817
2118
  * AgentAccount.executeBatch:
1818
2119
  * [collateral.approve, Morpho.supplyCollateral, Morpho.borrow]
1819
2120
  *
1820
2121
  * The collateral must be transferred to AgentAccount first.
2122
+ *
2123
+ * @param tokenSymbol - collateral token symbol (e.g. 'WETH')
2124
+ * @param collateralAmount - amount of collateral (e.g. '0.05')
2125
+ * @param borrowAmount - amount of loan token to borrow (e.g. '100')
2126
+ * @param marketParams - explicit market params (optional)
2127
+ * @param loanTokenSymbol - loan token to filter market (optional)
1821
2128
  */
1822
- async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
2129
+ async depositAndBorrow(tokenSymbol, collateralAmount, borrowAmount, marketParams, loanTokenSymbol) {
1823
2130
  const acctAddr = await this.getAccountAddress();
1824
2131
  const colInfo = await this._resolveToken(tokenSymbol);
1825
- const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
2132
+ const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
2133
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1826
2134
  const colWei = ethers2.parseUnits(collateralAmount, colInfo.decimals);
1827
- const borrowWei = ethers2.parseUnits(borrowUsdcAmount, 6);
2135
+ const borrowWei = ethers2.parseUnits(borrowAmount, loanDecimals);
1828
2136
  const morphoAddr = this.config.contracts.morphoBlue;
1829
2137
  try {
1830
2138
  const marketId = ethers2.keccak256(
@@ -1845,9 +2153,11 @@ var MorphoClient = class {
1845
2153
  const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
1846
2154
  const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
1847
2155
  if (borrowWei > maxAdditional) {
1848
- const maxUsd = ethers2.formatUnits(maxAdditional, 6);
2156
+ const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
2157
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
2158
+ const maxFormatted = ethers2.formatUnits(maxAdditional, loanDecimals);
1849
2159
  throw new AgetherError(
1850
- `Borrow of $${borrowUsdcAmount} USDC exceeds max borrowable $${maxUsd} USDC (total collateral: ${ethers2.formatUnits(totalCollateral, colInfo.decimals)} ${tokenSymbol}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Reduce borrow or increase collateral.`,
2160
+ `Borrow of ${borrowAmount} ${loanSymbol} exceeds max borrowable ${maxFormatted} ${loanSymbol} (total collateral: ${ethers2.formatUnits(totalCollateral, colInfo.decimals)} ${tokenSymbol}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Reduce borrow or increase collateral.`,
1851
2161
  "EXCEEDS_MAX_LTV"
1852
2162
  );
1853
2163
  }
@@ -1893,36 +2203,42 @@ var MorphoClient = class {
1893
2203
  tx: receipt.hash,
1894
2204
  collateralToken: tokenSymbol,
1895
2205
  collateralAmount,
1896
- borrowAmount: borrowUsdcAmount,
2206
+ borrowAmount,
1897
2207
  agentAccount: acctAddr
1898
2208
  };
1899
2209
  }
1900
2210
  /**
1901
- * Repay borrowed USDC from AgentAccount.
2211
+ * Repay borrowed loan token from AgentAccount.
1902
2212
  *
1903
2213
  * AgentAccount.executeBatch:
1904
- * [USDC.approve(MorphoBlue), Morpho.repay(params)]
2214
+ * [loanToken.approve(MorphoBlue), Morpho.repay(params)]
2215
+ *
2216
+ * @param amount - loan token amount to repay (e.g. '50' or 'all' for full repayment)
2217
+ * @param tokenSymbol - collateral symbol to identify which market (optional)
2218
+ * @param marketParams - explicit market params (optional)
2219
+ * @param loanTokenSymbol - loan token to filter market (optional)
1905
2220
  */
1906
- async repay(usdcAmount, tokenSymbol, marketParams) {
2221
+ async repay(amount, tokenSymbol, marketParams, loanTokenSymbol) {
1907
2222
  const acctAddr = await this.getAccountAddress();
1908
2223
  const morphoAddr = this.config.contracts.morphoBlue;
1909
- const usdcAddr = this.config.contracts.usdc;
1910
2224
  let params;
1911
2225
  if (marketParams) {
1912
2226
  params = marketParams;
1913
2227
  } else if (tokenSymbol) {
1914
- params = await this.findMarketForCollateral(tokenSymbol);
2228
+ params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
1915
2229
  } else {
1916
2230
  const { params: p } = await this._findActiveMarket();
1917
2231
  params = p;
1918
2232
  }
2233
+ const loanTokenAddr = params.loanToken;
2234
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1919
2235
  let repayAssets;
1920
2236
  let repayShares;
1921
2237
  let approveAmount;
1922
- if (usdcAmount === "all") {
2238
+ if (amount === "all") {
1923
2239
  const markets = await this.getMarkets();
1924
2240
  const mkt = markets.find(
1925
- (m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase()
2241
+ (m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase() && m.loanAsset?.address.toLowerCase() === params.loanToken.toLowerCase()
1926
2242
  );
1927
2243
  if (mkt) {
1928
2244
  const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
@@ -1932,33 +2248,35 @@ var MorphoClient = class {
1932
2248
  const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
1933
2249
  const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
1934
2250
  const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
1935
- approveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1", 6);
2251
+ approveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1", loanDecimals);
1936
2252
  } else {
1937
- repayAssets = ethers2.parseUnits("999999", 6);
2253
+ repayAssets = ethers2.parseUnits("999999", loanDecimals);
1938
2254
  repayShares = 0n;
1939
2255
  approveAmount = repayAssets;
1940
2256
  }
1941
2257
  } else {
1942
- repayAssets = ethers2.parseUnits(usdcAmount, 6);
2258
+ repayAssets = ethers2.parseUnits(amount, loanDecimals);
1943
2259
  repayShares = 0n;
1944
2260
  approveAmount = repayAssets;
1945
2261
  }
1946
- const usdcContract = new Contract2(usdcAddr, ERC20_ABI, this._signer);
1947
- const acctBalance = await usdcContract.balanceOf(acctAddr);
2262
+ const loanContract = new Contract2(loanTokenAddr, ERC20_ABI, this._signer);
2263
+ const acctBalance = await loanContract.balanceOf(acctAddr);
1948
2264
  if (acctBalance < approveAmount) {
1949
2265
  const shortfall = approveAmount - acctBalance;
1950
- const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
2266
+ const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
1951
2267
  if (eoaBalance < shortfall) {
2268
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
2269
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
1952
2270
  throw new AgetherError(
1953
- `Insufficient USDC for repay. Need ${ethers2.formatUnits(approveAmount, 6)} USDC, AgentAccount has ${ethers2.formatUnits(acctBalance, 6)}, EOA has ${ethers2.formatUnits(eoaBalance, 6)}.`,
2271
+ `Insufficient ${loanSymbol} for repay. Need ${ethers2.formatUnits(approveAmount, loanDecimals)}, AgentAccount has ${ethers2.formatUnits(acctBalance, loanDecimals)}, EOA has ${ethers2.formatUnits(eoaBalance, loanDecimals)}.`,
1954
2272
  "INSUFFICIENT_BALANCE"
1955
2273
  );
1956
2274
  }
1957
- const transferTx = await usdcContract.transfer(acctAddr, shortfall);
2275
+ const transferTx = await loanContract.transfer(acctAddr, shortfall);
1958
2276
  await transferTx.wait();
1959
2277
  this._refreshSigner();
1960
2278
  }
1961
- const targets = [usdcAddr, morphoAddr];
2279
+ const targets = [loanTokenAddr, morphoAddr];
1962
2280
  const values = [0n, 0n];
1963
2281
  const datas = [
1964
2282
  erc20Iface2.encodeFunctionData("approve", [morphoAddr, approveAmount]),
@@ -1978,7 +2296,7 @@ var MorphoClient = class {
1978
2296
  } catch (e) {
1979
2297
  console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
1980
2298
  }
1981
- return { tx: receipt.hash, amount: usdcAmount, remainingDebt };
2299
+ return { tx: receipt.hash, amount, remainingDebt };
1982
2300
  }
1983
2301
  /**
1984
2302
  * Withdraw collateral from Morpho Blue.
@@ -1992,12 +2310,13 @@ var MorphoClient = class {
1992
2310
  const colInfo = await this._resolveToken(tokenSymbol);
1993
2311
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1994
2312
  const morphoAddr = this.config.contracts.morphoBlue;
1995
- const usdcAddr = this.config.contracts.usdc;
2313
+ const loanTokenAddr = params.loanToken;
2314
+ const loanDecimals = await this._getLoanTokenDecimals(params);
1996
2315
  const dest = receiver || await this.getSignerAddress();
1997
2316
  let weiAmount;
1998
2317
  const markets = await this.getMarkets();
1999
2318
  const market = markets.find(
2000
- (m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase()
2319
+ (m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase() && m.loanAsset?.address.toLowerCase() === loanTokenAddr.toLowerCase()
2001
2320
  );
2002
2321
  if (amount === "all") {
2003
2322
  if (!market) throw new AgetherError("Market not found", "MARKET_NOT_FOUND");
@@ -2020,8 +2339,10 @@ var MorphoClient = class {
2020
2339
  const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
2021
2340
  const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
2022
2341
  const estimated = totalBorrowShares > 0n ? dustBorrowShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
2023
- dustApproveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1", 6);
2024
- console.log(`[agether] dust borrow shares detected: ${dustBorrowShares} shares \u2248 ${ethers2.formatUnits(dustApproveAmount, 6)} USDC \u2014 auto-repaying before withdraw`);
2342
+ dustApproveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1", loanDecimals);
2343
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
2344
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
2345
+ console.log(`[agether] dust borrow shares detected: ${dustBorrowShares} shares \u2248 ${ethers2.formatUnits(dustApproveAmount, loanDecimals)} ${loanSymbol} \u2014 auto-repaying before withdraw`);
2025
2346
  }
2026
2347
  } catch (e) {
2027
2348
  console.warn("[agether] failed to check borrow shares before withdraw:", e instanceof Error ? e.message : e);
@@ -2035,19 +2356,21 @@ var MorphoClient = class {
2035
2356
  ]);
2036
2357
  let receipt;
2037
2358
  if (hasDustDebt) {
2038
- const usdcContract = new Contract2(usdcAddr, ERC20_ABI, this._signer);
2039
- const acctBalance = await usdcContract.balanceOf(acctAddr);
2359
+ const loanContract = new Contract2(loanTokenAddr, ERC20_ABI, this._signer);
2360
+ const acctBalance = await loanContract.balanceOf(acctAddr);
2040
2361
  if (acctBalance < dustApproveAmount) {
2041
2362
  const shortfall = dustApproveAmount - acctBalance;
2042
- const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
2363
+ const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
2043
2364
  if (eoaBalance >= shortfall) {
2044
- console.log(`[agether] transferring ${ethers2.formatUnits(shortfall, 6)} USDC from EOA \u2192 AgentAccount for dust repay`);
2045
- const transferTx = await usdcContract.transfer(acctAddr, shortfall);
2365
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
2366
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
2367
+ console.log(`[agether] transferring ${ethers2.formatUnits(shortfall, loanDecimals)} ${loanSymbol} from EOA \u2192 AgentAccount for dust repay`);
2368
+ const transferTx = await loanContract.transfer(acctAddr, shortfall);
2046
2369
  await transferTx.wait();
2047
2370
  this._refreshSigner();
2048
2371
  }
2049
2372
  }
2050
- const targets = [usdcAddr, morphoAddr, morphoAddr];
2373
+ const targets = [loanTokenAddr, morphoAddr, morphoAddr];
2051
2374
  const values = [0n, 0n, 0n];
2052
2375
  const datas = [
2053
2376
  erc20Iface2.encodeFunctionData("approve", [morphoAddr, dustApproveAmount]),
@@ -2292,6 +2615,37 @@ var MorphoClient = class {
2292
2615
  }
2293
2616
  throw new AgetherError("No active supply position found", "NO_SUPPLY");
2294
2617
  }
2618
+ /**
2619
+ * Resolve loan token decimals from market params.
2620
+ * Uses the `_tokenCache` populated by `getMarkets()`.
2621
+ */
2622
+ async _getLoanTokenDecimals(params) {
2623
+ const tokenInfo = this._tokenCache.get(params.loanToken.toLowerCase());
2624
+ if (tokenInfo) return tokenInfo.decimals;
2625
+ await this.getMarkets();
2626
+ const fromApi = this._tokenCache.get(params.loanToken.toLowerCase());
2627
+ return fromApi?.decimals ?? 18;
2628
+ }
2629
+ /**
2630
+ * Apply client-side filter to discovered markets.
2631
+ */
2632
+ _applyMarketFilter(markets, filter) {
2633
+ return markets.filter((m) => {
2634
+ if (filter.loanToken) {
2635
+ const loanAddr = filter.loanToken.toLowerCase();
2636
+ if (m.loanAsset.address.toLowerCase() !== loanAddr && m.loanAsset.symbol.toUpperCase() !== filter.loanToken.toUpperCase()) {
2637
+ return false;
2638
+ }
2639
+ }
2640
+ if (filter.collateralToken) {
2641
+ const colAddr = filter.collateralToken.toLowerCase();
2642
+ if (m.collateralAsset.address.toLowerCase() !== colAddr && m.collateralAsset.symbol.toUpperCase() !== filter.collateralToken.toUpperCase()) {
2643
+ return false;
2644
+ }
2645
+ }
2646
+ return true;
2647
+ });
2648
+ }
2295
2649
  /**
2296
2650
  * Resolve a token symbol or address to { address, symbol, decimals }.
2297
2651
  *
@@ -2307,9 +2661,27 @@ var MorphoClient = class {
2307
2661
  await this.getMarkets();
2308
2662
  const fromApi = this._tokenCache.get(key);
2309
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
+ }
2310
2682
  throw new AgetherError(
2311
- `Unknown token: ${symbolOrAddress}. No Morpho market found with this collateral.`,
2312
- "UNKNOWN_COLLATERAL"
2683
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
2684
+ "UNKNOWN_TOKEN"
2313
2685
  );
2314
2686
  }
2315
2687
  /**