@agether/sdk 2.12.2 → 2.13.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.js +291 -150
- package/dist/index.d.mts +92 -27
- package/dist/index.d.ts +92 -27
- package/dist/index.js +291 -150
- package/dist/index.mjs +291 -150
- 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
1158
|
first: 50
|
|
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,33 @@ 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
|
+
}
|
|
1254
1280
|
throw new AgetherError(
|
|
1255
|
-
`No Morpho market found for collateral ${collateralSymbolOrAddress}
|
|
1281
|
+
`No Morpho market found for collateral ${collateralSymbolOrAddress}` + (loanTokenSymbolOrAddress ? ` with loan token ${loanTokenSymbolOrAddress}` : ""),
|
|
1256
1282
|
"MARKET_NOT_FOUND"
|
|
1257
1283
|
);
|
|
1258
1284
|
}
|
|
@@ -1287,12 +1313,13 @@ var MorphoClient = class {
|
|
|
1287
1313
|
const acctAddr = await this.getAccountAddress();
|
|
1288
1314
|
const markets = await this.getMarkets();
|
|
1289
1315
|
const positions = [];
|
|
1290
|
-
let
|
|
1316
|
+
let totalDebtFloat = 0;
|
|
1291
1317
|
for (const m of markets) {
|
|
1292
1318
|
if (!m.collateralAsset || m.collateralAsset.address === import_ethers2.ethers.ZeroAddress) continue;
|
|
1293
1319
|
try {
|
|
1294
1320
|
const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
|
|
1295
1321
|
if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
|
|
1322
|
+
const loanDecimals = m.loanAsset.decimals;
|
|
1296
1323
|
let debt = 0n;
|
|
1297
1324
|
if (pos.borrowShares > 0n) {
|
|
1298
1325
|
try {
|
|
@@ -1300,7 +1327,7 @@ var MorphoClient = class {
|
|
|
1300
1327
|
const totalBorrowShares = BigInt(mkt.totalBorrowShares);
|
|
1301
1328
|
const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
|
|
1302
1329
|
debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1303
|
-
|
|
1330
|
+
totalDebtFloat += parseFloat(import_ethers2.ethers.formatUnits(debt, loanDecimals));
|
|
1304
1331
|
} catch (e) {
|
|
1305
1332
|
console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
|
|
1306
1333
|
}
|
|
@@ -1308,10 +1335,11 @@ var MorphoClient = class {
|
|
|
1308
1335
|
positions.push({
|
|
1309
1336
|
marketId: m.uniqueKey,
|
|
1310
1337
|
collateralToken: m.collateralAsset.symbol,
|
|
1338
|
+
loanToken: m.loanAsset.symbol,
|
|
1311
1339
|
collateral: import_ethers2.ethers.formatUnits(pos.collateral, m.collateralAsset.decimals),
|
|
1312
1340
|
borrowShares: pos.borrowShares.toString(),
|
|
1313
1341
|
supplyShares: pos.supplyShares.toString(),
|
|
1314
|
-
debt: import_ethers2.ethers.formatUnits(debt,
|
|
1342
|
+
debt: import_ethers2.ethers.formatUnits(debt, loanDecimals)
|
|
1315
1343
|
});
|
|
1316
1344
|
} catch (e) {
|
|
1317
1345
|
console.warn(`[agether] position read failed for market:`, e instanceof Error ? e.message : e);
|
|
@@ -1321,16 +1349,28 @@ var MorphoClient = class {
|
|
|
1321
1349
|
return {
|
|
1322
1350
|
agentId: this.agentId,
|
|
1323
1351
|
agentAccount: acctAddr,
|
|
1324
|
-
totalDebt:
|
|
1352
|
+
totalDebt: totalDebtFloat.toFixed(6),
|
|
1325
1353
|
positions
|
|
1326
1354
|
};
|
|
1327
1355
|
}
|
|
1328
1356
|
// ════════════════════════════════════════════════════════
|
|
1329
1357
|
// Balance & Borrowing Capacity
|
|
1330
1358
|
// ════════════════════════════════════════════════════════
|
|
1359
|
+
/**
|
|
1360
|
+
* Get the balance of any ERC-20 token in the AgentAccount.
|
|
1361
|
+
* @param symbolOrAddress - token symbol (e.g. 'USDC', 'WETH') or address
|
|
1362
|
+
* @returns balance in raw units
|
|
1363
|
+
*/
|
|
1364
|
+
async getTokenBalance(symbolOrAddress) {
|
|
1365
|
+
const acctAddr = await this.getAccountAddress();
|
|
1366
|
+
const tokenInfo = await this._resolveToken(symbolOrAddress);
|
|
1367
|
+
const token = new import_ethers2.Contract(tokenInfo.address, ERC20_ABI, this.provider);
|
|
1368
|
+
return token.balanceOf(acctAddr);
|
|
1369
|
+
}
|
|
1331
1370
|
/**
|
|
1332
1371
|
* Get the USDC balance of the AgentAccount.
|
|
1333
1372
|
* @returns USDC balance in raw units (6 decimals)
|
|
1373
|
+
* @deprecated Use `getTokenBalance('USDC')` instead.
|
|
1334
1374
|
*/
|
|
1335
1375
|
async getUsdcBalance() {
|
|
1336
1376
|
const acctAddr = await this.getAccountAddress();
|
|
@@ -1338,7 +1378,7 @@ var MorphoClient = class {
|
|
|
1338
1378
|
return usdc.balanceOf(acctAddr);
|
|
1339
1379
|
}
|
|
1340
1380
|
/**
|
|
1341
|
-
* Calculate the maximum additional
|
|
1381
|
+
* Calculate the maximum additional loan token that can be borrowed
|
|
1342
1382
|
* given the agent's current collateral and debt across all markets.
|
|
1343
1383
|
*
|
|
1344
1384
|
* For each market with collateral deposited:
|
|
@@ -1346,7 +1386,7 @@ var MorphoClient = class {
|
|
|
1346
1386
|
*
|
|
1347
1387
|
* Uses the Morpho oracle to price collateral → loan token.
|
|
1348
1388
|
*
|
|
1349
|
-
* @returns Maximum additional
|
|
1389
|
+
* @returns Maximum additional borrowable per market (raw units in each market's loan token)
|
|
1350
1390
|
*/
|
|
1351
1391
|
async getMaxBorrowable() {
|
|
1352
1392
|
const acctAddr = await this.getAccountAddress();
|
|
@@ -1379,6 +1419,8 @@ var MorphoClient = class {
|
|
|
1379
1419
|
totalAdditional += maxAdditional;
|
|
1380
1420
|
byMarket.push({
|
|
1381
1421
|
collateralToken: m.collateralAsset.symbol,
|
|
1422
|
+
loanToken: m.loanAsset.symbol,
|
|
1423
|
+
loanDecimals: m.loanAsset.decimals,
|
|
1382
1424
|
maxAdditional,
|
|
1383
1425
|
currentDebt,
|
|
1384
1426
|
collateralValue: collateralValueInLoan
|
|
@@ -1394,14 +1436,16 @@ var MorphoClient = class {
|
|
|
1394
1436
|
// Market Rates & Yield Estimation
|
|
1395
1437
|
// ════════════════════════════════════════════════════════
|
|
1396
1438
|
/**
|
|
1397
|
-
* Fetch current supply/borrow APY for
|
|
1439
|
+
* Fetch current supply/borrow APY for markets from Morpho GraphQL API.
|
|
1398
1440
|
*
|
|
1399
1441
|
* Note: On Morpho Blue, collateral does NOT earn yield directly. Supply APY
|
|
1400
1442
|
* is what lenders earn; borrow APY is what borrowers pay.
|
|
1443
|
+
*
|
|
1444
|
+
* @param collateralSymbolOrAddress - filter by collateral token (optional)
|
|
1445
|
+
* @param loanTokenSymbolOrAddress - filter by loan token (optional). Omit for all loan tokens.
|
|
1401
1446
|
*/
|
|
1402
|
-
async getMarketRates(collateralSymbolOrAddress) {
|
|
1447
|
+
async getMarketRates(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
|
|
1403
1448
|
const chainId = this.config.chainId;
|
|
1404
|
-
const usdcAddr = this.config.contracts.usdc.toLowerCase();
|
|
1405
1449
|
let collateralFilter = "";
|
|
1406
1450
|
if (collateralSymbolOrAddress) {
|
|
1407
1451
|
let colAddr;
|
|
@@ -1417,12 +1461,27 @@ var MorphoClient = class {
|
|
|
1417
1461
|
}
|
|
1418
1462
|
collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
|
|
1419
1463
|
}
|
|
1464
|
+
let loanFilter = "";
|
|
1465
|
+
if (loanTokenSymbolOrAddress) {
|
|
1466
|
+
let loanAddr;
|
|
1467
|
+
if (loanTokenSymbolOrAddress.startsWith("0x")) {
|
|
1468
|
+
loanAddr = loanTokenSymbolOrAddress.toLowerCase();
|
|
1469
|
+
} else {
|
|
1470
|
+
try {
|
|
1471
|
+
const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
|
|
1472
|
+
loanAddr = resolved.address.toLowerCase();
|
|
1473
|
+
} catch {
|
|
1474
|
+
loanAddr = loanTokenSymbolOrAddress.toLowerCase();
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
loanFilter = `, loanAssetAddress_in: ["${loanAddr}"]`;
|
|
1478
|
+
}
|
|
1420
1479
|
const query = `{
|
|
1421
1480
|
markets(
|
|
1422
1481
|
first: 50
|
|
1423
1482
|
orderBy: SupplyAssetsUsd
|
|
1424
1483
|
orderDirection: Desc
|
|
1425
|
-
where: { chainId_in: [${chainId}]
|
|
1484
|
+
where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter} }
|
|
1426
1485
|
) {
|
|
1427
1486
|
items {
|
|
1428
1487
|
uniqueKey
|
|
@@ -1442,17 +1501,21 @@ var MorphoClient = class {
|
|
|
1442
1501
|
try {
|
|
1443
1502
|
const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
|
|
1444
1503
|
const items = resp.data?.data?.markets?.items ?? [];
|
|
1445
|
-
return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers2.ethers.ZeroAddress).map((m) =>
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1504
|
+
return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers2.ethers.ZeroAddress).map((m) => {
|
|
1505
|
+
const loanDecimals = m.loanAsset?.decimals ?? 18;
|
|
1506
|
+
return {
|
|
1507
|
+
collateralToken: m.collateralAsset.symbol,
|
|
1508
|
+
loanToken: m.loanAsset.symbol,
|
|
1509
|
+
loanDecimals,
|
|
1510
|
+
supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
|
|
1511
|
+
borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
|
|
1512
|
+
utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
|
|
1513
|
+
totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 10 ** loanDecimals : 0,
|
|
1514
|
+
totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 10 ** loanDecimals : 0,
|
|
1515
|
+
lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
|
|
1516
|
+
marketId: m.uniqueKey
|
|
1517
|
+
};
|
|
1518
|
+
});
|
|
1456
1519
|
} catch (e) {
|
|
1457
1520
|
console.warn("[agether] getMarketRates failed:", e instanceof Error ? e.message : e);
|
|
1458
1521
|
return [];
|
|
@@ -1486,14 +1549,15 @@ var MorphoClient = class {
|
|
|
1486
1549
|
} else {
|
|
1487
1550
|
try {
|
|
1488
1551
|
const params = await this.findMarketForCollateral(collateralSymbol);
|
|
1552
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1489
1553
|
const oracleContract = new import_ethers2.Contract(params.oracle, [
|
|
1490
1554
|
"function price() view returns (uint256)"
|
|
1491
1555
|
], this.provider);
|
|
1492
1556
|
const oraclePrice = await oracleContract.price();
|
|
1493
1557
|
const ORACLE_PRICE_SCALE = 10n ** 36n;
|
|
1494
1558
|
const amountWei = import_ethers2.ethers.parseUnits(amount, colInfo.decimals);
|
|
1495
|
-
const
|
|
1496
|
-
collateralValueUsd = Number(
|
|
1559
|
+
const valueInLoan = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
|
|
1560
|
+
collateralValueUsd = Number(valueInLoan) / 10 ** loanDecimals;
|
|
1497
1561
|
} catch (e) {
|
|
1498
1562
|
console.warn("[agether] oracle price fetch for yield estimation failed:", e instanceof Error ? e.message : e);
|
|
1499
1563
|
throw new AgetherError("Cannot determine collateral value. Provide ethPriceUsd.", "PRICE_UNAVAILABLE");
|
|
@@ -1514,61 +1578,65 @@ var MorphoClient = class {
|
|
|
1514
1578
|
// Supply-Side (Lending) — earn yield by supplying USDC
|
|
1515
1579
|
// ════════════════════════════════════════════════════════
|
|
1516
1580
|
/**
|
|
1517
|
-
* Supply
|
|
1581
|
+
* Supply loan token to a Morpho Blue market as a lender (earn yield).
|
|
1518
1582
|
*
|
|
1519
1583
|
* Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
|
|
1520
|
-
* you deposit the loanToken
|
|
1584
|
+
* you deposit the loanToken into the market's supply pool and earn
|
|
1521
1585
|
* interest paid by borrowers.
|
|
1522
1586
|
*
|
|
1523
|
-
* @param
|
|
1587
|
+
* @param amount - Amount of loan token to supply (e.g. '500' for 500 USDC, '0.5' for 0.5 WETH)
|
|
1524
1588
|
* @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
|
|
1525
1589
|
* Optional — defaults to highest-APY market
|
|
1590
|
+
* @param loanTokenSymbol - Loan token to filter market (e.g. 'USDC', 'WETH'). Optional.
|
|
1526
1591
|
*/
|
|
1527
|
-
async supplyAsset(
|
|
1592
|
+
async supplyAsset(amount, collateralSymbol, loanTokenSymbol) {
|
|
1528
1593
|
const acctAddr = await this.getAccountAddress();
|
|
1529
|
-
const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
|
|
1530
1594
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1531
|
-
const usdcAddr = this.config.contracts.usdc;
|
|
1532
1595
|
let params;
|
|
1533
1596
|
let usedCollateral;
|
|
1534
1597
|
if (collateralSymbol) {
|
|
1535
|
-
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1598
|
+
params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
|
|
1536
1599
|
usedCollateral = collateralSymbol;
|
|
1537
1600
|
} else {
|
|
1538
|
-
const rates = await this.getMarketRates();
|
|
1601
|
+
const rates = await this.getMarketRates(void 0, loanTokenSymbol);
|
|
1539
1602
|
if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
|
|
1540
1603
|
const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
|
|
1541
|
-
params = await this.findMarketForCollateral(best.collateralToken);
|
|
1604
|
+
params = await this.findMarketForCollateral(best.collateralToken, loanTokenSymbol);
|
|
1542
1605
|
usedCollateral = best.collateralToken;
|
|
1543
1606
|
}
|
|
1607
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1608
|
+
const loanTokenAddr = params.loanToken;
|
|
1609
|
+
const parsedAmount = import_ethers2.ethers.parseUnits(amount, loanDecimals);
|
|
1544
1610
|
const marketId = import_ethers2.ethers.keccak256(
|
|
1545
1611
|
import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
1546
1612
|
["address", "address", "address", "address", "uint256"],
|
|
1547
1613
|
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
1548
1614
|
)
|
|
1549
1615
|
);
|
|
1550
|
-
const
|
|
1551
|
-
const acctBalance = await
|
|
1552
|
-
if (acctBalance <
|
|
1553
|
-
const shortfall =
|
|
1554
|
-
const eoaBalance = await
|
|
1616
|
+
const loanContract = new import_ethers2.Contract(loanTokenAddr, ERC20_ABI, this._signer);
|
|
1617
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
1618
|
+
if (acctBalance < parsedAmount) {
|
|
1619
|
+
const shortfall = parsedAmount - acctBalance;
|
|
1620
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
1555
1621
|
if (eoaBalance < shortfall) {
|
|
1622
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
1623
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
1556
1624
|
throw new AgetherError(
|
|
1557
|
-
`Insufficient
|
|
1625
|
+
`Insufficient ${loanSymbol}. Need ${amount}, AgentAccount has ${import_ethers2.ethers.formatUnits(acctBalance, loanDecimals)}, EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, loanDecimals)}.`,
|
|
1558
1626
|
"INSUFFICIENT_BALANCE"
|
|
1559
1627
|
);
|
|
1560
1628
|
}
|
|
1561
|
-
const transferTx = await
|
|
1629
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
1562
1630
|
await transferTx.wait();
|
|
1563
1631
|
this._refreshSigner();
|
|
1564
1632
|
}
|
|
1565
|
-
const targets = [
|
|
1633
|
+
const targets = [loanTokenAddr, morphoAddr];
|
|
1566
1634
|
const values = [0n, 0n];
|
|
1567
1635
|
const datas = [
|
|
1568
|
-
erc20Iface2.encodeFunctionData("approve", [morphoAddr,
|
|
1636
|
+
erc20Iface2.encodeFunctionData("approve", [morphoAddr, parsedAmount]),
|
|
1569
1637
|
morphoIface.encodeFunctionData("supply", [
|
|
1570
1638
|
this._toTuple(params),
|
|
1571
|
-
|
|
1639
|
+
parsedAmount,
|
|
1572
1640
|
0n,
|
|
1573
1641
|
acctAddr,
|
|
1574
1642
|
"0x"
|
|
@@ -1577,7 +1645,7 @@ var MorphoClient = class {
|
|
|
1577
1645
|
const receipt = await this.batch(targets, values, datas);
|
|
1578
1646
|
return {
|
|
1579
1647
|
tx: receipt.hash,
|
|
1580
|
-
amount
|
|
1648
|
+
amount,
|
|
1581
1649
|
marketId,
|
|
1582
1650
|
collateralToken: usedCollateral,
|
|
1583
1651
|
agentAccount: acctAddr
|
|
@@ -1590,17 +1658,26 @@ var MorphoClient = class {
|
|
|
1590
1658
|
* @param collateralSymbol - Market collateral to identify which market
|
|
1591
1659
|
* @param receiver - Destination address (defaults to EOA)
|
|
1592
1660
|
*/
|
|
1593
|
-
|
|
1661
|
+
/**
|
|
1662
|
+
* Withdraw supplied loan token (+ earned interest) from a Morpho Blue market.
|
|
1663
|
+
*
|
|
1664
|
+
* @param amount - Amount to withdraw (e.g. '100' or 'all' for full position)
|
|
1665
|
+
* @param collateralSymbol - Market collateral to identify which market
|
|
1666
|
+
* @param receiver - Destination address (defaults to EOA)
|
|
1667
|
+
* @param loanTokenSymbol - Loan token to filter market (optional)
|
|
1668
|
+
*/
|
|
1669
|
+
async withdrawSupply(amount, collateralSymbol, receiver, loanTokenSymbol) {
|
|
1594
1670
|
const acctAddr = await this.getAccountAddress();
|
|
1595
1671
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1596
1672
|
const dest = receiver || await this.getSignerAddress();
|
|
1597
1673
|
let params;
|
|
1598
1674
|
if (collateralSymbol) {
|
|
1599
|
-
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1675
|
+
params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
|
|
1600
1676
|
} else {
|
|
1601
1677
|
const { params: p } = await this._findActiveSupplyMarket();
|
|
1602
1678
|
params = p;
|
|
1603
1679
|
}
|
|
1680
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1604
1681
|
const marketId = import_ethers2.ethers.keccak256(
|
|
1605
1682
|
import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
1606
1683
|
["address", "address", "address", "address", "uint256"],
|
|
@@ -1609,13 +1686,13 @@ var MorphoClient = class {
|
|
|
1609
1686
|
);
|
|
1610
1687
|
let withdrawAssets;
|
|
1611
1688
|
let withdrawShares;
|
|
1612
|
-
if (
|
|
1689
|
+
if (amount === "all") {
|
|
1613
1690
|
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
1614
1691
|
withdrawShares = BigInt(pos.supplyShares);
|
|
1615
1692
|
withdrawAssets = 0n;
|
|
1616
1693
|
if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
|
|
1617
1694
|
} else {
|
|
1618
|
-
withdrawAssets = import_ethers2.ethers.parseUnits(
|
|
1695
|
+
withdrawAssets = import_ethers2.ethers.parseUnits(amount, loanDecimals);
|
|
1619
1696
|
withdrawShares = 0n;
|
|
1620
1697
|
}
|
|
1621
1698
|
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
@@ -1633,13 +1710,13 @@ var MorphoClient = class {
|
|
|
1633
1710
|
const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
|
|
1634
1711
|
const totalSupplyShares = BigInt(mkt.totalSupplyShares);
|
|
1635
1712
|
const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
|
|
1636
|
-
remainingSupply = import_ethers2.ethers.formatUnits(currentAssets,
|
|
1713
|
+
remainingSupply = import_ethers2.ethers.formatUnits(currentAssets, loanDecimals);
|
|
1637
1714
|
} catch (e) {
|
|
1638
1715
|
console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
|
|
1639
1716
|
}
|
|
1640
1717
|
return {
|
|
1641
1718
|
tx: receipt.hash,
|
|
1642
|
-
amount
|
|
1719
|
+
amount,
|
|
1643
1720
|
remainingSupply,
|
|
1644
1721
|
destination: dest
|
|
1645
1722
|
};
|
|
@@ -1712,14 +1789,13 @@ var MorphoClient = class {
|
|
|
1712
1789
|
* Computes available yield, verifies the requested amount doesn't exceed it,
|
|
1713
1790
|
* then withdraws from the supply position and sends directly to the recipient.
|
|
1714
1791
|
*
|
|
1715
|
-
* @param recipient - Address to receive the
|
|
1716
|
-
* @param
|
|
1792
|
+
* @param recipient - Address to receive the loan token
|
|
1793
|
+
* @param amount - Amount to pay from yield (e.g. '5.50')
|
|
1717
1794
|
* @param collateralSymbol - Market collateral to identify which supply position
|
|
1718
1795
|
*/
|
|
1719
|
-
async payFromYield(recipient,
|
|
1796
|
+
async payFromYield(recipient, amount, collateralSymbol) {
|
|
1720
1797
|
const acctAddr = await this.getAccountAddress();
|
|
1721
1798
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1722
|
-
const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
|
|
1723
1799
|
const positions = await this.getSupplyPositions(collateralSymbol);
|
|
1724
1800
|
if (positions.length === 0) {
|
|
1725
1801
|
throw new AgetherError("No supply position found", "NO_SUPPLY");
|
|
@@ -1727,17 +1803,20 @@ var MorphoClient = class {
|
|
|
1727
1803
|
const pos = positions.reduce(
|
|
1728
1804
|
(a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
|
|
1729
1805
|
);
|
|
1730
|
-
const
|
|
1731
|
-
|
|
1806
|
+
const params = await this.findMarketForCollateral(pos.collateralToken, pos.loanToken);
|
|
1807
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1808
|
+
const parsedAmount = import_ethers2.ethers.parseUnits(amount, loanDecimals);
|
|
1809
|
+
const availableYield = import_ethers2.ethers.parseUnits(pos.earnedYield, loanDecimals);
|
|
1810
|
+
if (parsedAmount > availableYield) {
|
|
1811
|
+
const loanSymbol = pos.loanToken;
|
|
1732
1812
|
throw new AgetherError(
|
|
1733
|
-
`Requested ${
|
|
1813
|
+
`Requested ${amount} ${loanSymbol} exceeds available yield of ${pos.earnedYield} ${loanSymbol}. Use withdrawSupply to withdraw principal.`,
|
|
1734
1814
|
"EXCEEDS_YIELD"
|
|
1735
1815
|
);
|
|
1736
1816
|
}
|
|
1737
|
-
const params = await this.findMarketForCollateral(pos.collateralToken);
|
|
1738
1817
|
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
1739
1818
|
this._toTuple(params),
|
|
1740
|
-
|
|
1819
|
+
parsedAmount,
|
|
1741
1820
|
0n,
|
|
1742
1821
|
acctAddr,
|
|
1743
1822
|
recipient
|
|
@@ -1756,7 +1835,7 @@ var MorphoClient = class {
|
|
|
1756
1835
|
}
|
|
1757
1836
|
return {
|
|
1758
1837
|
tx: receipt.hash,
|
|
1759
|
-
yieldWithdrawn:
|
|
1838
|
+
yieldWithdrawn: amount,
|
|
1760
1839
|
recipient,
|
|
1761
1840
|
remainingYield,
|
|
1762
1841
|
remainingSupply
|
|
@@ -1814,28 +1893,31 @@ var MorphoClient = class {
|
|
|
1814
1893
|
};
|
|
1815
1894
|
}
|
|
1816
1895
|
/**
|
|
1817
|
-
* Borrow
|
|
1896
|
+
* Borrow loan token against existing collateral.
|
|
1818
1897
|
*
|
|
1819
1898
|
* AgentAccount.execute: Morpho.borrow(params, amount, 0, account, account)
|
|
1820
1899
|
*
|
|
1821
|
-
* @param
|
|
1900
|
+
* @param amount - Loan token amount (e.g. '100' for 100 USDC, '0.5' for 0.5 WETH)
|
|
1822
1901
|
* @param tokenSymbol - collateral symbol to identify which market (default: first with collateral)
|
|
1902
|
+
* @param marketParams - explicit market params (optional)
|
|
1903
|
+
* @param loanTokenSymbol - loan token to filter market (optional, e.g. 'USDC', 'WETH')
|
|
1823
1904
|
*/
|
|
1824
|
-
async borrow(
|
|
1905
|
+
async borrow(amount, tokenSymbol, marketParams, loanTokenSymbol) {
|
|
1825
1906
|
const acctAddr = await this.getAccountAddress();
|
|
1826
|
-
const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
|
|
1827
1907
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1828
1908
|
let params;
|
|
1829
1909
|
let usedToken = tokenSymbol || "WETH";
|
|
1830
1910
|
if (marketParams) {
|
|
1831
1911
|
params = marketParams;
|
|
1832
1912
|
} else if (tokenSymbol) {
|
|
1833
|
-
params = await this.findMarketForCollateral(tokenSymbol);
|
|
1913
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1834
1914
|
} else {
|
|
1835
1915
|
const { params: p, symbol } = await this._findActiveMarket();
|
|
1836
1916
|
params = p;
|
|
1837
1917
|
usedToken = symbol;
|
|
1838
1918
|
}
|
|
1919
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1920
|
+
const parsedAmount = import_ethers2.ethers.parseUnits(amount, loanDecimals);
|
|
1839
1921
|
try {
|
|
1840
1922
|
const marketId = import_ethers2.ethers.keccak256(
|
|
1841
1923
|
import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
@@ -1859,11 +1941,14 @@ var MorphoClient = class {
|
|
|
1859
1941
|
const totalBorrowAssets = BigInt(mktState.totalBorrowAssets);
|
|
1860
1942
|
const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1861
1943
|
const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
|
|
1862
|
-
if (
|
|
1863
|
-
const
|
|
1864
|
-
const
|
|
1944
|
+
if (parsedAmount > maxAdditional) {
|
|
1945
|
+
const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
|
|
1946
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
1947
|
+
const colInfo = await this._resolveToken(usedToken);
|
|
1948
|
+
const maxFormatted = import_ethers2.ethers.formatUnits(maxAdditional, loanDecimals);
|
|
1949
|
+
const colFormatted = import_ethers2.ethers.formatUnits(pos.collateral, colInfo.decimals);
|
|
1865
1950
|
throw new AgetherError(
|
|
1866
|
-
`Borrow of
|
|
1951
|
+
`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
1952
|
"EXCEEDS_MAX_LTV"
|
|
1868
1953
|
);
|
|
1869
1954
|
}
|
|
@@ -1873,7 +1958,7 @@ var MorphoClient = class {
|
|
|
1873
1958
|
}
|
|
1874
1959
|
const data = morphoIface.encodeFunctionData("borrow", [
|
|
1875
1960
|
this._toTuple(params),
|
|
1876
|
-
|
|
1961
|
+
parsedAmount,
|
|
1877
1962
|
0n,
|
|
1878
1963
|
acctAddr,
|
|
1879
1964
|
acctAddr
|
|
@@ -1881,7 +1966,7 @@ var MorphoClient = class {
|
|
|
1881
1966
|
const receipt = await this.exec(morphoAddr, data);
|
|
1882
1967
|
return {
|
|
1883
1968
|
tx: receipt.hash,
|
|
1884
|
-
amount
|
|
1969
|
+
amount,
|
|
1885
1970
|
collateralToken: usedToken,
|
|
1886
1971
|
agentAccount: acctAddr
|
|
1887
1972
|
};
|
|
@@ -1889,17 +1974,27 @@ var MorphoClient = class {
|
|
|
1889
1974
|
/**
|
|
1890
1975
|
* Deposit collateral AND borrow USDC in one batched transaction.
|
|
1891
1976
|
*
|
|
1977
|
+
/**
|
|
1978
|
+
* Deposit collateral AND borrow loan token in one batched transaction.
|
|
1979
|
+
*
|
|
1892
1980
|
* AgentAccount.executeBatch:
|
|
1893
1981
|
* [collateral.approve, Morpho.supplyCollateral, Morpho.borrow]
|
|
1894
1982
|
*
|
|
1895
1983
|
* The collateral must be transferred to AgentAccount first.
|
|
1984
|
+
*
|
|
1985
|
+
* @param tokenSymbol - collateral token symbol (e.g. 'WETH')
|
|
1986
|
+
* @param collateralAmount - amount of collateral (e.g. '0.05')
|
|
1987
|
+
* @param borrowAmount - amount of loan token to borrow (e.g. '100')
|
|
1988
|
+
* @param marketParams - explicit market params (optional)
|
|
1989
|
+
* @param loanTokenSymbol - loan token to filter market (optional)
|
|
1896
1990
|
*/
|
|
1897
|
-
async depositAndBorrow(tokenSymbol, collateralAmount,
|
|
1991
|
+
async depositAndBorrow(tokenSymbol, collateralAmount, borrowAmount, marketParams, loanTokenSymbol) {
|
|
1898
1992
|
const acctAddr = await this.getAccountAddress();
|
|
1899
1993
|
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1900
|
-
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1994
|
+
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1995
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1901
1996
|
const colWei = import_ethers2.ethers.parseUnits(collateralAmount, colInfo.decimals);
|
|
1902
|
-
const borrowWei = import_ethers2.ethers.parseUnits(
|
|
1997
|
+
const borrowWei = import_ethers2.ethers.parseUnits(borrowAmount, loanDecimals);
|
|
1903
1998
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1904
1999
|
try {
|
|
1905
2000
|
const marketId = import_ethers2.ethers.keccak256(
|
|
@@ -1920,9 +2015,11 @@ var MorphoClient = class {
|
|
|
1920
2015
|
const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1921
2016
|
const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
|
|
1922
2017
|
if (borrowWei > maxAdditional) {
|
|
1923
|
-
const
|
|
2018
|
+
const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
|
|
2019
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
2020
|
+
const maxFormatted = import_ethers2.ethers.formatUnits(maxAdditional, loanDecimals);
|
|
1924
2021
|
throw new AgetherError(
|
|
1925
|
-
`Borrow of
|
|
2022
|
+
`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
2023
|
"EXCEEDS_MAX_LTV"
|
|
1927
2024
|
);
|
|
1928
2025
|
}
|
|
@@ -1968,36 +2065,42 @@ var MorphoClient = class {
|
|
|
1968
2065
|
tx: receipt.hash,
|
|
1969
2066
|
collateralToken: tokenSymbol,
|
|
1970
2067
|
collateralAmount,
|
|
1971
|
-
borrowAmount
|
|
2068
|
+
borrowAmount,
|
|
1972
2069
|
agentAccount: acctAddr
|
|
1973
2070
|
};
|
|
1974
2071
|
}
|
|
1975
2072
|
/**
|
|
1976
|
-
* Repay borrowed
|
|
2073
|
+
* Repay borrowed loan token from AgentAccount.
|
|
1977
2074
|
*
|
|
1978
2075
|
* AgentAccount.executeBatch:
|
|
1979
|
-
* [
|
|
2076
|
+
* [loanToken.approve(MorphoBlue), Morpho.repay(params)]
|
|
2077
|
+
*
|
|
2078
|
+
* @param amount - loan token amount to repay (e.g. '50' or 'all' for full repayment)
|
|
2079
|
+
* @param tokenSymbol - collateral symbol to identify which market (optional)
|
|
2080
|
+
* @param marketParams - explicit market params (optional)
|
|
2081
|
+
* @param loanTokenSymbol - loan token to filter market (optional)
|
|
1980
2082
|
*/
|
|
1981
|
-
async repay(
|
|
2083
|
+
async repay(amount, tokenSymbol, marketParams, loanTokenSymbol) {
|
|
1982
2084
|
const acctAddr = await this.getAccountAddress();
|
|
1983
2085
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1984
|
-
const usdcAddr = this.config.contracts.usdc;
|
|
1985
2086
|
let params;
|
|
1986
2087
|
if (marketParams) {
|
|
1987
2088
|
params = marketParams;
|
|
1988
2089
|
} else if (tokenSymbol) {
|
|
1989
|
-
params = await this.findMarketForCollateral(tokenSymbol);
|
|
2090
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1990
2091
|
} else {
|
|
1991
2092
|
const { params: p } = await this._findActiveMarket();
|
|
1992
2093
|
params = p;
|
|
1993
2094
|
}
|
|
2095
|
+
const loanTokenAddr = params.loanToken;
|
|
2096
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1994
2097
|
let repayAssets;
|
|
1995
2098
|
let repayShares;
|
|
1996
2099
|
let approveAmount;
|
|
1997
|
-
if (
|
|
2100
|
+
if (amount === "all") {
|
|
1998
2101
|
const markets = await this.getMarkets();
|
|
1999
2102
|
const mkt = markets.find(
|
|
2000
|
-
(m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase()
|
|
2103
|
+
(m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase() && m.loanAsset?.address.toLowerCase() === params.loanToken.toLowerCase()
|
|
2001
2104
|
);
|
|
2002
2105
|
if (mkt) {
|
|
2003
2106
|
const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
|
|
@@ -2007,33 +2110,35 @@ var MorphoClient = class {
|
|
|
2007
2110
|
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
2008
2111
|
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
2009
2112
|
const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
|
|
2010
|
-
approveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1",
|
|
2113
|
+
approveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1", loanDecimals);
|
|
2011
2114
|
} else {
|
|
2012
|
-
repayAssets = import_ethers2.ethers.parseUnits("999999",
|
|
2115
|
+
repayAssets = import_ethers2.ethers.parseUnits("999999", loanDecimals);
|
|
2013
2116
|
repayShares = 0n;
|
|
2014
2117
|
approveAmount = repayAssets;
|
|
2015
2118
|
}
|
|
2016
2119
|
} else {
|
|
2017
|
-
repayAssets = import_ethers2.ethers.parseUnits(
|
|
2120
|
+
repayAssets = import_ethers2.ethers.parseUnits(amount, loanDecimals);
|
|
2018
2121
|
repayShares = 0n;
|
|
2019
2122
|
approveAmount = repayAssets;
|
|
2020
2123
|
}
|
|
2021
|
-
const
|
|
2022
|
-
const acctBalance = await
|
|
2124
|
+
const loanContract = new import_ethers2.Contract(loanTokenAddr, ERC20_ABI, this._signer);
|
|
2125
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
2023
2126
|
if (acctBalance < approveAmount) {
|
|
2024
2127
|
const shortfall = approveAmount - acctBalance;
|
|
2025
|
-
const eoaBalance = await
|
|
2128
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
2026
2129
|
if (eoaBalance < shortfall) {
|
|
2130
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
2131
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
2027
2132
|
throw new AgetherError(
|
|
2028
|
-
`Insufficient
|
|
2133
|
+
`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
2134
|
"INSUFFICIENT_BALANCE"
|
|
2030
2135
|
);
|
|
2031
2136
|
}
|
|
2032
|
-
const transferTx = await
|
|
2137
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
2033
2138
|
await transferTx.wait();
|
|
2034
2139
|
this._refreshSigner();
|
|
2035
2140
|
}
|
|
2036
|
-
const targets = [
|
|
2141
|
+
const targets = [loanTokenAddr, morphoAddr];
|
|
2037
2142
|
const values = [0n, 0n];
|
|
2038
2143
|
const datas = [
|
|
2039
2144
|
erc20Iface2.encodeFunctionData("approve", [morphoAddr, approveAmount]),
|
|
@@ -2053,7 +2158,7 @@ var MorphoClient = class {
|
|
|
2053
2158
|
} catch (e) {
|
|
2054
2159
|
console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
|
|
2055
2160
|
}
|
|
2056
|
-
return { tx: receipt.hash, amount
|
|
2161
|
+
return { tx: receipt.hash, amount, remainingDebt };
|
|
2057
2162
|
}
|
|
2058
2163
|
/**
|
|
2059
2164
|
* Withdraw collateral from Morpho Blue.
|
|
@@ -2067,12 +2172,13 @@ var MorphoClient = class {
|
|
|
2067
2172
|
const colInfo = await this._resolveToken(tokenSymbol);
|
|
2068
2173
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
2069
2174
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
2070
|
-
const
|
|
2175
|
+
const loanTokenAddr = params.loanToken;
|
|
2176
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
2071
2177
|
const dest = receiver || await this.getSignerAddress();
|
|
2072
2178
|
let weiAmount;
|
|
2073
2179
|
const markets = await this.getMarkets();
|
|
2074
2180
|
const market = markets.find(
|
|
2075
|
-
(m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase()
|
|
2181
|
+
(m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase() && m.loanAsset?.address.toLowerCase() === loanTokenAddr.toLowerCase()
|
|
2076
2182
|
);
|
|
2077
2183
|
if (amount === "all") {
|
|
2078
2184
|
if (!market) throw new AgetherError("Market not found", "MARKET_NOT_FOUND");
|
|
@@ -2095,8 +2201,10 @@ var MorphoClient = class {
|
|
|
2095
2201
|
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
2096
2202
|
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
2097
2203
|
const estimated = totalBorrowShares > 0n ? dustBorrowShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
|
|
2098
|
-
dustApproveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1",
|
|
2099
|
-
|
|
2204
|
+
dustApproveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1", loanDecimals);
|
|
2205
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
2206
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
2207
|
+
console.log(`[agether] dust borrow shares detected: ${dustBorrowShares} shares \u2248 ${import_ethers2.ethers.formatUnits(dustApproveAmount, loanDecimals)} ${loanSymbol} \u2014 auto-repaying before withdraw`);
|
|
2100
2208
|
}
|
|
2101
2209
|
} catch (e) {
|
|
2102
2210
|
console.warn("[agether] failed to check borrow shares before withdraw:", e instanceof Error ? e.message : e);
|
|
@@ -2110,19 +2218,21 @@ var MorphoClient = class {
|
|
|
2110
2218
|
]);
|
|
2111
2219
|
let receipt;
|
|
2112
2220
|
if (hasDustDebt) {
|
|
2113
|
-
const
|
|
2114
|
-
const acctBalance = await
|
|
2221
|
+
const loanContract = new import_ethers2.Contract(loanTokenAddr, ERC20_ABI, this._signer);
|
|
2222
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
2115
2223
|
if (acctBalance < dustApproveAmount) {
|
|
2116
2224
|
const shortfall = dustApproveAmount - acctBalance;
|
|
2117
|
-
const eoaBalance = await
|
|
2225
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
2118
2226
|
if (eoaBalance >= shortfall) {
|
|
2119
|
-
|
|
2120
|
-
const
|
|
2227
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
2228
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
2229
|
+
console.log(`[agether] transferring ${import_ethers2.ethers.formatUnits(shortfall, loanDecimals)} ${loanSymbol} from EOA \u2192 AgentAccount for dust repay`);
|
|
2230
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
2121
2231
|
await transferTx.wait();
|
|
2122
2232
|
this._refreshSigner();
|
|
2123
2233
|
}
|
|
2124
2234
|
}
|
|
2125
|
-
const targets = [
|
|
2235
|
+
const targets = [loanTokenAddr, morphoAddr, morphoAddr];
|
|
2126
2236
|
const values = [0n, 0n, 0n];
|
|
2127
2237
|
const datas = [
|
|
2128
2238
|
erc20Iface2.encodeFunctionData("approve", [morphoAddr, dustApproveAmount]),
|
|
@@ -2367,6 +2477,37 @@ var MorphoClient = class {
|
|
|
2367
2477
|
}
|
|
2368
2478
|
throw new AgetherError("No active supply position found", "NO_SUPPLY");
|
|
2369
2479
|
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Resolve loan token decimals from market params.
|
|
2482
|
+
* Uses the `_tokenCache` populated by `getMarkets()`.
|
|
2483
|
+
*/
|
|
2484
|
+
async _getLoanTokenDecimals(params) {
|
|
2485
|
+
const tokenInfo = this._tokenCache.get(params.loanToken.toLowerCase());
|
|
2486
|
+
if (tokenInfo) return tokenInfo.decimals;
|
|
2487
|
+
await this.getMarkets();
|
|
2488
|
+
const fromApi = this._tokenCache.get(params.loanToken.toLowerCase());
|
|
2489
|
+
return fromApi?.decimals ?? 18;
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* Apply client-side filter to discovered markets.
|
|
2493
|
+
*/
|
|
2494
|
+
_applyMarketFilter(markets, filter) {
|
|
2495
|
+
return markets.filter((m) => {
|
|
2496
|
+
if (filter.loanToken) {
|
|
2497
|
+
const loanAddr = filter.loanToken.toLowerCase();
|
|
2498
|
+
if (m.loanAsset.address.toLowerCase() !== loanAddr && m.loanAsset.symbol.toUpperCase() !== filter.loanToken.toUpperCase()) {
|
|
2499
|
+
return false;
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
if (filter.collateralToken) {
|
|
2503
|
+
const colAddr = filter.collateralToken.toLowerCase();
|
|
2504
|
+
if (m.collateralAsset.address.toLowerCase() !== colAddr && m.collateralAsset.symbol.toUpperCase() !== filter.collateralToken.toUpperCase()) {
|
|
2505
|
+
return false;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
return true;
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2370
2511
|
/**
|
|
2371
2512
|
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
2372
2513
|
*
|
|
@@ -2383,8 +2524,8 @@ var MorphoClient = class {
|
|
|
2383
2524
|
const fromApi = this._tokenCache.get(key);
|
|
2384
2525
|
if (fromApi) return fromApi;
|
|
2385
2526
|
throw new AgetherError(
|
|
2386
|
-
`Unknown token: ${symbolOrAddress}. No Morpho market found with this
|
|
2387
|
-
"
|
|
2527
|
+
`Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
|
|
2528
|
+
"UNKNOWN_TOKEN"
|
|
2388
2529
|
);
|
|
2389
2530
|
}
|
|
2390
2531
|
/**
|