@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/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +533 -161
- package/dist/clients/AgentIdentityClient.d.ts +200 -0
- package/dist/clients/AgentIdentityClient.d.ts.map +1 -0
- package/dist/clients/AgentIdentityClient.js +351 -0
- package/dist/clients/AgetherClient.d.ts +242 -0
- package/dist/clients/AgetherClient.d.ts.map +1 -0
- package/dist/clients/AgetherClient.js +736 -0
- package/dist/clients/MorphoClient.d.ts +572 -0
- package/dist/clients/MorphoClient.d.ts.map +1 -0
- package/dist/clients/MorphoClient.js +1974 -0
- package/dist/clients/ScoringClient.d.ts +103 -0
- package/dist/clients/ScoringClient.d.ts.map +1 -0
- package/dist/clients/ScoringClient.js +112 -0
- package/dist/clients/X402Client.d.ts +198 -0
- package/dist/clients/X402Client.d.ts.map +1 -0
- package/dist/clients/X402Client.js +438 -0
- package/dist/index.d.mts +175 -27
- package/dist/index.d.ts +175 -27
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +533 -161
- package/dist/index.mjs +533 -161
- package/dist/types/index.d.ts +132 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +46 -0
- package/dist/utils/abis.d.ts +29 -0
- package/dist/utils/abis.d.ts.map +1 -0
- package/dist/utils/abis.js +138 -0
- package/dist/utils/config.d.ts +36 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +168 -0
- package/dist/utils/format.d.ts +44 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +75 -0
- package/package.json +1 -1
package/dist/index.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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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 || "
|
|
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
|
|
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:
|
|
1083
|
+
first: 500
|
|
1085
1084
|
orderBy: SupplyAssetsUsd
|
|
1086
1085
|
orderDirection: Desc
|
|
1087
|
-
where: { chainId_in: [${chainId}]
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1175
|
-
if (
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1387
|
+
collateralFilter = `, collateralAssetAddress_in: ["${collateralSymbolOrAddress.toLowerCase()}"]`;
|
|
1335
1388
|
} else {
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
}
|
|
1340
|
-
|
|
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:
|
|
1412
|
+
first: 100
|
|
1348
1413
|
orderBy: SupplyAssetsUsd
|
|
1349
1414
|
orderDirection: Desc
|
|
1350
|
-
where: { chainId_in: [${chainId}]
|
|
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
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
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
|
|
1421
|
-
collateralValueUsd = Number(
|
|
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
|
|
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
|
|
1722
|
+
* you deposit the loanToken into the market's supply pool and earn
|
|
1446
1723
|
* interest paid by borrowers.
|
|
1447
1724
|
*
|
|
1448
|
-
* @param
|
|
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(
|
|
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
|
|
1476
|
-
const acctBalance = await
|
|
1477
|
-
if (acctBalance <
|
|
1478
|
-
const shortfall =
|
|
1479
|
-
const eoaBalance = await
|
|
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
|
|
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
|
|
1767
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
1487
1768
|
await transferTx.wait();
|
|
1488
1769
|
this._refreshSigner();
|
|
1489
1770
|
}
|
|
1490
|
-
const targets = [
|
|
1771
|
+
const targets = [loanTokenAddr, morphoAddr];
|
|
1491
1772
|
const values = [0n, 0n];
|
|
1492
1773
|
const datas = [
|
|
1493
|
-
erc20Iface2.encodeFunctionData("approve", [morphoAddr,
|
|
1774
|
+
erc20Iface2.encodeFunctionData("approve", [morphoAddr, parsedAmount]),
|
|
1494
1775
|
morphoIface.encodeFunctionData("supply", [
|
|
1495
1776
|
this._toTuple(params),
|
|
1496
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
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,
|
|
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
|
|
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
|
|
1641
|
-
* @param
|
|
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,
|
|
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
|
|
1656
|
-
|
|
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 ${
|
|
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
|
-
|
|
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:
|
|
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
|
|
2034
|
+
* Borrow loan token against existing collateral.
|
|
1743
2035
|
*
|
|
1744
2036
|
* AgentAccount.execute: Morpho.borrow(params, amount, 0, account, account)
|
|
1745
2037
|
*
|
|
1746
|
-
* @param
|
|
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(
|
|
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 (
|
|
1788
|
-
const
|
|
1789
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
2206
|
+
borrowAmount,
|
|
1897
2207
|
agentAccount: acctAddr
|
|
1898
2208
|
};
|
|
1899
2209
|
}
|
|
1900
2210
|
/**
|
|
1901
|
-
* Repay borrowed
|
|
2211
|
+
* Repay borrowed loan token from AgentAccount.
|
|
1902
2212
|
*
|
|
1903
2213
|
* AgentAccount.executeBatch:
|
|
1904
|
-
* [
|
|
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(
|
|
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 (
|
|
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",
|
|
2251
|
+
approveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1", loanDecimals);
|
|
1936
2252
|
} else {
|
|
1937
|
-
repayAssets = ethers2.parseUnits("999999",
|
|
2253
|
+
repayAssets = ethers2.parseUnits("999999", loanDecimals);
|
|
1938
2254
|
repayShares = 0n;
|
|
1939
2255
|
approveAmount = repayAssets;
|
|
1940
2256
|
}
|
|
1941
2257
|
} else {
|
|
1942
|
-
repayAssets = ethers2.parseUnits(
|
|
2258
|
+
repayAssets = ethers2.parseUnits(amount, loanDecimals);
|
|
1943
2259
|
repayShares = 0n;
|
|
1944
2260
|
approveAmount = repayAssets;
|
|
1945
2261
|
}
|
|
1946
|
-
const
|
|
1947
|
-
const acctBalance = await
|
|
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
|
|
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
|
|
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
|
|
2275
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
1958
2276
|
await transferTx.wait();
|
|
1959
2277
|
this._refreshSigner();
|
|
1960
2278
|
}
|
|
1961
|
-
const targets = [
|
|
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
|
|
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
|
|
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",
|
|
2024
|
-
|
|
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
|
|
2039
|
-
const acctBalance = await
|
|
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
|
|
2363
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
2043
2364
|
if (eoaBalance >= shortfall) {
|
|
2044
|
-
|
|
2045
|
-
const
|
|
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 = [
|
|
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
|
|
2312
|
-
"
|
|
2683
|
+
`Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
|
|
2684
|
+
"UNKNOWN_TOKEN"
|
|
2313
2685
|
);
|
|
2314
2686
|
}
|
|
2315
2687
|
/**
|