@agether/sdk 2.3.4 → 2.5.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
@@ -460,11 +460,6 @@ var AgetherClient = class _AgetherClient {
460
460
  // src/clients/MorphoClient.ts
461
461
  import { ethers as ethers2, Contract as Contract2 } from "ethers";
462
462
  import axios from "axios";
463
- var BASE_COLLATERALS = {
464
- WETH: { address: "0x4200000000000000000000000000000000000006", symbol: "WETH", decimals: 18 },
465
- wstETH: { address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", symbol: "wstETH", decimals: 18 },
466
- cbETH: { address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", symbol: "cbETH", decimals: 18 }
467
- };
468
463
  var MORPHO_API_URL = "https://api.morpho.org/graphql";
469
464
  var MODE_SINGLE = "0x0000000000000000000000000000000000000000000000000000000000000000";
470
465
  var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
@@ -473,6 +468,8 @@ var erc20Iface = new ethers2.Interface(ERC20_ABI);
473
468
  var MorphoClient = class {
474
469
  constructor(config) {
475
470
  this._marketCache = /* @__PURE__ */ new Map();
471
+ /** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
472
+ this._tokenCache = /* @__PURE__ */ new Map();
476
473
  this._discoveredAt = 0;
477
474
  const chainId = config.chainId ?? 8453 /* Base */;
478
475
  const defaultCfg = getDefaultConfig(chainId);
@@ -659,15 +656,16 @@ var MorphoClient = class {
659
656
  const usdc = new Contract2(this.config.contracts.usdc, ERC20_ABI, this.provider);
660
657
  const ethBal = await this.provider.getBalance(eoaAddr);
661
658
  const usdcBal = await usdc.balanceOf(eoaAddr);
659
+ const discoveredTokens = await this._getDiscoveredTokens();
662
660
  const eoaCollateral = {};
663
- for (const [symbol, info] of Object.entries(BASE_COLLATERALS)) {
661
+ for (const info of discoveredTokens) {
664
662
  try {
665
663
  const token = new Contract2(info.address, ERC20_ABI, this.provider);
666
664
  const bal = await token.balanceOf(eoaAddr);
667
- eoaCollateral[symbol] = ethers2.formatUnits(bal, info.decimals);
665
+ eoaCollateral[info.symbol] = ethers2.formatUnits(bal, info.decimals);
668
666
  } catch (e) {
669
- console.warn(`[agether] EOA collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
670
- eoaCollateral[symbol] = "0";
667
+ console.warn(`[agether] EOA collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
668
+ eoaCollateral[info.symbol] = "0";
671
669
  }
672
670
  }
673
671
  const result = {
@@ -682,14 +680,14 @@ var MorphoClient = class {
682
680
  const acctEth = await this.provider.getBalance(acctAddr);
683
681
  const acctUsdc = await usdc.balanceOf(acctAddr);
684
682
  const acctCollateral = {};
685
- for (const [symbol, info] of Object.entries(BASE_COLLATERALS)) {
683
+ for (const info of discoveredTokens) {
686
684
  try {
687
685
  const token = new Contract2(info.address, ERC20_ABI, this.provider);
688
686
  const bal = await token.balanceOf(acctAddr);
689
- acctCollateral[symbol] = ethers2.formatUnits(bal, info.decimals);
687
+ acctCollateral[info.symbol] = ethers2.formatUnits(bal, info.decimals);
690
688
  } catch (e) {
691
- console.warn(`[agether] AgentAccount collateral fetch failed for ${symbol}:`, e instanceof Error ? e.message : e);
692
- acctCollateral[symbol] = "0";
689
+ console.warn(`[agether] AgentAccount collateral fetch failed for ${info.symbol}:`, e instanceof Error ? e.message : e);
690
+ acctCollateral[info.symbol] = "0";
693
691
  }
694
692
  }
695
693
  result.agentAccount = {
@@ -775,6 +773,28 @@ var MorphoClient = class {
775
773
  irm: mi.irm,
776
774
  lltv: mi.lltv
777
775
  });
776
+ this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
777
+ address: mi.collateralAsset.address,
778
+ symbol: mi.collateralAsset.symbol,
779
+ decimals: mi.collateralAsset.decimals
780
+ });
781
+ this._tokenCache.set(mi.collateralAsset.address.toLowerCase(), {
782
+ address: mi.collateralAsset.address,
783
+ symbol: mi.collateralAsset.symbol,
784
+ decimals: mi.collateralAsset.decimals
785
+ });
786
+ }
787
+ if (mi.loanAsset.address !== ethers2.ZeroAddress) {
788
+ this._tokenCache.set(mi.loanAsset.symbol.toUpperCase(), {
789
+ address: mi.loanAsset.address,
790
+ symbol: mi.loanAsset.symbol,
791
+ decimals: mi.loanAsset.decimals
792
+ });
793
+ this._tokenCache.set(mi.loanAsset.address.toLowerCase(), {
794
+ address: mi.loanAsset.address,
795
+ symbol: mi.loanAsset.symbol,
796
+ decimals: mi.loanAsset.decimals
797
+ });
778
798
  }
779
799
  }
780
800
  return this._discoveredMarkets;
@@ -788,8 +808,17 @@ var MorphoClient = class {
788
808
  * Tries cache → API → onchain idToMarketParams.
789
809
  */
790
810
  async findMarketForCollateral(collateralSymbolOrAddress) {
791
- const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
792
- const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
811
+ let colAddr;
812
+ if (collateralSymbolOrAddress.startsWith("0x")) {
813
+ colAddr = collateralSymbolOrAddress.toLowerCase();
814
+ } else {
815
+ try {
816
+ const resolved = await this._resolveToken(collateralSymbolOrAddress);
817
+ colAddr = resolved.address.toLowerCase();
818
+ } catch {
819
+ colAddr = collateralSymbolOrAddress.toLowerCase();
820
+ }
821
+ }
793
822
  const cached = this._marketCache.get(colAddr);
794
823
  if (cached) return cached;
795
824
  await this.getMarkets();
@@ -948,8 +977,17 @@ var MorphoClient = class {
948
977
  const usdcAddr = this.config.contracts.usdc.toLowerCase();
949
978
  let collateralFilter = "";
950
979
  if (collateralSymbolOrAddress) {
951
- const colInfo = BASE_COLLATERALS[collateralSymbolOrAddress];
952
- const colAddr = (colInfo?.address ?? collateralSymbolOrAddress).toLowerCase();
980
+ let colAddr;
981
+ if (collateralSymbolOrAddress.startsWith("0x")) {
982
+ colAddr = collateralSymbolOrAddress.toLowerCase();
983
+ } else {
984
+ try {
985
+ const resolved = await this._resolveToken(collateralSymbolOrAddress);
986
+ colAddr = resolved.address.toLowerCase();
987
+ } catch {
988
+ colAddr = collateralSymbolOrAddress.toLowerCase();
989
+ }
990
+ }
953
991
  collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
954
992
  }
955
993
  const query = `{
@@ -1008,8 +1046,7 @@ var MorphoClient = class {
1008
1046
  * @returns Estimated yield in USD for the period
1009
1047
  */
1010
1048
  async getYieldEstimate(collateralSymbol, amount, periodDays = 1, ethPriceUsd) {
1011
- const colInfo = BASE_COLLATERALS[collateralSymbol];
1012
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${collateralSymbol}`, "UNKNOWN_COLLATERAL");
1049
+ const colInfo = await this._resolveToken(collateralSymbol);
1013
1050
  const rates = await this.getMarketRates(collateralSymbol);
1014
1051
  if (rates.length === 0) {
1015
1052
  throw new AgetherError(`No market found for ${collateralSymbol}`, "MARKET_NOT_FOUND");
@@ -1047,7 +1084,259 @@ var MorphoClient = class {
1047
1084
  };
1048
1085
  }
1049
1086
  // ════════════════════════════════════════════════════════
1050
- // Lending Operations (all via AgentAccount.executeBatch)
1087
+ // Supply-Side (Lending) earn yield by supplying USDC
1088
+ // ════════════════════════════════════════════════════════
1089
+ /**
1090
+ * Supply USDC to a Morpho Blue market as a lender (earn yield).
1091
+ *
1092
+ * Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
1093
+ * you deposit the loanToken (USDC) into the market's supply pool and earn
1094
+ * interest paid by borrowers.
1095
+ *
1096
+ * @param usdcAmount - Amount of USDC to supply (e.g. '500')
1097
+ * @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
1098
+ * Optional — defaults to highest-APY market
1099
+ */
1100
+ async supplyAsset(usdcAmount, collateralSymbol) {
1101
+ const acctAddr = await this.getAccountAddress();
1102
+ const amount = ethers2.parseUnits(usdcAmount, 6);
1103
+ const morphoAddr = this.config.contracts.morphoBlue;
1104
+ const usdcAddr = this.config.contracts.usdc;
1105
+ let params;
1106
+ let usedCollateral;
1107
+ if (collateralSymbol) {
1108
+ params = await this.findMarketForCollateral(collateralSymbol);
1109
+ usedCollateral = collateralSymbol;
1110
+ } else {
1111
+ const rates = await this.getMarketRates();
1112
+ if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
1113
+ const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
1114
+ params = await this.findMarketForCollateral(best.collateralToken);
1115
+ usedCollateral = best.collateralToken;
1116
+ }
1117
+ const marketId = ethers2.keccak256(
1118
+ ethers2.AbiCoder.defaultAbiCoder().encode(
1119
+ ["address", "address", "address", "address", "uint256"],
1120
+ [params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
1121
+ )
1122
+ );
1123
+ const usdcContract = new Contract2(usdcAddr, ERC20_ABI, this._signer);
1124
+ const acctBalance = await usdcContract.balanceOf(acctAddr);
1125
+ if (acctBalance < amount) {
1126
+ const shortfall = amount - acctBalance;
1127
+ const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
1128
+ if (eoaBalance < shortfall) {
1129
+ throw new AgetherError(
1130
+ `Insufficient USDC. Need ${usdcAmount}, AgentAccount has ${ethers2.formatUnits(acctBalance, 6)}, EOA has ${ethers2.formatUnits(eoaBalance, 6)}.`,
1131
+ "INSUFFICIENT_BALANCE"
1132
+ );
1133
+ }
1134
+ const transferTx = await usdcContract.transfer(acctAddr, shortfall);
1135
+ await transferTx.wait();
1136
+ this._refreshSigner();
1137
+ }
1138
+ const targets = [usdcAddr, morphoAddr];
1139
+ const values = [0n, 0n];
1140
+ const datas = [
1141
+ erc20Iface.encodeFunctionData("approve", [morphoAddr, amount]),
1142
+ morphoIface.encodeFunctionData("supply", [
1143
+ this._toTuple(params),
1144
+ amount,
1145
+ 0n,
1146
+ acctAddr,
1147
+ "0x"
1148
+ ])
1149
+ ];
1150
+ const receipt = await this.batch(targets, values, datas);
1151
+ return {
1152
+ tx: receipt.hash,
1153
+ amount: usdcAmount,
1154
+ marketId,
1155
+ collateralToken: usedCollateral,
1156
+ agentAccount: acctAddr
1157
+ };
1158
+ }
1159
+ /**
1160
+ * Withdraw supplied USDC (+ earned interest) from a Morpho Blue market.
1161
+ *
1162
+ * @param usdcAmount - Amount to withdraw (e.g. '100' or 'all' for full position)
1163
+ * @param collateralSymbol - Market collateral to identify which market
1164
+ * @param receiver - Destination address (defaults to EOA)
1165
+ */
1166
+ async withdrawSupply(usdcAmount, collateralSymbol, receiver) {
1167
+ const acctAddr = await this.getAccountAddress();
1168
+ const morphoAddr = this.config.contracts.morphoBlue;
1169
+ const dest = receiver || await this.getSignerAddress();
1170
+ let params;
1171
+ if (collateralSymbol) {
1172
+ params = await this.findMarketForCollateral(collateralSymbol);
1173
+ } else {
1174
+ const { params: p } = await this._findActiveSupplyMarket();
1175
+ params = p;
1176
+ }
1177
+ const marketId = ethers2.keccak256(
1178
+ ethers2.AbiCoder.defaultAbiCoder().encode(
1179
+ ["address", "address", "address", "address", "uint256"],
1180
+ [params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
1181
+ )
1182
+ );
1183
+ let withdrawAssets;
1184
+ let withdrawShares;
1185
+ if (usdcAmount === "all") {
1186
+ const pos = await this.morphoBlue.position(marketId, acctAddr);
1187
+ withdrawShares = BigInt(pos.supplyShares);
1188
+ withdrawAssets = 0n;
1189
+ if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
1190
+ } else {
1191
+ withdrawAssets = ethers2.parseUnits(usdcAmount, 6);
1192
+ withdrawShares = 0n;
1193
+ }
1194
+ const data = morphoIface.encodeFunctionData("withdraw", [
1195
+ this._toTuple(params),
1196
+ withdrawAssets,
1197
+ withdrawShares,
1198
+ acctAddr,
1199
+ dest
1200
+ ]);
1201
+ const receipt = await this.exec(morphoAddr, data);
1202
+ let remainingSupply = "0";
1203
+ try {
1204
+ const pos = await this.morphoBlue.position(marketId, acctAddr);
1205
+ const mkt = await this.morphoBlue.market(marketId);
1206
+ const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
1207
+ const totalSupplyShares = BigInt(mkt.totalSupplyShares);
1208
+ const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
1209
+ remainingSupply = ethers2.formatUnits(currentAssets, 6);
1210
+ } catch (e) {
1211
+ console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
1212
+ }
1213
+ return {
1214
+ tx: receipt.hash,
1215
+ amount: usdcAmount,
1216
+ remainingSupply,
1217
+ destination: dest
1218
+ };
1219
+ }
1220
+ /**
1221
+ * Get supply (lending) positions with yield tracking.
1222
+ *
1223
+ * Uses Morpho GraphQL API (no eth_getLogs / no DB / no indexer):
1224
+ * 1. `userByAddress` → all market positions with current supplyAssets, supplyApy
1225
+ * 2. `transactions` → all MarketSupply/MarketWithdraw history for net deposited
1226
+ * 3. earnedYield = currentSupplyAssets − netDeposited
1227
+ *
1228
+ * @param collateralSymbol - Market collateral token (optional, returns all if omitted)
1229
+ */
1230
+ async getSupplyPositions(collateralSymbol) {
1231
+ const acctAddr = (await this.getAccountAddress()).toLowerCase();
1232
+ const chainId = this.config.chainId;
1233
+ const positionsQuery = `{
1234
+ userByAddress(address: "${acctAddr}", chainId: ${chainId}) {
1235
+ marketPositions {
1236
+ market {
1237
+ uniqueKey
1238
+ loanAsset { symbol address decimals }
1239
+ collateralAsset { symbol address }
1240
+ state { supplyApy }
1241
+ }
1242
+ state {
1243
+ supplyShares
1244
+ supplyAssets
1245
+ supplyAssetsUsd
1246
+ }
1247
+ }
1248
+ }
1249
+ }`;
1250
+ const posResp = await axios.post(MORPHO_API_URL, { query: positionsQuery }, { timeout: 15e3 });
1251
+ const user = posResp.data?.data?.userByAddress;
1252
+ if (!user?.marketPositions) return [];
1253
+ const activePositions = user.marketPositions.filter(
1254
+ (p) => p.state && BigInt(p.state.supplyShares ?? "0") > 0n
1255
+ );
1256
+ if (activePositions.length === 0) return [];
1257
+ const filtered = collateralSymbol ? activePositions.filter((p) => {
1258
+ const sym = p.market.collateralAsset?.symbol;
1259
+ return sym && sym.toUpperCase() === collateralSymbol.toUpperCase();
1260
+ }) : activePositions;
1261
+ if (filtered.length === 0) return [];
1262
+ const netDepositedMap = await this._computeNetDepositedAll(acctAddr, chainId);
1263
+ const results = [];
1264
+ for (const p of filtered) {
1265
+ const currentAssets = BigInt(p.state.supplyAssets ?? "0");
1266
+ const marketKey = p.market.uniqueKey.toLowerCase();
1267
+ const netDeposited = netDepositedMap.get(marketKey) ?? 0n;
1268
+ const earnedYield = currentAssets > netDeposited ? currentAssets - netDeposited : 0n;
1269
+ results.push({
1270
+ marketId: p.market.uniqueKey,
1271
+ loanToken: p.market.loanAsset.symbol,
1272
+ collateralToken: p.market.collateralAsset?.symbol ?? "none",
1273
+ supplyShares: p.state.supplyShares,
1274
+ suppliedAssets: ethers2.formatUnits(currentAssets, p.market.loanAsset.decimals),
1275
+ netDeposited: ethers2.formatUnits(netDeposited, p.market.loanAsset.decimals),
1276
+ earnedYield: ethers2.formatUnits(earnedYield, p.market.loanAsset.decimals),
1277
+ supplyApy: p.market.state?.supplyApy ?? 0
1278
+ });
1279
+ }
1280
+ return results;
1281
+ }
1282
+ /**
1283
+ * Pay a recipient using ONLY earned yield from a supply position.
1284
+ *
1285
+ * Computes available yield, verifies the requested amount doesn't exceed it,
1286
+ * then withdraws from the supply position and sends directly to the recipient.
1287
+ *
1288
+ * @param recipient - Address to receive the USDC
1289
+ * @param usdcAmount - Amount to pay from yield (e.g. '5.50')
1290
+ * @param collateralSymbol - Market collateral to identify which supply position
1291
+ */
1292
+ async payFromYield(recipient, usdcAmount, collateralSymbol) {
1293
+ const acctAddr = await this.getAccountAddress();
1294
+ const morphoAddr = this.config.contracts.morphoBlue;
1295
+ const amount = ethers2.parseUnits(usdcAmount, 6);
1296
+ const positions = await this.getSupplyPositions(collateralSymbol);
1297
+ if (positions.length === 0) {
1298
+ throw new AgetherError("No supply position found", "NO_SUPPLY");
1299
+ }
1300
+ const pos = positions.reduce(
1301
+ (a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
1302
+ );
1303
+ const availableYield = ethers2.parseUnits(pos.earnedYield, 6);
1304
+ if (amount > availableYield) {
1305
+ throw new AgetherError(
1306
+ `Requested ${usdcAmount} USDC exceeds available yield of ${pos.earnedYield} USDC. Use withdrawSupply to withdraw principal.`,
1307
+ "EXCEEDS_YIELD"
1308
+ );
1309
+ }
1310
+ const params = await this.findMarketForCollateral(pos.collateralToken);
1311
+ const data = morphoIface.encodeFunctionData("withdraw", [
1312
+ this._toTuple(params),
1313
+ amount,
1314
+ 0n,
1315
+ acctAddr,
1316
+ recipient
1317
+ ]);
1318
+ const receipt = await this.exec(morphoAddr, data);
1319
+ let remainingYield = "0";
1320
+ let remainingSupply = "0";
1321
+ try {
1322
+ const updatedPositions = await this.getSupplyPositions(pos.collateralToken);
1323
+ if (updatedPositions.length > 0) {
1324
+ remainingYield = updatedPositions[0].earnedYield;
1325
+ remainingSupply = updatedPositions[0].suppliedAssets;
1326
+ }
1327
+ } catch (e) {
1328
+ console.warn("[agether] failed to read remaining yield:", e instanceof Error ? e.message : e);
1329
+ }
1330
+ return {
1331
+ tx: receipt.hash,
1332
+ yieldWithdrawn: usdcAmount,
1333
+ recipient,
1334
+ remainingYield,
1335
+ remainingSupply
1336
+ };
1337
+ }
1338
+ // ════════════════════════════════════════════════════════
1339
+ // Collateral & Borrowing Operations (all via AgentAccount)
1051
1340
  // ════════════════════════════════════════════════════════
1052
1341
  /**
1053
1342
  * Deposit collateral into Morpho Blue.
@@ -1059,8 +1348,7 @@ var MorphoClient = class {
1059
1348
  */
1060
1349
  async supplyCollateral(tokenSymbol, amount, marketParams) {
1061
1350
  const acctAddr = await this.getAccountAddress();
1062
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1063
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1351
+ const colInfo = await this._resolveToken(tokenSymbol);
1064
1352
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1065
1353
  const weiAmount = ethers2.parseUnits(amount, colInfo.decimals);
1066
1354
  const morphoAddr = this.config.contracts.morphoBlue;
@@ -1181,8 +1469,7 @@ var MorphoClient = class {
1181
1469
  */
1182
1470
  async depositAndBorrow(tokenSymbol, collateralAmount, borrowUsdcAmount, marketParams) {
1183
1471
  const acctAddr = await this.getAccountAddress();
1184
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1185
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1472
+ const colInfo = await this._resolveToken(tokenSymbol);
1186
1473
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1187
1474
  const colWei = ethers2.parseUnits(collateralAmount, colInfo.decimals);
1188
1475
  const borrowWei = ethers2.parseUnits(borrowUsdcAmount, 6);
@@ -1350,8 +1637,7 @@ var MorphoClient = class {
1350
1637
  */
1351
1638
  async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
1352
1639
  const acctAddr = await this.getAccountAddress();
1353
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1354
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1640
+ const colInfo = await this._resolveToken(tokenSymbol);
1355
1641
  const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
1356
1642
  const morphoAddr = this.config.contracts.morphoBlue;
1357
1643
  const usdcAddr = this.config.contracts.usdc;
@@ -1448,8 +1734,7 @@ var MorphoClient = class {
1448
1734
  * (The agent must then supplyCollateral themselves via their own account.)
1449
1735
  */
1450
1736
  async sponsor(target, tokenSymbol, amount) {
1451
- const colInfo = BASE_COLLATERALS[tokenSymbol];
1452
- if (!colInfo) throw new AgetherError(`Unknown collateral: ${tokenSymbol}`, "UNKNOWN_COLLATERAL");
1737
+ const colInfo = await this._resolveToken(tokenSymbol);
1453
1738
  let targetAddr;
1454
1739
  if (target.address) {
1455
1740
  targetAddr = target.address;
@@ -1678,6 +1963,131 @@ var MorphoClient = class {
1678
1963
  const params = await this.findMarketForCollateral("WETH");
1679
1964
  return { params, symbol: "WETH" };
1680
1965
  }
1966
+ /** Find the first market where the agent has a supply (lending) position. */
1967
+ async _findActiveSupplyMarket() {
1968
+ const acctAddr = await this.getAccountAddress();
1969
+ const markets = await this.getMarkets();
1970
+ for (const m of markets) {
1971
+ if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
1972
+ try {
1973
+ const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
1974
+ if (BigInt(pos.supplyShares) > 0n) {
1975
+ return {
1976
+ params: {
1977
+ loanToken: m.loanAsset.address,
1978
+ collateralToken: m.collateralAsset.address,
1979
+ oracle: m.oracle,
1980
+ irm: m.irm,
1981
+ lltv: m.lltv
1982
+ },
1983
+ symbol: m.collateralAsset.symbol
1984
+ };
1985
+ }
1986
+ } catch (e) {
1987
+ console.warn("[agether] _findActiveSupplyMarket position check failed:", e instanceof Error ? e.message : e);
1988
+ continue;
1989
+ }
1990
+ }
1991
+ throw new AgetherError("No active supply position found", "NO_SUPPLY");
1992
+ }
1993
+ /**
1994
+ * Resolve a token symbol or address to { address, symbol, decimals }.
1995
+ *
1996
+ * Uses the dynamic `_tokenCache` populated by `getMarkets()` from the
1997
+ * Morpho GraphQL API — no hardcoded token list needed.
1998
+ *
1999
+ * @param symbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
2000
+ */
2001
+ async _resolveToken(symbolOrAddress) {
2002
+ const key = symbolOrAddress.startsWith("0x") ? symbolOrAddress.toLowerCase() : symbolOrAddress.toUpperCase();
2003
+ const cached = this._tokenCache.get(key);
2004
+ if (cached) return cached;
2005
+ await this.getMarkets();
2006
+ const fromApi = this._tokenCache.get(key);
2007
+ if (fromApi) return fromApi;
2008
+ throw new AgetherError(
2009
+ `Unknown token: ${symbolOrAddress}. No Morpho market found with this collateral.`,
2010
+ "UNKNOWN_COLLATERAL"
2011
+ );
2012
+ }
2013
+ /**
2014
+ * Get all discovered collateral tokens (for balance iteration, etc.).
2015
+ * Returns unique tokens from the Morpho API market discovery.
2016
+ */
2017
+ async _getDiscoveredTokens() {
2018
+ await this.getMarkets();
2019
+ const seen = /* @__PURE__ */ new Set();
2020
+ const tokens = [];
2021
+ for (const [key, info] of this._tokenCache) {
2022
+ if (key.startsWith("0x")) continue;
2023
+ if (seen.has(info.address.toLowerCase())) continue;
2024
+ seen.add(info.address.toLowerCase());
2025
+ if (info.symbol === "USDC" || info.symbol === "USDbC") continue;
2026
+ tokens.push(info);
2027
+ }
2028
+ return tokens;
2029
+ }
2030
+ /**
2031
+ * Compute net deposited amounts per market using Morpho GraphQL API.
2032
+ *
2033
+ * Fetches all MarketSupply and MarketWithdraw transactions for the account,
2034
+ * then computes: netDeposited[marketId] = Σ Supply.assets − Σ Withdraw.assets
2035
+ *
2036
+ * Returns a Map<marketId (lowercase), bigint>.
2037
+ *
2038
+ * Uses pagination (100 per page) for completeness, though agent accounts
2039
+ * typically have single-digit transaction counts.
2040
+ */
2041
+ async _computeNetDepositedAll(accountAddr, chainId) {
2042
+ const result = /* @__PURE__ */ new Map();
2043
+ let skip = 0;
2044
+ const pageSize = 100;
2045
+ let hasMore = true;
2046
+ while (hasMore) {
2047
+ const txQuery = `{
2048
+ transactions(
2049
+ first: ${pageSize}
2050
+ skip: ${skip}
2051
+ where: {
2052
+ userAddress_in: ["${accountAddr}"]
2053
+ type_in: [MarketSupply, MarketWithdraw]
2054
+ chainId_in: [${chainId}]
2055
+ }
2056
+ ) {
2057
+ pageInfo { count countTotal }
2058
+ items {
2059
+ type
2060
+ data {
2061
+ ... on MarketTransferTransactionData {
2062
+ assets
2063
+ market { uniqueKey }
2064
+ }
2065
+ }
2066
+ }
2067
+ }
2068
+ }`;
2069
+ const resp = await axios.post(MORPHO_API_URL, { query: txQuery }, { timeout: 15e3 });
2070
+ const txData = resp.data?.data?.transactions;
2071
+ if (!txData?.items) break;
2072
+ for (const tx of txData.items) {
2073
+ const marketKey = tx.data?.market?.uniqueKey?.toLowerCase();
2074
+ if (!marketKey || !tx.data?.assets) continue;
2075
+ const assets = BigInt(tx.data.assets);
2076
+ const current = result.get(marketKey) ?? 0n;
2077
+ if (tx.type === "MarketSupply") {
2078
+ result.set(marketKey, current + assets);
2079
+ } else if (tx.type === "MarketWithdraw") {
2080
+ const newVal = current - assets;
2081
+ result.set(marketKey, newVal > 0n ? newVal : 0n);
2082
+ }
2083
+ }
2084
+ const fetched = skip + txData.items.length;
2085
+ const total = txData.pageInfo?.countTotal ?? 0;
2086
+ hasMore = fetched < total;
2087
+ skip += pageSize;
2088
+ }
2089
+ return result;
2090
+ }
1681
2091
  };
1682
2092
 
1683
2093
  // src/clients/ScoringClient.ts