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