@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.mjs
CHANGED
|
@@ -419,25 +419,21 @@ var AgetherClient = class _AgetherClient {
|
|
|
419
419
|
const acctAddr = await this.agether4337Factory.getAccount(agentId);
|
|
420
420
|
this.accountAddress = acctAddr;
|
|
421
421
|
if (!acctExists) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
this._refreshSigner();
|
|
438
|
-
} catch (e) {
|
|
439
|
-
console.warn("[agether] setAgentURI failed (non-fatal):", e instanceof Error ? e.message : e);
|
|
440
|
-
}
|
|
422
|
+
const updatedMeta = JSON.stringify({
|
|
423
|
+
type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
|
|
424
|
+
name: options?.name || "Unnamed Agent",
|
|
425
|
+
description: options?.description || "AI agent registered via @agether/sdk",
|
|
426
|
+
active: true,
|
|
427
|
+
wallet: `eip155:${this.config.chainId}:${acctAddr}`,
|
|
428
|
+
registrations: [{
|
|
429
|
+
agentId: Number(agentId),
|
|
430
|
+
agentRegistry: `eip155:${this.config.chainId}:${this.config.contracts.identityRegistry}`
|
|
431
|
+
}]
|
|
432
|
+
});
|
|
433
|
+
const finalURI = `data:application/json;base64,${Buffer.from(updatedMeta).toString("base64")}`;
|
|
434
|
+
const uriTx = await this.identityRegistry.setAgentURI(agentId, finalURI);
|
|
435
|
+
await uriTx.wait();
|
|
436
|
+
this._refreshSigner();
|
|
441
437
|
}
|
|
442
438
|
const kyaRequired = await this.isKyaRequired();
|
|
443
439
|
return {
|
|
@@ -455,7 +451,7 @@ var AgetherClient = class _AgetherClient {
|
|
|
455
451
|
async _mintNewIdentity(name, description) {
|
|
456
452
|
const registrationFile = JSON.stringify({
|
|
457
453
|
type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
|
|
458
|
-
name: name || "
|
|
454
|
+
name: name || "Agether Agent",
|
|
459
455
|
description: description || "AI agent registered via @agether/sdk",
|
|
460
456
|
active: true,
|
|
461
457
|
registrations: [{
|
|
@@ -966,8 +962,9 @@ var morphoIface = new ethers2.Interface(MORPHO_BLUE_ABI);
|
|
|
966
962
|
var erc20Iface2 = new ethers2.Interface(ERC20_ABI);
|
|
967
963
|
var MorphoClient = class {
|
|
968
964
|
constructor(config) {
|
|
965
|
+
/** Market params cache: keyed by market uniqueKey (bytes32 hash) */
|
|
969
966
|
this._marketCache = /* @__PURE__ */ new Map();
|
|
970
|
-
/** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
|
|
967
|
+
/** Dynamic token registry: symbol (uppercase) or address (lowercase) → { address, symbol, decimals } */
|
|
971
968
|
this._tokenCache = /* @__PURE__ */ new Map();
|
|
972
969
|
this._discoveredAt = 0;
|
|
973
970
|
if (!config.agentId) {
|
|
@@ -1070,21 +1067,23 @@ var MorphoClient = class {
|
|
|
1070
1067
|
// Market Discovery (Morpho GraphQL API)
|
|
1071
1068
|
// ════════════════════════════════════════════════════════
|
|
1072
1069
|
/**
|
|
1073
|
-
* Fetch
|
|
1074
|
-
* Caches results for 5 minutes.
|
|
1070
|
+
* Fetch available markets on the current chain from Morpho API.
|
|
1071
|
+
* Caches results for 5 minutes. Supports all loan tokens (not just USDC).
|
|
1072
|
+
*
|
|
1073
|
+
* @param forceRefresh - bypass cache TTL
|
|
1074
|
+
* @param filter - optional filter by loan token and/or collateral token
|
|
1075
1075
|
*/
|
|
1076
|
-
async getMarkets(forceRefresh = false) {
|
|
1076
|
+
async getMarkets(forceRefresh = false, filter) {
|
|
1077
1077
|
if (!forceRefresh && this._discoveredMarkets && Date.now() - this._discoveredAt < 3e5) {
|
|
1078
|
-
return this._discoveredMarkets;
|
|
1078
|
+
return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
|
|
1079
1079
|
}
|
|
1080
1080
|
const chainId = this.config.chainId;
|
|
1081
|
-
const usdcAddr = this.config.contracts.usdc.toLowerCase();
|
|
1082
1081
|
const query = `{
|
|
1083
1082
|
markets(
|
|
1084
1083
|
first: 50
|
|
1085
1084
|
orderBy: SupplyAssetsUsd
|
|
1086
1085
|
orderDirection: Desc
|
|
1087
|
-
where: { chainId_in: [${chainId}]
|
|
1086
|
+
where: { chainId_in: [${chainId}] }
|
|
1088
1087
|
) {
|
|
1089
1088
|
items {
|
|
1090
1089
|
uniqueKey
|
|
@@ -1117,14 +1116,14 @@ var MorphoClient = class {
|
|
|
1117
1116
|
}));
|
|
1118
1117
|
this._discoveredAt = Date.now();
|
|
1119
1118
|
for (const mi of this._discoveredMarkets) {
|
|
1119
|
+
this._marketCache.set(mi.uniqueKey.toLowerCase(), {
|
|
1120
|
+
loanToken: mi.loanAsset.address,
|
|
1121
|
+
collateralToken: mi.collateralAsset.address,
|
|
1122
|
+
oracle: mi.oracle,
|
|
1123
|
+
irm: mi.irm,
|
|
1124
|
+
lltv: mi.lltv
|
|
1125
|
+
});
|
|
1120
1126
|
if (mi.collateralAsset.address !== ethers2.ZeroAddress) {
|
|
1121
|
-
this._marketCache.set(mi.collateralAsset.address.toLowerCase(), {
|
|
1122
|
-
loanToken: mi.loanAsset.address,
|
|
1123
|
-
collateralToken: mi.collateralAsset.address,
|
|
1124
|
-
oracle: mi.oracle,
|
|
1125
|
-
irm: mi.irm,
|
|
1126
|
-
lltv: mi.lltv
|
|
1127
|
-
});
|
|
1128
1127
|
this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
|
|
1129
1128
|
address: mi.collateralAsset.address,
|
|
1130
1129
|
symbol: mi.collateralAsset.symbol,
|
|
@@ -1149,17 +1148,24 @@ var MorphoClient = class {
|
|
|
1149
1148
|
});
|
|
1150
1149
|
}
|
|
1151
1150
|
}
|
|
1152
|
-
return this._discoveredMarkets;
|
|
1151
|
+
return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
|
|
1153
1152
|
} catch (e) {
|
|
1154
1153
|
console.warn("[agether] getMarkets failed, using cache:", e instanceof Error ? e.message : e);
|
|
1155
|
-
|
|
1154
|
+
const cached = this._discoveredMarkets ?? [];
|
|
1155
|
+
return filter ? this._applyMarketFilter(cached, filter) : cached;
|
|
1156
1156
|
}
|
|
1157
1157
|
}
|
|
1158
1158
|
/**
|
|
1159
|
-
* Get MarketParams for a collateral token.
|
|
1160
|
-
* Tries cache → API
|
|
1159
|
+
* Get MarketParams for a collateral token (and optionally a specific loan token).
|
|
1160
|
+
* Tries cache → API discovery.
|
|
1161
|
+
*
|
|
1162
|
+
* When `loanTokenSymbolOrAddress` is omitted, returns the most liquid market
|
|
1163
|
+
* for that collateral (sorted by supply, typically the USDC market).
|
|
1164
|
+
*
|
|
1165
|
+
* @param collateralSymbolOrAddress - e.g. 'WETH', 'wstETH', or '0x4200...'
|
|
1166
|
+
* @param loanTokenSymbolOrAddress - e.g. 'USDC', 'WETH', or '0x833589...' (optional)
|
|
1161
1167
|
*/
|
|
1162
|
-
async findMarketForCollateral(collateralSymbolOrAddress) {
|
|
1168
|
+
async findMarketForCollateral(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
|
|
1163
1169
|
let colAddr;
|
|
1164
1170
|
if (collateralSymbolOrAddress.startsWith("0x")) {
|
|
1165
1171
|
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
@@ -1171,13 +1177,33 @@ var MorphoClient = class {
|
|
|
1171
1177
|
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
1172
1178
|
}
|
|
1173
1179
|
}
|
|
1174
|
-
|
|
1175
|
-
if (
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1180
|
+
let loanAddr;
|
|
1181
|
+
if (loanTokenSymbolOrAddress) {
|
|
1182
|
+
if (loanTokenSymbolOrAddress.startsWith("0x")) {
|
|
1183
|
+
loanAddr = loanTokenSymbolOrAddress.toLowerCase();
|
|
1184
|
+
} else {
|
|
1185
|
+
try {
|
|
1186
|
+
const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
|
|
1187
|
+
loanAddr = resolved.address.toLowerCase();
|
|
1188
|
+
} catch {
|
|
1189
|
+
loanAddr = loanTokenSymbolOrAddress.toLowerCase();
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (!this._discoveredMarkets) await this.getMarkets();
|
|
1194
|
+
for (const m of this._discoveredMarkets ?? []) {
|
|
1195
|
+
if (m.collateralAsset.address.toLowerCase() !== colAddr) continue;
|
|
1196
|
+
if (loanAddr && m.loanAsset.address.toLowerCase() !== loanAddr) continue;
|
|
1197
|
+
return {
|
|
1198
|
+
loanToken: m.loanAsset.address,
|
|
1199
|
+
collateralToken: m.collateralAsset.address,
|
|
1200
|
+
oracle: m.oracle,
|
|
1201
|
+
irm: m.irm,
|
|
1202
|
+
lltv: m.lltv
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1179
1205
|
throw new AgetherError(
|
|
1180
|
-
`No Morpho market found for collateral ${collateralSymbolOrAddress}
|
|
1206
|
+
`No Morpho market found for collateral ${collateralSymbolOrAddress}` + (loanTokenSymbolOrAddress ? ` with loan token ${loanTokenSymbolOrAddress}` : ""),
|
|
1181
1207
|
"MARKET_NOT_FOUND"
|
|
1182
1208
|
);
|
|
1183
1209
|
}
|
|
@@ -1212,12 +1238,13 @@ var MorphoClient = class {
|
|
|
1212
1238
|
const acctAddr = await this.getAccountAddress();
|
|
1213
1239
|
const markets = await this.getMarkets();
|
|
1214
1240
|
const positions = [];
|
|
1215
|
-
let
|
|
1241
|
+
let totalDebtFloat = 0;
|
|
1216
1242
|
for (const m of markets) {
|
|
1217
1243
|
if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
|
|
1218
1244
|
try {
|
|
1219
1245
|
const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
|
|
1220
1246
|
if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
|
|
1247
|
+
const loanDecimals = m.loanAsset.decimals;
|
|
1221
1248
|
let debt = 0n;
|
|
1222
1249
|
if (pos.borrowShares > 0n) {
|
|
1223
1250
|
try {
|
|
@@ -1225,7 +1252,7 @@ var MorphoClient = class {
|
|
|
1225
1252
|
const totalBorrowShares = BigInt(mkt.totalBorrowShares);
|
|
1226
1253
|
const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
|
|
1227
1254
|
debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1228
|
-
|
|
1255
|
+
totalDebtFloat += parseFloat(ethers2.formatUnits(debt, loanDecimals));
|
|
1229
1256
|
} catch (e) {
|
|
1230
1257
|
console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
|
|
1231
1258
|
}
|
|
@@ -1233,10 +1260,11 @@ var MorphoClient = class {
|
|
|
1233
1260
|
positions.push({
|
|
1234
1261
|
marketId: m.uniqueKey,
|
|
1235
1262
|
collateralToken: m.collateralAsset.symbol,
|
|
1263
|
+
loanToken: m.loanAsset.symbol,
|
|
1236
1264
|
collateral: ethers2.formatUnits(pos.collateral, m.collateralAsset.decimals),
|
|
1237
1265
|
borrowShares: pos.borrowShares.toString(),
|
|
1238
1266
|
supplyShares: pos.supplyShares.toString(),
|
|
1239
|
-
debt: ethers2.formatUnits(debt,
|
|
1267
|
+
debt: ethers2.formatUnits(debt, loanDecimals)
|
|
1240
1268
|
});
|
|
1241
1269
|
} catch (e) {
|
|
1242
1270
|
console.warn(`[agether] position read failed for market:`, e instanceof Error ? e.message : e);
|
|
@@ -1246,16 +1274,28 @@ var MorphoClient = class {
|
|
|
1246
1274
|
return {
|
|
1247
1275
|
agentId: this.agentId,
|
|
1248
1276
|
agentAccount: acctAddr,
|
|
1249
|
-
totalDebt:
|
|
1277
|
+
totalDebt: totalDebtFloat.toFixed(6),
|
|
1250
1278
|
positions
|
|
1251
1279
|
};
|
|
1252
1280
|
}
|
|
1253
1281
|
// ════════════════════════════════════════════════════════
|
|
1254
1282
|
// Balance & Borrowing Capacity
|
|
1255
1283
|
// ════════════════════════════════════════════════════════
|
|
1284
|
+
/**
|
|
1285
|
+
* Get the balance of any ERC-20 token in the AgentAccount.
|
|
1286
|
+
* @param symbolOrAddress - token symbol (e.g. 'USDC', 'WETH') or address
|
|
1287
|
+
* @returns balance in raw units
|
|
1288
|
+
*/
|
|
1289
|
+
async getTokenBalance(symbolOrAddress) {
|
|
1290
|
+
const acctAddr = await this.getAccountAddress();
|
|
1291
|
+
const tokenInfo = await this._resolveToken(symbolOrAddress);
|
|
1292
|
+
const token = new Contract2(tokenInfo.address, ERC20_ABI, this.provider);
|
|
1293
|
+
return token.balanceOf(acctAddr);
|
|
1294
|
+
}
|
|
1256
1295
|
/**
|
|
1257
1296
|
* Get the USDC balance of the AgentAccount.
|
|
1258
1297
|
* @returns USDC balance in raw units (6 decimals)
|
|
1298
|
+
* @deprecated Use `getTokenBalance('USDC')` instead.
|
|
1259
1299
|
*/
|
|
1260
1300
|
async getUsdcBalance() {
|
|
1261
1301
|
const acctAddr = await this.getAccountAddress();
|
|
@@ -1263,7 +1303,7 @@ var MorphoClient = class {
|
|
|
1263
1303
|
return usdc.balanceOf(acctAddr);
|
|
1264
1304
|
}
|
|
1265
1305
|
/**
|
|
1266
|
-
* Calculate the maximum additional
|
|
1306
|
+
* Calculate the maximum additional loan token that can be borrowed
|
|
1267
1307
|
* given the agent's current collateral and debt across all markets.
|
|
1268
1308
|
*
|
|
1269
1309
|
* For each market with collateral deposited:
|
|
@@ -1271,7 +1311,7 @@ var MorphoClient = class {
|
|
|
1271
1311
|
*
|
|
1272
1312
|
* Uses the Morpho oracle to price collateral → loan token.
|
|
1273
1313
|
*
|
|
1274
|
-
* @returns Maximum additional
|
|
1314
|
+
* @returns Maximum additional borrowable per market (raw units in each market's loan token)
|
|
1275
1315
|
*/
|
|
1276
1316
|
async getMaxBorrowable() {
|
|
1277
1317
|
const acctAddr = await this.getAccountAddress();
|
|
@@ -1304,6 +1344,8 @@ var MorphoClient = class {
|
|
|
1304
1344
|
totalAdditional += maxAdditional;
|
|
1305
1345
|
byMarket.push({
|
|
1306
1346
|
collateralToken: m.collateralAsset.symbol,
|
|
1347
|
+
loanToken: m.loanAsset.symbol,
|
|
1348
|
+
loanDecimals: m.loanAsset.decimals,
|
|
1307
1349
|
maxAdditional,
|
|
1308
1350
|
currentDebt,
|
|
1309
1351
|
collateralValue: collateralValueInLoan
|
|
@@ -1319,14 +1361,16 @@ var MorphoClient = class {
|
|
|
1319
1361
|
// Market Rates & Yield Estimation
|
|
1320
1362
|
// ════════════════════════════════════════════════════════
|
|
1321
1363
|
/**
|
|
1322
|
-
* Fetch current supply/borrow APY for
|
|
1364
|
+
* Fetch current supply/borrow APY for markets from Morpho GraphQL API.
|
|
1323
1365
|
*
|
|
1324
1366
|
* Note: On Morpho Blue, collateral does NOT earn yield directly. Supply APY
|
|
1325
1367
|
* is what lenders earn; borrow APY is what borrowers pay.
|
|
1368
|
+
*
|
|
1369
|
+
* @param collateralSymbolOrAddress - filter by collateral token (optional)
|
|
1370
|
+
* @param loanTokenSymbolOrAddress - filter by loan token (optional). Omit for all loan tokens.
|
|
1326
1371
|
*/
|
|
1327
|
-
async getMarketRates(collateralSymbolOrAddress) {
|
|
1372
|
+
async getMarketRates(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
|
|
1328
1373
|
const chainId = this.config.chainId;
|
|
1329
|
-
const usdcAddr = this.config.contracts.usdc.toLowerCase();
|
|
1330
1374
|
let collateralFilter = "";
|
|
1331
1375
|
if (collateralSymbolOrAddress) {
|
|
1332
1376
|
let colAddr;
|
|
@@ -1342,12 +1386,27 @@ var MorphoClient = class {
|
|
|
1342
1386
|
}
|
|
1343
1387
|
collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
|
|
1344
1388
|
}
|
|
1389
|
+
let loanFilter = "";
|
|
1390
|
+
if (loanTokenSymbolOrAddress) {
|
|
1391
|
+
let loanAddr;
|
|
1392
|
+
if (loanTokenSymbolOrAddress.startsWith("0x")) {
|
|
1393
|
+
loanAddr = loanTokenSymbolOrAddress.toLowerCase();
|
|
1394
|
+
} else {
|
|
1395
|
+
try {
|
|
1396
|
+
const resolved = await this._resolveToken(loanTokenSymbolOrAddress);
|
|
1397
|
+
loanAddr = resolved.address.toLowerCase();
|
|
1398
|
+
} catch {
|
|
1399
|
+
loanAddr = loanTokenSymbolOrAddress.toLowerCase();
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
loanFilter = `, loanAssetAddress_in: ["${loanAddr}"]`;
|
|
1403
|
+
}
|
|
1345
1404
|
const query = `{
|
|
1346
1405
|
markets(
|
|
1347
1406
|
first: 50
|
|
1348
1407
|
orderBy: SupplyAssetsUsd
|
|
1349
1408
|
orderDirection: Desc
|
|
1350
|
-
where: { chainId_in: [${chainId}]
|
|
1409
|
+
where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter} }
|
|
1351
1410
|
) {
|
|
1352
1411
|
items {
|
|
1353
1412
|
uniqueKey
|
|
@@ -1367,17 +1426,21 @@ var MorphoClient = class {
|
|
|
1367
1426
|
try {
|
|
1368
1427
|
const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
|
|
1369
1428
|
const items = resp.data?.data?.markets?.items ?? [];
|
|
1370
|
-
return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress).map((m) =>
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1429
|
+
return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress).map((m) => {
|
|
1430
|
+
const loanDecimals = m.loanAsset?.decimals ?? 18;
|
|
1431
|
+
return {
|
|
1432
|
+
collateralToken: m.collateralAsset.symbol,
|
|
1433
|
+
loanToken: m.loanAsset.symbol,
|
|
1434
|
+
loanDecimals,
|
|
1435
|
+
supplyApy: m.state?.supplyApy ? Number(m.state.supplyApy) : 0,
|
|
1436
|
+
borrowApy: m.state?.borrowApy ? Number(m.state.borrowApy) : 0,
|
|
1437
|
+
utilization: m.state?.utilization ? Number(m.state.utilization) : 0,
|
|
1438
|
+
totalSupplyUsd: m.state?.supplyAssets ? Number(m.state.supplyAssets) / 10 ** loanDecimals : 0,
|
|
1439
|
+
totalBorrowUsd: m.state?.borrowAssets ? Number(m.state.borrowAssets) / 10 ** loanDecimals : 0,
|
|
1440
|
+
lltv: `${(Number(m.lltv) / 1e16).toFixed(0)}%`,
|
|
1441
|
+
marketId: m.uniqueKey
|
|
1442
|
+
};
|
|
1443
|
+
});
|
|
1381
1444
|
} catch (e) {
|
|
1382
1445
|
console.warn("[agether] getMarketRates failed:", e instanceof Error ? e.message : e);
|
|
1383
1446
|
return [];
|
|
@@ -1411,14 +1474,15 @@ var MorphoClient = class {
|
|
|
1411
1474
|
} else {
|
|
1412
1475
|
try {
|
|
1413
1476
|
const params = await this.findMarketForCollateral(collateralSymbol);
|
|
1477
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1414
1478
|
const oracleContract = new Contract2(params.oracle, [
|
|
1415
1479
|
"function price() view returns (uint256)"
|
|
1416
1480
|
], this.provider);
|
|
1417
1481
|
const oraclePrice = await oracleContract.price();
|
|
1418
1482
|
const ORACLE_PRICE_SCALE = 10n ** 36n;
|
|
1419
1483
|
const amountWei = ethers2.parseUnits(amount, colInfo.decimals);
|
|
1420
|
-
const
|
|
1421
|
-
collateralValueUsd = Number(
|
|
1484
|
+
const valueInLoan = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
|
|
1485
|
+
collateralValueUsd = Number(valueInLoan) / 10 ** loanDecimals;
|
|
1422
1486
|
} catch (e) {
|
|
1423
1487
|
console.warn("[agether] oracle price fetch for yield estimation failed:", e instanceof Error ? e.message : e);
|
|
1424
1488
|
throw new AgetherError("Cannot determine collateral value. Provide ethPriceUsd.", "PRICE_UNAVAILABLE");
|
|
@@ -1439,61 +1503,65 @@ var MorphoClient = class {
|
|
|
1439
1503
|
// Supply-Side (Lending) — earn yield by supplying USDC
|
|
1440
1504
|
// ════════════════════════════════════════════════════════
|
|
1441
1505
|
/**
|
|
1442
|
-
* Supply
|
|
1506
|
+
* Supply loan token to a Morpho Blue market as a lender (earn yield).
|
|
1443
1507
|
*
|
|
1444
1508
|
* Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
|
|
1445
|
-
* you deposit the loanToken
|
|
1509
|
+
* you deposit the loanToken into the market's supply pool and earn
|
|
1446
1510
|
* interest paid by borrowers.
|
|
1447
1511
|
*
|
|
1448
|
-
* @param
|
|
1512
|
+
* @param amount - Amount of loan token to supply (e.g. '500' for 500 USDC, '0.5' for 0.5 WETH)
|
|
1449
1513
|
* @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
|
|
1450
1514
|
* Optional — defaults to highest-APY market
|
|
1515
|
+
* @param loanTokenSymbol - Loan token to filter market (e.g. 'USDC', 'WETH'). Optional.
|
|
1451
1516
|
*/
|
|
1452
|
-
async supplyAsset(
|
|
1517
|
+
async supplyAsset(amount, collateralSymbol, loanTokenSymbol) {
|
|
1453
1518
|
const acctAddr = await this.getAccountAddress();
|
|
1454
|
-
const amount = ethers2.parseUnits(usdcAmount, 6);
|
|
1455
1519
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1456
|
-
const usdcAddr = this.config.contracts.usdc;
|
|
1457
1520
|
let params;
|
|
1458
1521
|
let usedCollateral;
|
|
1459
1522
|
if (collateralSymbol) {
|
|
1460
|
-
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1523
|
+
params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
|
|
1461
1524
|
usedCollateral = collateralSymbol;
|
|
1462
1525
|
} else {
|
|
1463
|
-
const rates = await this.getMarketRates();
|
|
1526
|
+
const rates = await this.getMarketRates(void 0, loanTokenSymbol);
|
|
1464
1527
|
if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
|
|
1465
1528
|
const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
|
|
1466
|
-
params = await this.findMarketForCollateral(best.collateralToken);
|
|
1529
|
+
params = await this.findMarketForCollateral(best.collateralToken, loanTokenSymbol);
|
|
1467
1530
|
usedCollateral = best.collateralToken;
|
|
1468
1531
|
}
|
|
1532
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1533
|
+
const loanTokenAddr = params.loanToken;
|
|
1534
|
+
const parsedAmount = ethers2.parseUnits(amount, loanDecimals);
|
|
1469
1535
|
const marketId = ethers2.keccak256(
|
|
1470
1536
|
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
1471
1537
|
["address", "address", "address", "address", "uint256"],
|
|
1472
1538
|
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
1473
1539
|
)
|
|
1474
1540
|
);
|
|
1475
|
-
const
|
|
1476
|
-
const acctBalance = await
|
|
1477
|
-
if (acctBalance <
|
|
1478
|
-
const shortfall =
|
|
1479
|
-
const eoaBalance = await
|
|
1541
|
+
const loanContract = new Contract2(loanTokenAddr, ERC20_ABI, this._signer);
|
|
1542
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
1543
|
+
if (acctBalance < parsedAmount) {
|
|
1544
|
+
const shortfall = parsedAmount - acctBalance;
|
|
1545
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
1480
1546
|
if (eoaBalance < shortfall) {
|
|
1547
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
1548
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
1481
1549
|
throw new AgetherError(
|
|
1482
|
-
`Insufficient
|
|
1550
|
+
`Insufficient ${loanSymbol}. Need ${amount}, AgentAccount has ${ethers2.formatUnits(acctBalance, loanDecimals)}, EOA has ${ethers2.formatUnits(eoaBalance, loanDecimals)}.`,
|
|
1483
1551
|
"INSUFFICIENT_BALANCE"
|
|
1484
1552
|
);
|
|
1485
1553
|
}
|
|
1486
|
-
const transferTx = await
|
|
1554
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
1487
1555
|
await transferTx.wait();
|
|
1488
1556
|
this._refreshSigner();
|
|
1489
1557
|
}
|
|
1490
|
-
const targets = [
|
|
1558
|
+
const targets = [loanTokenAddr, morphoAddr];
|
|
1491
1559
|
const values = [0n, 0n];
|
|
1492
1560
|
const datas = [
|
|
1493
|
-
erc20Iface2.encodeFunctionData("approve", [morphoAddr,
|
|
1561
|
+
erc20Iface2.encodeFunctionData("approve", [morphoAddr, parsedAmount]),
|
|
1494
1562
|
morphoIface.encodeFunctionData("supply", [
|
|
1495
1563
|
this._toTuple(params),
|
|
1496
|
-
|
|
1564
|
+
parsedAmount,
|
|
1497
1565
|
0n,
|
|
1498
1566
|
acctAddr,
|
|
1499
1567
|
"0x"
|
|
@@ -1502,7 +1570,7 @@ var MorphoClient = class {
|
|
|
1502
1570
|
const receipt = await this.batch(targets, values, datas);
|
|
1503
1571
|
return {
|
|
1504
1572
|
tx: receipt.hash,
|
|
1505
|
-
amount
|
|
1573
|
+
amount,
|
|
1506
1574
|
marketId,
|
|
1507
1575
|
collateralToken: usedCollateral,
|
|
1508
1576
|
agentAccount: acctAddr
|
|
@@ -1515,17 +1583,26 @@ var MorphoClient = class {
|
|
|
1515
1583
|
* @param collateralSymbol - Market collateral to identify which market
|
|
1516
1584
|
* @param receiver - Destination address (defaults to EOA)
|
|
1517
1585
|
*/
|
|
1518
|
-
|
|
1586
|
+
/**
|
|
1587
|
+
* Withdraw supplied loan token (+ earned interest) from a Morpho Blue market.
|
|
1588
|
+
*
|
|
1589
|
+
* @param amount - Amount to withdraw (e.g. '100' or 'all' for full position)
|
|
1590
|
+
* @param collateralSymbol - Market collateral to identify which market
|
|
1591
|
+
* @param receiver - Destination address (defaults to EOA)
|
|
1592
|
+
* @param loanTokenSymbol - Loan token to filter market (optional)
|
|
1593
|
+
*/
|
|
1594
|
+
async withdrawSupply(amount, collateralSymbol, receiver, loanTokenSymbol) {
|
|
1519
1595
|
const acctAddr = await this.getAccountAddress();
|
|
1520
1596
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1521
1597
|
const dest = receiver || await this.getSignerAddress();
|
|
1522
1598
|
let params;
|
|
1523
1599
|
if (collateralSymbol) {
|
|
1524
|
-
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1600
|
+
params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
|
|
1525
1601
|
} else {
|
|
1526
1602
|
const { params: p } = await this._findActiveSupplyMarket();
|
|
1527
1603
|
params = p;
|
|
1528
1604
|
}
|
|
1605
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1529
1606
|
const marketId = ethers2.keccak256(
|
|
1530
1607
|
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
1531
1608
|
["address", "address", "address", "address", "uint256"],
|
|
@@ -1534,13 +1611,13 @@ var MorphoClient = class {
|
|
|
1534
1611
|
);
|
|
1535
1612
|
let withdrawAssets;
|
|
1536
1613
|
let withdrawShares;
|
|
1537
|
-
if (
|
|
1614
|
+
if (amount === "all") {
|
|
1538
1615
|
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
1539
1616
|
withdrawShares = BigInt(pos.supplyShares);
|
|
1540
1617
|
withdrawAssets = 0n;
|
|
1541
1618
|
if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
|
|
1542
1619
|
} else {
|
|
1543
|
-
withdrawAssets = ethers2.parseUnits(
|
|
1620
|
+
withdrawAssets = ethers2.parseUnits(amount, loanDecimals);
|
|
1544
1621
|
withdrawShares = 0n;
|
|
1545
1622
|
}
|
|
1546
1623
|
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
@@ -1558,13 +1635,13 @@ var MorphoClient = class {
|
|
|
1558
1635
|
const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
|
|
1559
1636
|
const totalSupplyShares = BigInt(mkt.totalSupplyShares);
|
|
1560
1637
|
const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
|
|
1561
|
-
remainingSupply = ethers2.formatUnits(currentAssets,
|
|
1638
|
+
remainingSupply = ethers2.formatUnits(currentAssets, loanDecimals);
|
|
1562
1639
|
} catch (e) {
|
|
1563
1640
|
console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
|
|
1564
1641
|
}
|
|
1565
1642
|
return {
|
|
1566
1643
|
tx: receipt.hash,
|
|
1567
|
-
amount
|
|
1644
|
+
amount,
|
|
1568
1645
|
remainingSupply,
|
|
1569
1646
|
destination: dest
|
|
1570
1647
|
};
|
|
@@ -1637,14 +1714,13 @@ var MorphoClient = class {
|
|
|
1637
1714
|
* Computes available yield, verifies the requested amount doesn't exceed it,
|
|
1638
1715
|
* then withdraws from the supply position and sends directly to the recipient.
|
|
1639
1716
|
*
|
|
1640
|
-
* @param recipient - Address to receive the
|
|
1641
|
-
* @param
|
|
1717
|
+
* @param recipient - Address to receive the loan token
|
|
1718
|
+
* @param amount - Amount to pay from yield (e.g. '5.50')
|
|
1642
1719
|
* @param collateralSymbol - Market collateral to identify which supply position
|
|
1643
1720
|
*/
|
|
1644
|
-
async payFromYield(recipient,
|
|
1721
|
+
async payFromYield(recipient, amount, collateralSymbol) {
|
|
1645
1722
|
const acctAddr = await this.getAccountAddress();
|
|
1646
1723
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1647
|
-
const amount = ethers2.parseUnits(usdcAmount, 6);
|
|
1648
1724
|
const positions = await this.getSupplyPositions(collateralSymbol);
|
|
1649
1725
|
if (positions.length === 0) {
|
|
1650
1726
|
throw new AgetherError("No supply position found", "NO_SUPPLY");
|
|
@@ -1652,17 +1728,20 @@ var MorphoClient = class {
|
|
|
1652
1728
|
const pos = positions.reduce(
|
|
1653
1729
|
(a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
|
|
1654
1730
|
);
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1731
|
+
const params = await this.findMarketForCollateral(pos.collateralToken, pos.loanToken);
|
|
1732
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1733
|
+
const parsedAmount = ethers2.parseUnits(amount, loanDecimals);
|
|
1734
|
+
const availableYield = ethers2.parseUnits(pos.earnedYield, loanDecimals);
|
|
1735
|
+
if (parsedAmount > availableYield) {
|
|
1736
|
+
const loanSymbol = pos.loanToken;
|
|
1657
1737
|
throw new AgetherError(
|
|
1658
|
-
`Requested ${
|
|
1738
|
+
`Requested ${amount} ${loanSymbol} exceeds available yield of ${pos.earnedYield} ${loanSymbol}. Use withdrawSupply to withdraw principal.`,
|
|
1659
1739
|
"EXCEEDS_YIELD"
|
|
1660
1740
|
);
|
|
1661
1741
|
}
|
|
1662
|
-
const params = await this.findMarketForCollateral(pos.collateralToken);
|
|
1663
1742
|
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
1664
1743
|
this._toTuple(params),
|
|
1665
|
-
|
|
1744
|
+
parsedAmount,
|
|
1666
1745
|
0n,
|
|
1667
1746
|
acctAddr,
|
|
1668
1747
|
recipient
|
|
@@ -1681,7 +1760,7 @@ var MorphoClient = class {
|
|
|
1681
1760
|
}
|
|
1682
1761
|
return {
|
|
1683
1762
|
tx: receipt.hash,
|
|
1684
|
-
yieldWithdrawn:
|
|
1763
|
+
yieldWithdrawn: amount,
|
|
1685
1764
|
recipient,
|
|
1686
1765
|
remainingYield,
|
|
1687
1766
|
remainingSupply
|
|
@@ -1739,28 +1818,31 @@ var MorphoClient = class {
|
|
|
1739
1818
|
};
|
|
1740
1819
|
}
|
|
1741
1820
|
/**
|
|
1742
|
-
* Borrow
|
|
1821
|
+
* Borrow loan token against existing collateral.
|
|
1743
1822
|
*
|
|
1744
1823
|
* AgentAccount.execute: Morpho.borrow(params, amount, 0, account, account)
|
|
1745
1824
|
*
|
|
1746
|
-
* @param
|
|
1825
|
+
* @param amount - Loan token amount (e.g. '100' for 100 USDC, '0.5' for 0.5 WETH)
|
|
1747
1826
|
* @param tokenSymbol - collateral symbol to identify which market (default: first with collateral)
|
|
1827
|
+
* @param marketParams - explicit market params (optional)
|
|
1828
|
+
* @param loanTokenSymbol - loan token to filter market (optional, e.g. 'USDC', 'WETH')
|
|
1748
1829
|
*/
|
|
1749
|
-
async borrow(
|
|
1830
|
+
async borrow(amount, tokenSymbol, marketParams, loanTokenSymbol) {
|
|
1750
1831
|
const acctAddr = await this.getAccountAddress();
|
|
1751
|
-
const amount = ethers2.parseUnits(usdcAmount, 6);
|
|
1752
1832
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1753
1833
|
let params;
|
|
1754
1834
|
let usedToken = tokenSymbol || "WETH";
|
|
1755
1835
|
if (marketParams) {
|
|
1756
1836
|
params = marketParams;
|
|
1757
1837
|
} else if (tokenSymbol) {
|
|
1758
|
-
params = await this.findMarketForCollateral(tokenSymbol);
|
|
1838
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1759
1839
|
} else {
|
|
1760
1840
|
const { params: p, symbol } = await this._findActiveMarket();
|
|
1761
1841
|
params = p;
|
|
1762
1842
|
usedToken = symbol;
|
|
1763
1843
|
}
|
|
1844
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1845
|
+
const parsedAmount = ethers2.parseUnits(amount, loanDecimals);
|
|
1764
1846
|
try {
|
|
1765
1847
|
const marketId = ethers2.keccak256(
|
|
1766
1848
|
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
@@ -1784,11 +1866,14 @@ var MorphoClient = class {
|
|
|
1784
1866
|
const totalBorrowAssets = BigInt(mktState.totalBorrowAssets);
|
|
1785
1867
|
const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1786
1868
|
const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
|
|
1787
|
-
if (
|
|
1788
|
-
const
|
|
1789
|
-
const
|
|
1869
|
+
if (parsedAmount > maxAdditional) {
|
|
1870
|
+
const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
|
|
1871
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
1872
|
+
const colInfo = await this._resolveToken(usedToken);
|
|
1873
|
+
const maxFormatted = ethers2.formatUnits(maxAdditional, loanDecimals);
|
|
1874
|
+
const colFormatted = ethers2.formatUnits(pos.collateral, colInfo.decimals);
|
|
1790
1875
|
throw new AgetherError(
|
|
1791
|
-
`Borrow of
|
|
1876
|
+
`Borrow of ${amount} ${loanSymbol} exceeds max borrowable ${maxFormatted} ${loanSymbol} (collateral: ${colFormatted} ${usedToken}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Deposit more collateral or reduce borrow amount.`,
|
|
1792
1877
|
"EXCEEDS_MAX_LTV"
|
|
1793
1878
|
);
|
|
1794
1879
|
}
|
|
@@ -1798,7 +1883,7 @@ var MorphoClient = class {
|
|
|
1798
1883
|
}
|
|
1799
1884
|
const data = morphoIface.encodeFunctionData("borrow", [
|
|
1800
1885
|
this._toTuple(params),
|
|
1801
|
-
|
|
1886
|
+
parsedAmount,
|
|
1802
1887
|
0n,
|
|
1803
1888
|
acctAddr,
|
|
1804
1889
|
acctAddr
|
|
@@ -1806,7 +1891,7 @@ var MorphoClient = class {
|
|
|
1806
1891
|
const receipt = await this.exec(morphoAddr, data);
|
|
1807
1892
|
return {
|
|
1808
1893
|
tx: receipt.hash,
|
|
1809
|
-
amount
|
|
1894
|
+
amount,
|
|
1810
1895
|
collateralToken: usedToken,
|
|
1811
1896
|
agentAccount: acctAddr
|
|
1812
1897
|
};
|
|
@@ -1814,17 +1899,27 @@ var MorphoClient = class {
|
|
|
1814
1899
|
/**
|
|
1815
1900
|
* Deposit collateral AND borrow USDC in one batched transaction.
|
|
1816
1901
|
*
|
|
1902
|
+
/**
|
|
1903
|
+
* Deposit collateral AND borrow loan token in one batched transaction.
|
|
1904
|
+
*
|
|
1817
1905
|
* AgentAccount.executeBatch:
|
|
1818
1906
|
* [collateral.approve, Morpho.supplyCollateral, Morpho.borrow]
|
|
1819
1907
|
*
|
|
1820
1908
|
* The collateral must be transferred to AgentAccount first.
|
|
1909
|
+
*
|
|
1910
|
+
* @param tokenSymbol - collateral token symbol (e.g. 'WETH')
|
|
1911
|
+
* @param collateralAmount - amount of collateral (e.g. '0.05')
|
|
1912
|
+
* @param borrowAmount - amount of loan token to borrow (e.g. '100')
|
|
1913
|
+
* @param marketParams - explicit market params (optional)
|
|
1914
|
+
* @param loanTokenSymbol - loan token to filter market (optional)
|
|
1821
1915
|
*/
|
|
1822
|
-
async depositAndBorrow(tokenSymbol, collateralAmount,
|
|
1916
|
+
async depositAndBorrow(tokenSymbol, collateralAmount, borrowAmount, marketParams, loanTokenSymbol) {
|
|
1823
1917
|
const acctAddr = await this.getAccountAddress();
|
|
1824
1918
|
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1825
|
-
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1919
|
+
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1920
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1826
1921
|
const colWei = ethers2.parseUnits(collateralAmount, colInfo.decimals);
|
|
1827
|
-
const borrowWei = ethers2.parseUnits(
|
|
1922
|
+
const borrowWei = ethers2.parseUnits(borrowAmount, loanDecimals);
|
|
1828
1923
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1829
1924
|
try {
|
|
1830
1925
|
const marketId = ethers2.keccak256(
|
|
@@ -1845,9 +1940,11 @@ var MorphoClient = class {
|
|
|
1845
1940
|
const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1846
1941
|
const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
|
|
1847
1942
|
if (borrowWei > maxAdditional) {
|
|
1848
|
-
const
|
|
1943
|
+
const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
|
|
1944
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
1945
|
+
const maxFormatted = ethers2.formatUnits(maxAdditional, loanDecimals);
|
|
1849
1946
|
throw new AgetherError(
|
|
1850
|
-
`Borrow of
|
|
1947
|
+
`Borrow of ${borrowAmount} ${loanSymbol} exceeds max borrowable ${maxFormatted} ${loanSymbol} (total collateral: ${ethers2.formatUnits(totalCollateral, colInfo.decimals)} ${tokenSymbol}, LLTV: ${Number(params.lltv) / 1e18 * 100}%). Reduce borrow or increase collateral.`,
|
|
1851
1948
|
"EXCEEDS_MAX_LTV"
|
|
1852
1949
|
);
|
|
1853
1950
|
}
|
|
@@ -1893,36 +1990,42 @@ var MorphoClient = class {
|
|
|
1893
1990
|
tx: receipt.hash,
|
|
1894
1991
|
collateralToken: tokenSymbol,
|
|
1895
1992
|
collateralAmount,
|
|
1896
|
-
borrowAmount
|
|
1993
|
+
borrowAmount,
|
|
1897
1994
|
agentAccount: acctAddr
|
|
1898
1995
|
};
|
|
1899
1996
|
}
|
|
1900
1997
|
/**
|
|
1901
|
-
* Repay borrowed
|
|
1998
|
+
* Repay borrowed loan token from AgentAccount.
|
|
1902
1999
|
*
|
|
1903
2000
|
* AgentAccount.executeBatch:
|
|
1904
|
-
* [
|
|
2001
|
+
* [loanToken.approve(MorphoBlue), Morpho.repay(params)]
|
|
2002
|
+
*
|
|
2003
|
+
* @param amount - loan token amount to repay (e.g. '50' or 'all' for full repayment)
|
|
2004
|
+
* @param tokenSymbol - collateral symbol to identify which market (optional)
|
|
2005
|
+
* @param marketParams - explicit market params (optional)
|
|
2006
|
+
* @param loanTokenSymbol - loan token to filter market (optional)
|
|
1905
2007
|
*/
|
|
1906
|
-
async repay(
|
|
2008
|
+
async repay(amount, tokenSymbol, marketParams, loanTokenSymbol) {
|
|
1907
2009
|
const acctAddr = await this.getAccountAddress();
|
|
1908
2010
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1909
|
-
const usdcAddr = this.config.contracts.usdc;
|
|
1910
2011
|
let params;
|
|
1911
2012
|
if (marketParams) {
|
|
1912
2013
|
params = marketParams;
|
|
1913
2014
|
} else if (tokenSymbol) {
|
|
1914
|
-
params = await this.findMarketForCollateral(tokenSymbol);
|
|
2015
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1915
2016
|
} else {
|
|
1916
2017
|
const { params: p } = await this._findActiveMarket();
|
|
1917
2018
|
params = p;
|
|
1918
2019
|
}
|
|
2020
|
+
const loanTokenAddr = params.loanToken;
|
|
2021
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1919
2022
|
let repayAssets;
|
|
1920
2023
|
let repayShares;
|
|
1921
2024
|
let approveAmount;
|
|
1922
|
-
if (
|
|
2025
|
+
if (amount === "all") {
|
|
1923
2026
|
const markets = await this.getMarkets();
|
|
1924
2027
|
const mkt = markets.find(
|
|
1925
|
-
(m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase()
|
|
2028
|
+
(m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase() && m.loanAsset?.address.toLowerCase() === params.loanToken.toLowerCase()
|
|
1926
2029
|
);
|
|
1927
2030
|
if (mkt) {
|
|
1928
2031
|
const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
|
|
@@ -1932,33 +2035,35 @@ var MorphoClient = class {
|
|
|
1932
2035
|
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
1933
2036
|
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
1934
2037
|
const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
|
|
1935
|
-
approveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1",
|
|
2038
|
+
approveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1", loanDecimals);
|
|
1936
2039
|
} else {
|
|
1937
|
-
repayAssets = ethers2.parseUnits("999999",
|
|
2040
|
+
repayAssets = ethers2.parseUnits("999999", loanDecimals);
|
|
1938
2041
|
repayShares = 0n;
|
|
1939
2042
|
approveAmount = repayAssets;
|
|
1940
2043
|
}
|
|
1941
2044
|
} else {
|
|
1942
|
-
repayAssets = ethers2.parseUnits(
|
|
2045
|
+
repayAssets = ethers2.parseUnits(amount, loanDecimals);
|
|
1943
2046
|
repayShares = 0n;
|
|
1944
2047
|
approveAmount = repayAssets;
|
|
1945
2048
|
}
|
|
1946
|
-
const
|
|
1947
|
-
const acctBalance = await
|
|
2049
|
+
const loanContract = new Contract2(loanTokenAddr, ERC20_ABI, this._signer);
|
|
2050
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
1948
2051
|
if (acctBalance < approveAmount) {
|
|
1949
2052
|
const shortfall = approveAmount - acctBalance;
|
|
1950
|
-
const eoaBalance = await
|
|
2053
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
1951
2054
|
if (eoaBalance < shortfall) {
|
|
2055
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
2056
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
1952
2057
|
throw new AgetherError(
|
|
1953
|
-
`Insufficient
|
|
2058
|
+
`Insufficient ${loanSymbol} for repay. Need ${ethers2.formatUnits(approveAmount, loanDecimals)}, AgentAccount has ${ethers2.formatUnits(acctBalance, loanDecimals)}, EOA has ${ethers2.formatUnits(eoaBalance, loanDecimals)}.`,
|
|
1954
2059
|
"INSUFFICIENT_BALANCE"
|
|
1955
2060
|
);
|
|
1956
2061
|
}
|
|
1957
|
-
const transferTx = await
|
|
2062
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
1958
2063
|
await transferTx.wait();
|
|
1959
2064
|
this._refreshSigner();
|
|
1960
2065
|
}
|
|
1961
|
-
const targets = [
|
|
2066
|
+
const targets = [loanTokenAddr, morphoAddr];
|
|
1962
2067
|
const values = [0n, 0n];
|
|
1963
2068
|
const datas = [
|
|
1964
2069
|
erc20Iface2.encodeFunctionData("approve", [morphoAddr, approveAmount]),
|
|
@@ -1978,7 +2083,7 @@ var MorphoClient = class {
|
|
|
1978
2083
|
} catch (e) {
|
|
1979
2084
|
console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
|
|
1980
2085
|
}
|
|
1981
|
-
return { tx: receipt.hash, amount
|
|
2086
|
+
return { tx: receipt.hash, amount, remainingDebt };
|
|
1982
2087
|
}
|
|
1983
2088
|
/**
|
|
1984
2089
|
* Withdraw collateral from Morpho Blue.
|
|
@@ -1992,12 +2097,13 @@ var MorphoClient = class {
|
|
|
1992
2097
|
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1993
2098
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1994
2099
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1995
|
-
const
|
|
2100
|
+
const loanTokenAddr = params.loanToken;
|
|
2101
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1996
2102
|
const dest = receiver || await this.getSignerAddress();
|
|
1997
2103
|
let weiAmount;
|
|
1998
2104
|
const markets = await this.getMarkets();
|
|
1999
2105
|
const market = markets.find(
|
|
2000
|
-
(m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase()
|
|
2106
|
+
(m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase() && m.loanAsset?.address.toLowerCase() === loanTokenAddr.toLowerCase()
|
|
2001
2107
|
);
|
|
2002
2108
|
if (amount === "all") {
|
|
2003
2109
|
if (!market) throw new AgetherError("Market not found", "MARKET_NOT_FOUND");
|
|
@@ -2020,8 +2126,10 @@ var MorphoClient = class {
|
|
|
2020
2126
|
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
2021
2127
|
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
2022
2128
|
const estimated = totalBorrowShares > 0n ? dustBorrowShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
|
|
2023
|
-
dustApproveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1",
|
|
2024
|
-
|
|
2129
|
+
dustApproveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1", loanDecimals);
|
|
2130
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
2131
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
2132
|
+
console.log(`[agether] dust borrow shares detected: ${dustBorrowShares} shares \u2248 ${ethers2.formatUnits(dustApproveAmount, loanDecimals)} ${loanSymbol} \u2014 auto-repaying before withdraw`);
|
|
2025
2133
|
}
|
|
2026
2134
|
} catch (e) {
|
|
2027
2135
|
console.warn("[agether] failed to check borrow shares before withdraw:", e instanceof Error ? e.message : e);
|
|
@@ -2035,19 +2143,21 @@ var MorphoClient = class {
|
|
|
2035
2143
|
]);
|
|
2036
2144
|
let receipt;
|
|
2037
2145
|
if (hasDustDebt) {
|
|
2038
|
-
const
|
|
2039
|
-
const acctBalance = await
|
|
2146
|
+
const loanContract = new Contract2(loanTokenAddr, ERC20_ABI, this._signer);
|
|
2147
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
2040
2148
|
if (acctBalance < dustApproveAmount) {
|
|
2041
2149
|
const shortfall = dustApproveAmount - acctBalance;
|
|
2042
|
-
const eoaBalance = await
|
|
2150
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
2043
2151
|
if (eoaBalance >= shortfall) {
|
|
2044
|
-
|
|
2045
|
-
const
|
|
2152
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
2153
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
2154
|
+
console.log(`[agether] transferring ${ethers2.formatUnits(shortfall, loanDecimals)} ${loanSymbol} from EOA \u2192 AgentAccount for dust repay`);
|
|
2155
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
2046
2156
|
await transferTx.wait();
|
|
2047
2157
|
this._refreshSigner();
|
|
2048
2158
|
}
|
|
2049
2159
|
}
|
|
2050
|
-
const targets = [
|
|
2160
|
+
const targets = [loanTokenAddr, morphoAddr, morphoAddr];
|
|
2051
2161
|
const values = [0n, 0n, 0n];
|
|
2052
2162
|
const datas = [
|
|
2053
2163
|
erc20Iface2.encodeFunctionData("approve", [morphoAddr, dustApproveAmount]),
|
|
@@ -2292,6 +2402,37 @@ var MorphoClient = class {
|
|
|
2292
2402
|
}
|
|
2293
2403
|
throw new AgetherError("No active supply position found", "NO_SUPPLY");
|
|
2294
2404
|
}
|
|
2405
|
+
/**
|
|
2406
|
+
* Resolve loan token decimals from market params.
|
|
2407
|
+
* Uses the `_tokenCache` populated by `getMarkets()`.
|
|
2408
|
+
*/
|
|
2409
|
+
async _getLoanTokenDecimals(params) {
|
|
2410
|
+
const tokenInfo = this._tokenCache.get(params.loanToken.toLowerCase());
|
|
2411
|
+
if (tokenInfo) return tokenInfo.decimals;
|
|
2412
|
+
await this.getMarkets();
|
|
2413
|
+
const fromApi = this._tokenCache.get(params.loanToken.toLowerCase());
|
|
2414
|
+
return fromApi?.decimals ?? 18;
|
|
2415
|
+
}
|
|
2416
|
+
/**
|
|
2417
|
+
* Apply client-side filter to discovered markets.
|
|
2418
|
+
*/
|
|
2419
|
+
_applyMarketFilter(markets, filter) {
|
|
2420
|
+
return markets.filter((m) => {
|
|
2421
|
+
if (filter.loanToken) {
|
|
2422
|
+
const loanAddr = filter.loanToken.toLowerCase();
|
|
2423
|
+
if (m.loanAsset.address.toLowerCase() !== loanAddr && m.loanAsset.symbol.toUpperCase() !== filter.loanToken.toUpperCase()) {
|
|
2424
|
+
return false;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
if (filter.collateralToken) {
|
|
2428
|
+
const colAddr = filter.collateralToken.toLowerCase();
|
|
2429
|
+
if (m.collateralAsset.address.toLowerCase() !== colAddr && m.collateralAsset.symbol.toUpperCase() !== filter.collateralToken.toUpperCase()) {
|
|
2430
|
+
return false;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
return true;
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2295
2436
|
/**
|
|
2296
2437
|
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
2297
2438
|
*
|
|
@@ -2308,8 +2449,8 @@ var MorphoClient = class {
|
|
|
2308
2449
|
const fromApi = this._tokenCache.get(key);
|
|
2309
2450
|
if (fromApi) return fromApi;
|
|
2310
2451
|
throw new AgetherError(
|
|
2311
|
-
`Unknown token: ${symbolOrAddress}. No Morpho market found with this
|
|
2312
|
-
"
|
|
2452
|
+
`Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
|
|
2453
|
+
"UNKNOWN_TOKEN"
|
|
2313
2454
|
);
|
|
2314
2455
|
}
|
|
2315
2456
|
/**
|