@agether/sdk 2.3.3 → 2.4.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
@@ -1119,7 +1119,259 @@ var MorphoClient = class {
1119
1119
  };
1120
1120
  }
1121
1121
  // ════════════════════════════════════════════════════════
1122
- // Lending Operations (all via AgentAccount.executeBatch)
1122
+ // Supply-Side (Lending) earn yield by supplying USDC
1123
+ // ════════════════════════════════════════════════════════
1124
+ /**
1125
+ * Supply USDC to a Morpho Blue market as a lender (earn yield).
1126
+ *
1127
+ * Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
1128
+ * you deposit the loanToken (USDC) into the market's supply pool and earn
1129
+ * interest paid by borrowers.
1130
+ *
1131
+ * @param usdcAmount - Amount of USDC to supply (e.g. '500')
1132
+ * @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
1133
+ * Optional — defaults to highest-APY market
1134
+ */
1135
+ async supplyAsset(usdcAmount, collateralSymbol) {
1136
+ const acctAddr = await this.getAccountAddress();
1137
+ const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
1138
+ const morphoAddr = this.config.contracts.morphoBlue;
1139
+ const usdcAddr = this.config.contracts.usdc;
1140
+ let params;
1141
+ let usedCollateral;
1142
+ if (collateralSymbol) {
1143
+ params = await this.findMarketForCollateral(collateralSymbol);
1144
+ usedCollateral = collateralSymbol;
1145
+ } else {
1146
+ const rates = await this.getMarketRates();
1147
+ if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
1148
+ const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
1149
+ params = await this.findMarketForCollateral(best.collateralToken);
1150
+ usedCollateral = best.collateralToken;
1151
+ }
1152
+ const marketId = import_ethers2.ethers.keccak256(
1153
+ import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
1154
+ ["address", "address", "address", "address", "uint256"],
1155
+ [params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
1156
+ )
1157
+ );
1158
+ const usdcContract = new import_ethers2.Contract(usdcAddr, ERC20_ABI, this._signer);
1159
+ const acctBalance = await usdcContract.balanceOf(acctAddr);
1160
+ if (acctBalance < amount) {
1161
+ const shortfall = amount - acctBalance;
1162
+ const eoaBalance = await usdcContract.balanceOf(await this.getSignerAddress());
1163
+ if (eoaBalance < shortfall) {
1164
+ throw new AgetherError(
1165
+ `Insufficient USDC. Need ${usdcAmount}, AgentAccount has ${import_ethers2.ethers.formatUnits(acctBalance, 6)}, EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, 6)}.`,
1166
+ "INSUFFICIENT_BALANCE"
1167
+ );
1168
+ }
1169
+ const transferTx = await usdcContract.transfer(acctAddr, shortfall);
1170
+ await transferTx.wait();
1171
+ this._refreshSigner();
1172
+ }
1173
+ const targets = [usdcAddr, morphoAddr];
1174
+ const values = [0n, 0n];
1175
+ const datas = [
1176
+ erc20Iface.encodeFunctionData("approve", [morphoAddr, amount]),
1177
+ morphoIface.encodeFunctionData("supply", [
1178
+ this._toTuple(params),
1179
+ amount,
1180
+ 0n,
1181
+ acctAddr,
1182
+ "0x"
1183
+ ])
1184
+ ];
1185
+ const receipt = await this.batch(targets, values, datas);
1186
+ return {
1187
+ tx: receipt.hash,
1188
+ amount: usdcAmount,
1189
+ marketId,
1190
+ collateralToken: usedCollateral,
1191
+ agentAccount: acctAddr
1192
+ };
1193
+ }
1194
+ /**
1195
+ * Withdraw supplied USDC (+ earned interest) from a Morpho Blue market.
1196
+ *
1197
+ * @param usdcAmount - Amount to withdraw (e.g. '100' or 'all' for full position)
1198
+ * @param collateralSymbol - Market collateral to identify which market
1199
+ * @param receiver - Destination address (defaults to EOA)
1200
+ */
1201
+ async withdrawSupply(usdcAmount, collateralSymbol, receiver) {
1202
+ const acctAddr = await this.getAccountAddress();
1203
+ const morphoAddr = this.config.contracts.morphoBlue;
1204
+ const dest = receiver || await this.getSignerAddress();
1205
+ let params;
1206
+ if (collateralSymbol) {
1207
+ params = await this.findMarketForCollateral(collateralSymbol);
1208
+ } else {
1209
+ const { params: p } = await this._findActiveSupplyMarket();
1210
+ params = p;
1211
+ }
1212
+ const marketId = import_ethers2.ethers.keccak256(
1213
+ import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
1214
+ ["address", "address", "address", "address", "uint256"],
1215
+ [params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
1216
+ )
1217
+ );
1218
+ let withdrawAssets;
1219
+ let withdrawShares;
1220
+ if (usdcAmount === "all") {
1221
+ const pos = await this.morphoBlue.position(marketId, acctAddr);
1222
+ withdrawShares = BigInt(pos.supplyShares);
1223
+ withdrawAssets = 0n;
1224
+ if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
1225
+ } else {
1226
+ withdrawAssets = import_ethers2.ethers.parseUnits(usdcAmount, 6);
1227
+ withdrawShares = 0n;
1228
+ }
1229
+ const data = morphoIface.encodeFunctionData("withdraw", [
1230
+ this._toTuple(params),
1231
+ withdrawAssets,
1232
+ withdrawShares,
1233
+ acctAddr,
1234
+ dest
1235
+ ]);
1236
+ const receipt = await this.exec(morphoAddr, data);
1237
+ let remainingSupply = "0";
1238
+ try {
1239
+ const pos = await this.morphoBlue.position(marketId, acctAddr);
1240
+ const mkt = await this.morphoBlue.market(marketId);
1241
+ const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
1242
+ const totalSupplyShares = BigInt(mkt.totalSupplyShares);
1243
+ const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
1244
+ remainingSupply = import_ethers2.ethers.formatUnits(currentAssets, 6);
1245
+ } catch (e) {
1246
+ console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
1247
+ }
1248
+ return {
1249
+ tx: receipt.hash,
1250
+ amount: usdcAmount,
1251
+ remainingSupply,
1252
+ destination: dest
1253
+ };
1254
+ }
1255
+ /**
1256
+ * Get supply (lending) positions with yield tracking.
1257
+ *
1258
+ * Uses Morpho GraphQL API (no eth_getLogs / no DB / no indexer):
1259
+ * 1. `userByAddress` → all market positions with current supplyAssets, supplyApy
1260
+ * 2. `transactions` → all MarketSupply/MarketWithdraw history for net deposited
1261
+ * 3. earnedYield = currentSupplyAssets − netDeposited
1262
+ *
1263
+ * @param collateralSymbol - Market collateral token (optional, returns all if omitted)
1264
+ */
1265
+ async getSupplyPositions(collateralSymbol) {
1266
+ const acctAddr = (await this.getAccountAddress()).toLowerCase();
1267
+ const chainId = this.config.chainId;
1268
+ const positionsQuery = `{
1269
+ userByAddress(address: "${acctAddr}", chainId: ${chainId}) {
1270
+ marketPositions {
1271
+ market {
1272
+ uniqueKey
1273
+ loanAsset { symbol address decimals }
1274
+ collateralAsset { symbol address }
1275
+ state { supplyApy }
1276
+ }
1277
+ state {
1278
+ supplyShares
1279
+ supplyAssets
1280
+ supplyAssetsUsd
1281
+ }
1282
+ }
1283
+ }
1284
+ }`;
1285
+ const posResp = await import_axios.default.post(MORPHO_API_URL, { query: positionsQuery }, { timeout: 15e3 });
1286
+ const user = posResp.data?.data?.userByAddress;
1287
+ if (!user?.marketPositions) return [];
1288
+ const activePositions = user.marketPositions.filter(
1289
+ (p) => p.state && BigInt(p.state.supplyShares ?? "0") > 0n
1290
+ );
1291
+ if (activePositions.length === 0) return [];
1292
+ const filtered = collateralSymbol ? activePositions.filter((p) => {
1293
+ const sym = p.market.collateralAsset?.symbol;
1294
+ return sym && sym.toUpperCase() === collateralSymbol.toUpperCase();
1295
+ }) : activePositions;
1296
+ if (filtered.length === 0) return [];
1297
+ const netDepositedMap = await this._computeNetDepositedAll(acctAddr, chainId);
1298
+ const results = [];
1299
+ for (const p of filtered) {
1300
+ const currentAssets = BigInt(p.state.supplyAssets ?? "0");
1301
+ const marketKey = p.market.uniqueKey.toLowerCase();
1302
+ const netDeposited = netDepositedMap.get(marketKey) ?? 0n;
1303
+ const earnedYield = currentAssets > netDeposited ? currentAssets - netDeposited : 0n;
1304
+ results.push({
1305
+ marketId: p.market.uniqueKey,
1306
+ loanToken: p.market.loanAsset.symbol,
1307
+ collateralToken: p.market.collateralAsset?.symbol ?? "none",
1308
+ supplyShares: p.state.supplyShares,
1309
+ suppliedAssets: import_ethers2.ethers.formatUnits(currentAssets, p.market.loanAsset.decimals),
1310
+ netDeposited: import_ethers2.ethers.formatUnits(netDeposited, p.market.loanAsset.decimals),
1311
+ earnedYield: import_ethers2.ethers.formatUnits(earnedYield, p.market.loanAsset.decimals),
1312
+ supplyApy: p.market.state?.supplyApy ?? 0
1313
+ });
1314
+ }
1315
+ return results;
1316
+ }
1317
+ /**
1318
+ * Pay a recipient using ONLY earned yield from a supply position.
1319
+ *
1320
+ * Computes available yield, verifies the requested amount doesn't exceed it,
1321
+ * then withdraws from the supply position and sends directly to the recipient.
1322
+ *
1323
+ * @param recipient - Address to receive the USDC
1324
+ * @param usdcAmount - Amount to pay from yield (e.g. '5.50')
1325
+ * @param collateralSymbol - Market collateral to identify which supply position
1326
+ */
1327
+ async payFromYield(recipient, usdcAmount, collateralSymbol) {
1328
+ const acctAddr = await this.getAccountAddress();
1329
+ const morphoAddr = this.config.contracts.morphoBlue;
1330
+ const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
1331
+ const positions = await this.getSupplyPositions(collateralSymbol);
1332
+ if (positions.length === 0) {
1333
+ throw new AgetherError("No supply position found", "NO_SUPPLY");
1334
+ }
1335
+ const pos = positions.reduce(
1336
+ (a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
1337
+ );
1338
+ const availableYield = import_ethers2.ethers.parseUnits(pos.earnedYield, 6);
1339
+ if (amount > availableYield) {
1340
+ throw new AgetherError(
1341
+ `Requested ${usdcAmount} USDC exceeds available yield of ${pos.earnedYield} USDC. Use withdrawSupply to withdraw principal.`,
1342
+ "EXCEEDS_YIELD"
1343
+ );
1344
+ }
1345
+ const params = await this.findMarketForCollateral(pos.collateralToken);
1346
+ const data = morphoIface.encodeFunctionData("withdraw", [
1347
+ this._toTuple(params),
1348
+ amount,
1349
+ 0n,
1350
+ acctAddr,
1351
+ recipient
1352
+ ]);
1353
+ const receipt = await this.exec(morphoAddr, data);
1354
+ let remainingYield = "0";
1355
+ let remainingSupply = "0";
1356
+ try {
1357
+ const updatedPositions = await this.getSupplyPositions(pos.collateralToken);
1358
+ if (updatedPositions.length > 0) {
1359
+ remainingYield = updatedPositions[0].earnedYield;
1360
+ remainingSupply = updatedPositions[0].suppliedAssets;
1361
+ }
1362
+ } catch (e) {
1363
+ console.warn("[agether] failed to read remaining yield:", e instanceof Error ? e.message : e);
1364
+ }
1365
+ return {
1366
+ tx: receipt.hash,
1367
+ yieldWithdrawn: usdcAmount,
1368
+ recipient,
1369
+ remainingYield,
1370
+ remainingSupply
1371
+ };
1372
+ }
1373
+ // ════════════════════════════════════════════════════════
1374
+ // Collateral & Borrowing Operations (all via AgentAccount)
1123
1375
  // ════════════════════════════════════════════════════════
1124
1376
  /**
1125
1377
  * Deposit collateral into Morpho Blue.
@@ -1750,6 +2002,94 @@ var MorphoClient = class {
1750
2002
  const params = await this.findMarketForCollateral("WETH");
1751
2003
  return { params, symbol: "WETH" };
1752
2004
  }
2005
+ /** Find the first market where the agent has a supply (lending) position. */
2006
+ async _findActiveSupplyMarket() {
2007
+ const acctAddr = await this.getAccountAddress();
2008
+ const markets = await this.getMarkets();
2009
+ for (const m of markets) {
2010
+ if (!m.collateralAsset || m.collateralAsset.address === import_ethers2.ethers.ZeroAddress) continue;
2011
+ try {
2012
+ const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
2013
+ if (BigInt(pos.supplyShares) > 0n) {
2014
+ return {
2015
+ params: {
2016
+ loanToken: m.loanAsset.address,
2017
+ collateralToken: m.collateralAsset.address,
2018
+ oracle: m.oracle,
2019
+ irm: m.irm,
2020
+ lltv: m.lltv
2021
+ },
2022
+ symbol: m.collateralAsset.symbol
2023
+ };
2024
+ }
2025
+ } catch (e) {
2026
+ console.warn("[agether] _findActiveSupplyMarket position check failed:", e instanceof Error ? e.message : e);
2027
+ continue;
2028
+ }
2029
+ }
2030
+ throw new AgetherError("No active supply position found", "NO_SUPPLY");
2031
+ }
2032
+ /**
2033
+ * Compute net deposited amounts per market using Morpho GraphQL API.
2034
+ *
2035
+ * Fetches all MarketSupply and MarketWithdraw transactions for the account,
2036
+ * then computes: netDeposited[marketId] = Σ Supply.assets − Σ Withdraw.assets
2037
+ *
2038
+ * Returns a Map<marketId (lowercase), bigint>.
2039
+ *
2040
+ * Uses pagination (100 per page) for completeness, though agent accounts
2041
+ * typically have single-digit transaction counts.
2042
+ */
2043
+ async _computeNetDepositedAll(accountAddr, chainId) {
2044
+ const result = /* @__PURE__ */ new Map();
2045
+ let skip = 0;
2046
+ const pageSize = 100;
2047
+ let hasMore = true;
2048
+ while (hasMore) {
2049
+ const txQuery = `{
2050
+ transactions(
2051
+ first: ${pageSize}
2052
+ skip: ${skip}
2053
+ where: {
2054
+ userAddress_in: ["${accountAddr}"]
2055
+ type_in: [MarketSupply, MarketWithdraw]
2056
+ chainId_in: [${chainId}]
2057
+ }
2058
+ ) {
2059
+ pageInfo { count countTotal }
2060
+ items {
2061
+ type
2062
+ data {
2063
+ ... on MarketTransferTransactionData {
2064
+ assets
2065
+ market { uniqueKey }
2066
+ }
2067
+ }
2068
+ }
2069
+ }
2070
+ }`;
2071
+ const resp = await import_axios.default.post(MORPHO_API_URL, { query: txQuery }, { timeout: 15e3 });
2072
+ const txData = resp.data?.data?.transactions;
2073
+ if (!txData?.items) break;
2074
+ for (const tx of txData.items) {
2075
+ const marketKey = tx.data?.market?.uniqueKey?.toLowerCase();
2076
+ if (!marketKey || !tx.data?.assets) continue;
2077
+ const assets = BigInt(tx.data.assets);
2078
+ const current = result.get(marketKey) ?? 0n;
2079
+ if (tx.type === "MarketSupply") {
2080
+ result.set(marketKey, current + assets);
2081
+ } else if (tx.type === "MarketWithdraw") {
2082
+ const newVal = current - assets;
2083
+ result.set(marketKey, newVal > 0n ? newVal : 0n);
2084
+ }
2085
+ }
2086
+ const fetched = skip + txData.items.length;
2087
+ const total = txData.pageInfo?.countTotal ?? 0;
2088
+ hasMore = fetched < total;
2089
+ skip += pageSize;
2090
+ }
2091
+ return result;
2092
+ }
1753
2093
  };
1754
2094
 
1755
2095
  // src/clients/ScoringClient.ts
@@ -1813,6 +2153,11 @@ var X402Client = class {
1813
2153
  },
1814
2154
  async signTypedData(typedData) {
1815
2155
  const sig = await inner.signTypedData(typedData);
2156
+ if (config.validatorModule) {
2157
+ const validatorHex = config.validatorModule.replace("0x", "").toLowerCase();
2158
+ const sigHex = sig.replace("0x", "");
2159
+ return `0x${validatorHex}${sigHex}`;
2160
+ }
1816
2161
  return `${sig}00`;
1817
2162
  }
1818
2163
  });