@agether/sdk 2.12.1 → 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 +294 -131
- package/dist/index.d.mts +92 -27
- package/dist/index.d.ts +92 -27
- package/dist/index.js +294 -131
- package/dist/index.mjs +294 -131
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -129,6 +129,7 @@ var IDENTITY_REGISTRY_ABI = [
|
|
|
129
129
|
"function exists(uint256 agentId) view returns (bool)",
|
|
130
130
|
"function register() returns (uint256 agentId)",
|
|
131
131
|
"function register(string agentURI) returns (uint256 agentId)",
|
|
132
|
+
"function setAgentURI(uint256 agentId, string newURI)",
|
|
132
133
|
"event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
|
|
133
134
|
];
|
|
134
135
|
var AGETHER_4337_FACTORY_ABI = [
|
|
@@ -492,6 +493,23 @@ var AgetherClient = class _AgetherClient {
|
|
|
492
493
|
}
|
|
493
494
|
const acctAddr = await this.agether4337Factory.getAccount(agentId);
|
|
494
495
|
this.accountAddress = acctAddr;
|
|
496
|
+
if (!acctExists) {
|
|
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();
|
|
512
|
+
}
|
|
495
513
|
const kyaRequired = await this.isKyaRequired();
|
|
496
514
|
return {
|
|
497
515
|
agentId: agentId.toString(),
|
|
@@ -508,7 +526,7 @@ var AgetherClient = class _AgetherClient {
|
|
|
508
526
|
async _mintNewIdentity(name, description) {
|
|
509
527
|
const registrationFile = JSON.stringify({
|
|
510
528
|
type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
|
|
511
|
-
name: name || "
|
|
529
|
+
name: name || "Agether Agent",
|
|
512
530
|
description: description || "AI agent registered via @agether/sdk",
|
|
513
531
|
active: true,
|
|
514
532
|
registrations: [{
|
|
@@ -1019,8 +1037,9 @@ var morphoIface = new import_ethers2.ethers.Interface(MORPHO_BLUE_ABI);
|
|
|
1019
1037
|
var erc20Iface2 = new import_ethers2.ethers.Interface(ERC20_ABI);
|
|
1020
1038
|
var MorphoClient = class {
|
|
1021
1039
|
constructor(config) {
|
|
1040
|
+
/** Market params cache: keyed by market uniqueKey (bytes32 hash) */
|
|
1022
1041
|
this._marketCache = /* @__PURE__ */ new Map();
|
|
1023
|
-
/** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
|
|
1042
|
+
/** Dynamic token registry: symbol (uppercase) or address (lowercase) → { address, symbol, decimals } */
|
|
1024
1043
|
this._tokenCache = /* @__PURE__ */ new Map();
|
|
1025
1044
|
this._discoveredAt = 0;
|
|
1026
1045
|
if (!config.agentId) {
|
|
@@ -1123,21 +1142,23 @@ var MorphoClient = class {
|
|
|
1123
1142
|
// Market Discovery (Morpho GraphQL API)
|
|
1124
1143
|
// ════════════════════════════════════════════════════════
|
|
1125
1144
|
/**
|
|
1126
|
-
* Fetch
|
|
1127
|
-
* 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
|
|
1128
1150
|
*/
|
|
1129
|
-
async getMarkets(forceRefresh = false) {
|
|
1151
|
+
async getMarkets(forceRefresh = false, filter) {
|
|
1130
1152
|
if (!forceRefresh && this._discoveredMarkets && Date.now() - this._discoveredAt < 3e5) {
|
|
1131
|
-
return this._discoveredMarkets;
|
|
1153
|
+
return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
|
|
1132
1154
|
}
|
|
1133
1155
|
const chainId = this.config.chainId;
|
|
1134
|
-
const usdcAddr = this.config.contracts.usdc.toLowerCase();
|
|
1135
1156
|
const query = `{
|
|
1136
1157
|
markets(
|
|
1137
1158
|
first: 50
|
|
1138
1159
|
orderBy: SupplyAssetsUsd
|
|
1139
1160
|
orderDirection: Desc
|
|
1140
|
-
where: { chainId_in: [${chainId}]
|
|
1161
|
+
where: { chainId_in: [${chainId}] }
|
|
1141
1162
|
) {
|
|
1142
1163
|
items {
|
|
1143
1164
|
uniqueKey
|
|
@@ -1170,14 +1191,14 @@ var MorphoClient = class {
|
|
|
1170
1191
|
}));
|
|
1171
1192
|
this._discoveredAt = Date.now();
|
|
1172
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
|
+
});
|
|
1173
1201
|
if (mi.collateralAsset.address !== import_ethers2.ethers.ZeroAddress) {
|
|
1174
|
-
this._marketCache.set(mi.collateralAsset.address.toLowerCase(), {
|
|
1175
|
-
loanToken: mi.loanAsset.address,
|
|
1176
|
-
collateralToken: mi.collateralAsset.address,
|
|
1177
|
-
oracle: mi.oracle,
|
|
1178
|
-
irm: mi.irm,
|
|
1179
|
-
lltv: mi.lltv
|
|
1180
|
-
});
|
|
1181
1202
|
this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
|
|
1182
1203
|
address: mi.collateralAsset.address,
|
|
1183
1204
|
symbol: mi.collateralAsset.symbol,
|
|
@@ -1202,17 +1223,24 @@ var MorphoClient = class {
|
|
|
1202
1223
|
});
|
|
1203
1224
|
}
|
|
1204
1225
|
}
|
|
1205
|
-
return this._discoveredMarkets;
|
|
1226
|
+
return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
|
|
1206
1227
|
} catch (e) {
|
|
1207
1228
|
console.warn("[agether] getMarkets failed, using cache:", e instanceof Error ? e.message : e);
|
|
1208
|
-
|
|
1229
|
+
const cached = this._discoveredMarkets ?? [];
|
|
1230
|
+
return filter ? this._applyMarketFilter(cached, filter) : cached;
|
|
1209
1231
|
}
|
|
1210
1232
|
}
|
|
1211
1233
|
/**
|
|
1212
|
-
* Get MarketParams for a collateral token.
|
|
1213
|
-
* 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)
|
|
1214
1242
|
*/
|
|
1215
|
-
async findMarketForCollateral(collateralSymbolOrAddress) {
|
|
1243
|
+
async findMarketForCollateral(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
|
|
1216
1244
|
let colAddr;
|
|
1217
1245
|
if (collateralSymbolOrAddress.startsWith("0x")) {
|
|
1218
1246
|
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
@@ -1224,13 +1252,33 @@ var MorphoClient = class {
|
|
|
1224
1252
|
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
1225
1253
|
}
|
|
1226
1254
|
}
|
|
1227
|
-
|
|
1228
|
-
if (
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
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
|
+
}
|
|
1232
1280
|
throw new AgetherError(
|
|
1233
|
-
`No Morpho market found for collateral ${collateralSymbolOrAddress}
|
|
1281
|
+
`No Morpho market found for collateral ${collateralSymbolOrAddress}` + (loanTokenSymbolOrAddress ? ` with loan token ${loanTokenSymbolOrAddress}` : ""),
|
|
1234
1282
|
"MARKET_NOT_FOUND"
|
|
1235
1283
|
);
|
|
1236
1284
|
}
|
|
@@ -1265,12 +1313,13 @@ var MorphoClient = class {
|
|
|
1265
1313
|
const acctAddr = await this.getAccountAddress();
|
|
1266
1314
|
const markets = await this.getMarkets();
|
|
1267
1315
|
const positions = [];
|
|
1268
|
-
let
|
|
1316
|
+
let totalDebtFloat = 0;
|
|
1269
1317
|
for (const m of markets) {
|
|
1270
1318
|
if (!m.collateralAsset || m.collateralAsset.address === import_ethers2.ethers.ZeroAddress) continue;
|
|
1271
1319
|
try {
|
|
1272
1320
|
const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
|
|
1273
1321
|
if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
|
|
1322
|
+
const loanDecimals = m.loanAsset.decimals;
|
|
1274
1323
|
let debt = 0n;
|
|
1275
1324
|
if (pos.borrowShares > 0n) {
|
|
1276
1325
|
try {
|
|
@@ -1278,7 +1327,7 @@ var MorphoClient = class {
|
|
|
1278
1327
|
const totalBorrowShares = BigInt(mkt.totalBorrowShares);
|
|
1279
1328
|
const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
|
|
1280
1329
|
debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1281
|
-
|
|
1330
|
+
totalDebtFloat += parseFloat(import_ethers2.ethers.formatUnits(debt, loanDecimals));
|
|
1282
1331
|
} catch (e) {
|
|
1283
1332
|
console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
|
|
1284
1333
|
}
|
|
@@ -1286,10 +1335,11 @@ var MorphoClient = class {
|
|
|
1286
1335
|
positions.push({
|
|
1287
1336
|
marketId: m.uniqueKey,
|
|
1288
1337
|
collateralToken: m.collateralAsset.symbol,
|
|
1338
|
+
loanToken: m.loanAsset.symbol,
|
|
1289
1339
|
collateral: import_ethers2.ethers.formatUnits(pos.collateral, m.collateralAsset.decimals),
|
|
1290
1340
|
borrowShares: pos.borrowShares.toString(),
|
|
1291
1341
|
supplyShares: pos.supplyShares.toString(),
|
|
1292
|
-
debt: import_ethers2.ethers.formatUnits(debt,
|
|
1342
|
+
debt: import_ethers2.ethers.formatUnits(debt, loanDecimals)
|
|
1293
1343
|
});
|
|
1294
1344
|
} catch (e) {
|
|
1295
1345
|
console.warn(`[agether] position read failed for market:`, e instanceof Error ? e.message : e);
|
|
@@ -1299,16 +1349,28 @@ var MorphoClient = class {
|
|
|
1299
1349
|
return {
|
|
1300
1350
|
agentId: this.agentId,
|
|
1301
1351
|
agentAccount: acctAddr,
|
|
1302
|
-
totalDebt:
|
|
1352
|
+
totalDebt: totalDebtFloat.toFixed(6),
|
|
1303
1353
|
positions
|
|
1304
1354
|
};
|
|
1305
1355
|
}
|
|
1306
1356
|
// ════════════════════════════════════════════════════════
|
|
1307
1357
|
// Balance & Borrowing Capacity
|
|
1308
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
|
+
}
|
|
1309
1370
|
/**
|
|
1310
1371
|
* Get the USDC balance of the AgentAccount.
|
|
1311
1372
|
* @returns USDC balance in raw units (6 decimals)
|
|
1373
|
+
* @deprecated Use `getTokenBalance('USDC')` instead.
|
|
1312
1374
|
*/
|
|
1313
1375
|
async getUsdcBalance() {
|
|
1314
1376
|
const acctAddr = await this.getAccountAddress();
|
|
@@ -1316,7 +1378,7 @@ var MorphoClient = class {
|
|
|
1316
1378
|
return usdc.balanceOf(acctAddr);
|
|
1317
1379
|
}
|
|
1318
1380
|
/**
|
|
1319
|
-
* Calculate the maximum additional
|
|
1381
|
+
* Calculate the maximum additional loan token that can be borrowed
|
|
1320
1382
|
* given the agent's current collateral and debt across all markets.
|
|
1321
1383
|
*
|
|
1322
1384
|
* For each market with collateral deposited:
|
|
@@ -1324,7 +1386,7 @@ var MorphoClient = class {
|
|
|
1324
1386
|
*
|
|
1325
1387
|
* Uses the Morpho oracle to price collateral → loan token.
|
|
1326
1388
|
*
|
|
1327
|
-
* @returns Maximum additional
|
|
1389
|
+
* @returns Maximum additional borrowable per market (raw units in each market's loan token)
|
|
1328
1390
|
*/
|
|
1329
1391
|
async getMaxBorrowable() {
|
|
1330
1392
|
const acctAddr = await this.getAccountAddress();
|
|
@@ -1357,6 +1419,8 @@ var MorphoClient = class {
|
|
|
1357
1419
|
totalAdditional += maxAdditional;
|
|
1358
1420
|
byMarket.push({
|
|
1359
1421
|
collateralToken: m.collateralAsset.symbol,
|
|
1422
|
+
loanToken: m.loanAsset.symbol,
|
|
1423
|
+
loanDecimals: m.loanAsset.decimals,
|
|
1360
1424
|
maxAdditional,
|
|
1361
1425
|
currentDebt,
|
|
1362
1426
|
collateralValue: collateralValueInLoan
|
|
@@ -1372,14 +1436,16 @@ var MorphoClient = class {
|
|
|
1372
1436
|
// Market Rates & Yield Estimation
|
|
1373
1437
|
// ════════════════════════════════════════════════════════
|
|
1374
1438
|
/**
|
|
1375
|
-
* Fetch current supply/borrow APY for
|
|
1439
|
+
* Fetch current supply/borrow APY for markets from Morpho GraphQL API.
|
|
1376
1440
|
*
|
|
1377
1441
|
* Note: On Morpho Blue, collateral does NOT earn yield directly. Supply APY
|
|
1378
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.
|
|
1379
1446
|
*/
|
|
1380
|
-
async getMarketRates(collateralSymbolOrAddress) {
|
|
1447
|
+
async getMarketRates(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
|
|
1381
1448
|
const chainId = this.config.chainId;
|
|
1382
|
-
const usdcAddr = this.config.contracts.usdc.toLowerCase();
|
|
1383
1449
|
let collateralFilter = "";
|
|
1384
1450
|
if (collateralSymbolOrAddress) {
|
|
1385
1451
|
let colAddr;
|
|
@@ -1395,12 +1461,27 @@ var MorphoClient = class {
|
|
|
1395
1461
|
}
|
|
1396
1462
|
collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
|
|
1397
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
|
+
}
|
|
1398
1479
|
const query = `{
|
|
1399
1480
|
markets(
|
|
1400
1481
|
first: 50
|
|
1401
1482
|
orderBy: SupplyAssetsUsd
|
|
1402
1483
|
orderDirection: Desc
|
|
1403
|
-
where: { chainId_in: [${chainId}]
|
|
1484
|
+
where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter} }
|
|
1404
1485
|
) {
|
|
1405
1486
|
items {
|
|
1406
1487
|
uniqueKey
|
|
@@ -1420,17 +1501,21 @@ var MorphoClient = class {
|
|
|
1420
1501
|
try {
|
|
1421
1502
|
const resp = await import_axios.default.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
|
|
1422
1503
|
const items = resp.data?.data?.markets?.items ?? [];
|
|
1423
|
-
return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== import_ethers2.ethers.ZeroAddress).map((m) =>
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
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
|
+
});
|
|
1434
1519
|
} catch (e) {
|
|
1435
1520
|
console.warn("[agether] getMarketRates failed:", e instanceof Error ? e.message : e);
|
|
1436
1521
|
return [];
|
|
@@ -1464,14 +1549,15 @@ var MorphoClient = class {
|
|
|
1464
1549
|
} else {
|
|
1465
1550
|
try {
|
|
1466
1551
|
const params = await this.findMarketForCollateral(collateralSymbol);
|
|
1552
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1467
1553
|
const oracleContract = new import_ethers2.Contract(params.oracle, [
|
|
1468
1554
|
"function price() view returns (uint256)"
|
|
1469
1555
|
], this.provider);
|
|
1470
1556
|
const oraclePrice = await oracleContract.price();
|
|
1471
1557
|
const ORACLE_PRICE_SCALE = 10n ** 36n;
|
|
1472
1558
|
const amountWei = import_ethers2.ethers.parseUnits(amount, colInfo.decimals);
|
|
1473
|
-
const
|
|
1474
|
-
collateralValueUsd = Number(
|
|
1559
|
+
const valueInLoan = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
|
|
1560
|
+
collateralValueUsd = Number(valueInLoan) / 10 ** loanDecimals;
|
|
1475
1561
|
} catch (e) {
|
|
1476
1562
|
console.warn("[agether] oracle price fetch for yield estimation failed:", e instanceof Error ? e.message : e);
|
|
1477
1563
|
throw new AgetherError("Cannot determine collateral value. Provide ethPriceUsd.", "PRICE_UNAVAILABLE");
|
|
@@ -1492,61 +1578,65 @@ var MorphoClient = class {
|
|
|
1492
1578
|
// Supply-Side (Lending) — earn yield by supplying USDC
|
|
1493
1579
|
// ════════════════════════════════════════════════════════
|
|
1494
1580
|
/**
|
|
1495
|
-
* Supply
|
|
1581
|
+
* Supply loan token to a Morpho Blue market as a lender (earn yield).
|
|
1496
1582
|
*
|
|
1497
1583
|
* Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
|
|
1498
|
-
* you deposit the loanToken
|
|
1584
|
+
* you deposit the loanToken into the market's supply pool and earn
|
|
1499
1585
|
* interest paid by borrowers.
|
|
1500
1586
|
*
|
|
1501
|
-
* @param
|
|
1587
|
+
* @param amount - Amount of loan token to supply (e.g. '500' for 500 USDC, '0.5' for 0.5 WETH)
|
|
1502
1588
|
* @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
|
|
1503
1589
|
* Optional — defaults to highest-APY market
|
|
1590
|
+
* @param loanTokenSymbol - Loan token to filter market (e.g. 'USDC', 'WETH'). Optional.
|
|
1504
1591
|
*/
|
|
1505
|
-
async supplyAsset(
|
|
1592
|
+
async supplyAsset(amount, collateralSymbol, loanTokenSymbol) {
|
|
1506
1593
|
const acctAddr = await this.getAccountAddress();
|
|
1507
|
-
const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
|
|
1508
1594
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1509
|
-
const usdcAddr = this.config.contracts.usdc;
|
|
1510
1595
|
let params;
|
|
1511
1596
|
let usedCollateral;
|
|
1512
1597
|
if (collateralSymbol) {
|
|
1513
|
-
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1598
|
+
params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
|
|
1514
1599
|
usedCollateral = collateralSymbol;
|
|
1515
1600
|
} else {
|
|
1516
|
-
const rates = await this.getMarketRates();
|
|
1601
|
+
const rates = await this.getMarketRates(void 0, loanTokenSymbol);
|
|
1517
1602
|
if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
|
|
1518
1603
|
const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
|
|
1519
|
-
params = await this.findMarketForCollateral(best.collateralToken);
|
|
1604
|
+
params = await this.findMarketForCollateral(best.collateralToken, loanTokenSymbol);
|
|
1520
1605
|
usedCollateral = best.collateralToken;
|
|
1521
1606
|
}
|
|
1607
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1608
|
+
const loanTokenAddr = params.loanToken;
|
|
1609
|
+
const parsedAmount = import_ethers2.ethers.parseUnits(amount, loanDecimals);
|
|
1522
1610
|
const marketId = import_ethers2.ethers.keccak256(
|
|
1523
1611
|
import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
1524
1612
|
["address", "address", "address", "address", "uint256"],
|
|
1525
1613
|
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
1526
1614
|
)
|
|
1527
1615
|
);
|
|
1528
|
-
const
|
|
1529
|
-
const acctBalance = await
|
|
1530
|
-
if (acctBalance <
|
|
1531
|
-
const shortfall =
|
|
1532
|
-
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());
|
|
1533
1621
|
if (eoaBalance < shortfall) {
|
|
1622
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
1623
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
1534
1624
|
throw new AgetherError(
|
|
1535
|
-
`Insufficient
|
|
1625
|
+
`Insufficient ${loanSymbol}. Need ${amount}, AgentAccount has ${import_ethers2.ethers.formatUnits(acctBalance, loanDecimals)}, EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, loanDecimals)}.`,
|
|
1536
1626
|
"INSUFFICIENT_BALANCE"
|
|
1537
1627
|
);
|
|
1538
1628
|
}
|
|
1539
|
-
const transferTx = await
|
|
1629
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
1540
1630
|
await transferTx.wait();
|
|
1541
1631
|
this._refreshSigner();
|
|
1542
1632
|
}
|
|
1543
|
-
const targets = [
|
|
1633
|
+
const targets = [loanTokenAddr, morphoAddr];
|
|
1544
1634
|
const values = [0n, 0n];
|
|
1545
1635
|
const datas = [
|
|
1546
|
-
erc20Iface2.encodeFunctionData("approve", [morphoAddr,
|
|
1636
|
+
erc20Iface2.encodeFunctionData("approve", [morphoAddr, parsedAmount]),
|
|
1547
1637
|
morphoIface.encodeFunctionData("supply", [
|
|
1548
1638
|
this._toTuple(params),
|
|
1549
|
-
|
|
1639
|
+
parsedAmount,
|
|
1550
1640
|
0n,
|
|
1551
1641
|
acctAddr,
|
|
1552
1642
|
"0x"
|
|
@@ -1555,7 +1645,7 @@ var MorphoClient = class {
|
|
|
1555
1645
|
const receipt = await this.batch(targets, values, datas);
|
|
1556
1646
|
return {
|
|
1557
1647
|
tx: receipt.hash,
|
|
1558
|
-
amount
|
|
1648
|
+
amount,
|
|
1559
1649
|
marketId,
|
|
1560
1650
|
collateralToken: usedCollateral,
|
|
1561
1651
|
agentAccount: acctAddr
|
|
@@ -1568,17 +1658,26 @@ var MorphoClient = class {
|
|
|
1568
1658
|
* @param collateralSymbol - Market collateral to identify which market
|
|
1569
1659
|
* @param receiver - Destination address (defaults to EOA)
|
|
1570
1660
|
*/
|
|
1571
|
-
|
|
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) {
|
|
1572
1670
|
const acctAddr = await this.getAccountAddress();
|
|
1573
1671
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1574
1672
|
const dest = receiver || await this.getSignerAddress();
|
|
1575
1673
|
let params;
|
|
1576
1674
|
if (collateralSymbol) {
|
|
1577
|
-
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1675
|
+
params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
|
|
1578
1676
|
} else {
|
|
1579
1677
|
const { params: p } = await this._findActiveSupplyMarket();
|
|
1580
1678
|
params = p;
|
|
1581
1679
|
}
|
|
1680
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1582
1681
|
const marketId = import_ethers2.ethers.keccak256(
|
|
1583
1682
|
import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
1584
1683
|
["address", "address", "address", "address", "uint256"],
|
|
@@ -1587,13 +1686,13 @@ var MorphoClient = class {
|
|
|
1587
1686
|
);
|
|
1588
1687
|
let withdrawAssets;
|
|
1589
1688
|
let withdrawShares;
|
|
1590
|
-
if (
|
|
1689
|
+
if (amount === "all") {
|
|
1591
1690
|
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
1592
1691
|
withdrawShares = BigInt(pos.supplyShares);
|
|
1593
1692
|
withdrawAssets = 0n;
|
|
1594
1693
|
if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
|
|
1595
1694
|
} else {
|
|
1596
|
-
withdrawAssets = import_ethers2.ethers.parseUnits(
|
|
1695
|
+
withdrawAssets = import_ethers2.ethers.parseUnits(amount, loanDecimals);
|
|
1597
1696
|
withdrawShares = 0n;
|
|
1598
1697
|
}
|
|
1599
1698
|
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
@@ -1611,13 +1710,13 @@ var MorphoClient = class {
|
|
|
1611
1710
|
const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
|
|
1612
1711
|
const totalSupplyShares = BigInt(mkt.totalSupplyShares);
|
|
1613
1712
|
const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
|
|
1614
|
-
remainingSupply = import_ethers2.ethers.formatUnits(currentAssets,
|
|
1713
|
+
remainingSupply = import_ethers2.ethers.formatUnits(currentAssets, loanDecimals);
|
|
1615
1714
|
} catch (e) {
|
|
1616
1715
|
console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
|
|
1617
1716
|
}
|
|
1618
1717
|
return {
|
|
1619
1718
|
tx: receipt.hash,
|
|
1620
|
-
amount
|
|
1719
|
+
amount,
|
|
1621
1720
|
remainingSupply,
|
|
1622
1721
|
destination: dest
|
|
1623
1722
|
};
|
|
@@ -1690,14 +1789,13 @@ var MorphoClient = class {
|
|
|
1690
1789
|
* Computes available yield, verifies the requested amount doesn't exceed it,
|
|
1691
1790
|
* then withdraws from the supply position and sends directly to the recipient.
|
|
1692
1791
|
*
|
|
1693
|
-
* @param recipient - Address to receive the
|
|
1694
|
-
* @param
|
|
1792
|
+
* @param recipient - Address to receive the loan token
|
|
1793
|
+
* @param amount - Amount to pay from yield (e.g. '5.50')
|
|
1695
1794
|
* @param collateralSymbol - Market collateral to identify which supply position
|
|
1696
1795
|
*/
|
|
1697
|
-
async payFromYield(recipient,
|
|
1796
|
+
async payFromYield(recipient, amount, collateralSymbol) {
|
|
1698
1797
|
const acctAddr = await this.getAccountAddress();
|
|
1699
1798
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1700
|
-
const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
|
|
1701
1799
|
const positions = await this.getSupplyPositions(collateralSymbol);
|
|
1702
1800
|
if (positions.length === 0) {
|
|
1703
1801
|
throw new AgetherError("No supply position found", "NO_SUPPLY");
|
|
@@ -1705,17 +1803,20 @@ var MorphoClient = class {
|
|
|
1705
1803
|
const pos = positions.reduce(
|
|
1706
1804
|
(a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
|
|
1707
1805
|
);
|
|
1708
|
-
const
|
|
1709
|
-
|
|
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;
|
|
1710
1812
|
throw new AgetherError(
|
|
1711
|
-
`Requested ${
|
|
1813
|
+
`Requested ${amount} ${loanSymbol} exceeds available yield of ${pos.earnedYield} ${loanSymbol}. Use withdrawSupply to withdraw principal.`,
|
|
1712
1814
|
"EXCEEDS_YIELD"
|
|
1713
1815
|
);
|
|
1714
1816
|
}
|
|
1715
|
-
const params = await this.findMarketForCollateral(pos.collateralToken);
|
|
1716
1817
|
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
1717
1818
|
this._toTuple(params),
|
|
1718
|
-
|
|
1819
|
+
parsedAmount,
|
|
1719
1820
|
0n,
|
|
1720
1821
|
acctAddr,
|
|
1721
1822
|
recipient
|
|
@@ -1734,7 +1835,7 @@ var MorphoClient = class {
|
|
|
1734
1835
|
}
|
|
1735
1836
|
return {
|
|
1736
1837
|
tx: receipt.hash,
|
|
1737
|
-
yieldWithdrawn:
|
|
1838
|
+
yieldWithdrawn: amount,
|
|
1738
1839
|
recipient,
|
|
1739
1840
|
remainingYield,
|
|
1740
1841
|
remainingSupply
|
|
@@ -1792,28 +1893,31 @@ var MorphoClient = class {
|
|
|
1792
1893
|
};
|
|
1793
1894
|
}
|
|
1794
1895
|
/**
|
|
1795
|
-
* Borrow
|
|
1896
|
+
* Borrow loan token against existing collateral.
|
|
1796
1897
|
*
|
|
1797
1898
|
* AgentAccount.execute: Morpho.borrow(params, amount, 0, account, account)
|
|
1798
1899
|
*
|
|
1799
|
-
* @param
|
|
1900
|
+
* @param amount - Loan token amount (e.g. '100' for 100 USDC, '0.5' for 0.5 WETH)
|
|
1800
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')
|
|
1801
1904
|
*/
|
|
1802
|
-
async borrow(
|
|
1905
|
+
async borrow(amount, tokenSymbol, marketParams, loanTokenSymbol) {
|
|
1803
1906
|
const acctAddr = await this.getAccountAddress();
|
|
1804
|
-
const amount = import_ethers2.ethers.parseUnits(usdcAmount, 6);
|
|
1805
1907
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1806
1908
|
let params;
|
|
1807
1909
|
let usedToken = tokenSymbol || "WETH";
|
|
1808
1910
|
if (marketParams) {
|
|
1809
1911
|
params = marketParams;
|
|
1810
1912
|
} else if (tokenSymbol) {
|
|
1811
|
-
params = await this.findMarketForCollateral(tokenSymbol);
|
|
1913
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1812
1914
|
} else {
|
|
1813
1915
|
const { params: p, symbol } = await this._findActiveMarket();
|
|
1814
1916
|
params = p;
|
|
1815
1917
|
usedToken = symbol;
|
|
1816
1918
|
}
|
|
1919
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1920
|
+
const parsedAmount = import_ethers2.ethers.parseUnits(amount, loanDecimals);
|
|
1817
1921
|
try {
|
|
1818
1922
|
const marketId = import_ethers2.ethers.keccak256(
|
|
1819
1923
|
import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
@@ -1837,11 +1941,14 @@ var MorphoClient = class {
|
|
|
1837
1941
|
const totalBorrowAssets = BigInt(mktState.totalBorrowAssets);
|
|
1838
1942
|
const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1839
1943
|
const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
|
|
1840
|
-
if (
|
|
1841
|
-
const
|
|
1842
|
-
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);
|
|
1843
1950
|
throw new AgetherError(
|
|
1844
|
-
`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.`,
|
|
1845
1952
|
"EXCEEDS_MAX_LTV"
|
|
1846
1953
|
);
|
|
1847
1954
|
}
|
|
@@ -1851,7 +1958,7 @@ var MorphoClient = class {
|
|
|
1851
1958
|
}
|
|
1852
1959
|
const data = morphoIface.encodeFunctionData("borrow", [
|
|
1853
1960
|
this._toTuple(params),
|
|
1854
|
-
|
|
1961
|
+
parsedAmount,
|
|
1855
1962
|
0n,
|
|
1856
1963
|
acctAddr,
|
|
1857
1964
|
acctAddr
|
|
@@ -1859,7 +1966,7 @@ var MorphoClient = class {
|
|
|
1859
1966
|
const receipt = await this.exec(morphoAddr, data);
|
|
1860
1967
|
return {
|
|
1861
1968
|
tx: receipt.hash,
|
|
1862
|
-
amount
|
|
1969
|
+
amount,
|
|
1863
1970
|
collateralToken: usedToken,
|
|
1864
1971
|
agentAccount: acctAddr
|
|
1865
1972
|
};
|
|
@@ -1867,17 +1974,27 @@ var MorphoClient = class {
|
|
|
1867
1974
|
/**
|
|
1868
1975
|
* Deposit collateral AND borrow USDC in one batched transaction.
|
|
1869
1976
|
*
|
|
1977
|
+
/**
|
|
1978
|
+
* Deposit collateral AND borrow loan token in one batched transaction.
|
|
1979
|
+
*
|
|
1870
1980
|
* AgentAccount.executeBatch:
|
|
1871
1981
|
* [collateral.approve, Morpho.supplyCollateral, Morpho.borrow]
|
|
1872
1982
|
*
|
|
1873
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)
|
|
1874
1990
|
*/
|
|
1875
|
-
async depositAndBorrow(tokenSymbol, collateralAmount,
|
|
1991
|
+
async depositAndBorrow(tokenSymbol, collateralAmount, borrowAmount, marketParams, loanTokenSymbol) {
|
|
1876
1992
|
const acctAddr = await this.getAccountAddress();
|
|
1877
1993
|
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1878
|
-
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1994
|
+
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1995
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1879
1996
|
const colWei = import_ethers2.ethers.parseUnits(collateralAmount, colInfo.decimals);
|
|
1880
|
-
const borrowWei = import_ethers2.ethers.parseUnits(
|
|
1997
|
+
const borrowWei = import_ethers2.ethers.parseUnits(borrowAmount, loanDecimals);
|
|
1881
1998
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1882
1999
|
try {
|
|
1883
2000
|
const marketId = import_ethers2.ethers.keccak256(
|
|
@@ -1898,9 +2015,11 @@ var MorphoClient = class {
|
|
|
1898
2015
|
const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1899
2016
|
const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
|
|
1900
2017
|
if (borrowWei > maxAdditional) {
|
|
1901
|
-
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);
|
|
1902
2021
|
throw new AgetherError(
|
|
1903
|
-
`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.`,
|
|
1904
2023
|
"EXCEEDS_MAX_LTV"
|
|
1905
2024
|
);
|
|
1906
2025
|
}
|
|
@@ -1946,36 +2065,42 @@ var MorphoClient = class {
|
|
|
1946
2065
|
tx: receipt.hash,
|
|
1947
2066
|
collateralToken: tokenSymbol,
|
|
1948
2067
|
collateralAmount,
|
|
1949
|
-
borrowAmount
|
|
2068
|
+
borrowAmount,
|
|
1950
2069
|
agentAccount: acctAddr
|
|
1951
2070
|
};
|
|
1952
2071
|
}
|
|
1953
2072
|
/**
|
|
1954
|
-
* Repay borrowed
|
|
2073
|
+
* Repay borrowed loan token from AgentAccount.
|
|
1955
2074
|
*
|
|
1956
2075
|
* AgentAccount.executeBatch:
|
|
1957
|
-
* [
|
|
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)
|
|
1958
2082
|
*/
|
|
1959
|
-
async repay(
|
|
2083
|
+
async repay(amount, tokenSymbol, marketParams, loanTokenSymbol) {
|
|
1960
2084
|
const acctAddr = await this.getAccountAddress();
|
|
1961
2085
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1962
|
-
const usdcAddr = this.config.contracts.usdc;
|
|
1963
2086
|
let params;
|
|
1964
2087
|
if (marketParams) {
|
|
1965
2088
|
params = marketParams;
|
|
1966
2089
|
} else if (tokenSymbol) {
|
|
1967
|
-
params = await this.findMarketForCollateral(tokenSymbol);
|
|
2090
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1968
2091
|
} else {
|
|
1969
2092
|
const { params: p } = await this._findActiveMarket();
|
|
1970
2093
|
params = p;
|
|
1971
2094
|
}
|
|
2095
|
+
const loanTokenAddr = params.loanToken;
|
|
2096
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1972
2097
|
let repayAssets;
|
|
1973
2098
|
let repayShares;
|
|
1974
2099
|
let approveAmount;
|
|
1975
|
-
if (
|
|
2100
|
+
if (amount === "all") {
|
|
1976
2101
|
const markets = await this.getMarkets();
|
|
1977
2102
|
const mkt = markets.find(
|
|
1978
|
-
(m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase()
|
|
2103
|
+
(m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase() && m.loanAsset?.address.toLowerCase() === params.loanToken.toLowerCase()
|
|
1979
2104
|
);
|
|
1980
2105
|
if (mkt) {
|
|
1981
2106
|
const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
|
|
@@ -1985,33 +2110,35 @@ var MorphoClient = class {
|
|
|
1985
2110
|
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
1986
2111
|
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
1987
2112
|
const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
|
|
1988
|
-
approveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1",
|
|
2113
|
+
approveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1", loanDecimals);
|
|
1989
2114
|
} else {
|
|
1990
|
-
repayAssets = import_ethers2.ethers.parseUnits("999999",
|
|
2115
|
+
repayAssets = import_ethers2.ethers.parseUnits("999999", loanDecimals);
|
|
1991
2116
|
repayShares = 0n;
|
|
1992
2117
|
approveAmount = repayAssets;
|
|
1993
2118
|
}
|
|
1994
2119
|
} else {
|
|
1995
|
-
repayAssets = import_ethers2.ethers.parseUnits(
|
|
2120
|
+
repayAssets = import_ethers2.ethers.parseUnits(amount, loanDecimals);
|
|
1996
2121
|
repayShares = 0n;
|
|
1997
2122
|
approveAmount = repayAssets;
|
|
1998
2123
|
}
|
|
1999
|
-
const
|
|
2000
|
-
const acctBalance = await
|
|
2124
|
+
const loanContract = new import_ethers2.Contract(loanTokenAddr, ERC20_ABI, this._signer);
|
|
2125
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
2001
2126
|
if (acctBalance < approveAmount) {
|
|
2002
2127
|
const shortfall = approveAmount - acctBalance;
|
|
2003
|
-
const eoaBalance = await
|
|
2128
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
2004
2129
|
if (eoaBalance < shortfall) {
|
|
2130
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
2131
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
2005
2132
|
throw new AgetherError(
|
|
2006
|
-
`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)}.`,
|
|
2007
2134
|
"INSUFFICIENT_BALANCE"
|
|
2008
2135
|
);
|
|
2009
2136
|
}
|
|
2010
|
-
const transferTx = await
|
|
2137
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
2011
2138
|
await transferTx.wait();
|
|
2012
2139
|
this._refreshSigner();
|
|
2013
2140
|
}
|
|
2014
|
-
const targets = [
|
|
2141
|
+
const targets = [loanTokenAddr, morphoAddr];
|
|
2015
2142
|
const values = [0n, 0n];
|
|
2016
2143
|
const datas = [
|
|
2017
2144
|
erc20Iface2.encodeFunctionData("approve", [morphoAddr, approveAmount]),
|
|
@@ -2031,7 +2158,7 @@ var MorphoClient = class {
|
|
|
2031
2158
|
} catch (e) {
|
|
2032
2159
|
console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
|
|
2033
2160
|
}
|
|
2034
|
-
return { tx: receipt.hash, amount
|
|
2161
|
+
return { tx: receipt.hash, amount, remainingDebt };
|
|
2035
2162
|
}
|
|
2036
2163
|
/**
|
|
2037
2164
|
* Withdraw collateral from Morpho Blue.
|
|
@@ -2045,12 +2172,13 @@ var MorphoClient = class {
|
|
|
2045
2172
|
const colInfo = await this._resolveToken(tokenSymbol);
|
|
2046
2173
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
2047
2174
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
2048
|
-
const
|
|
2175
|
+
const loanTokenAddr = params.loanToken;
|
|
2176
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
2049
2177
|
const dest = receiver || await this.getSignerAddress();
|
|
2050
2178
|
let weiAmount;
|
|
2051
2179
|
const markets = await this.getMarkets();
|
|
2052
2180
|
const market = markets.find(
|
|
2053
|
-
(m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase()
|
|
2181
|
+
(m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase() && m.loanAsset?.address.toLowerCase() === loanTokenAddr.toLowerCase()
|
|
2054
2182
|
);
|
|
2055
2183
|
if (amount === "all") {
|
|
2056
2184
|
if (!market) throw new AgetherError("Market not found", "MARKET_NOT_FOUND");
|
|
@@ -2073,8 +2201,10 @@ var MorphoClient = class {
|
|
|
2073
2201
|
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
2074
2202
|
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
2075
2203
|
const estimated = totalBorrowShares > 0n ? dustBorrowShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
|
|
2076
|
-
dustApproveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1",
|
|
2077
|
-
|
|
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`);
|
|
2078
2208
|
}
|
|
2079
2209
|
} catch (e) {
|
|
2080
2210
|
console.warn("[agether] failed to check borrow shares before withdraw:", e instanceof Error ? e.message : e);
|
|
@@ -2088,19 +2218,21 @@ var MorphoClient = class {
|
|
|
2088
2218
|
]);
|
|
2089
2219
|
let receipt;
|
|
2090
2220
|
if (hasDustDebt) {
|
|
2091
|
-
const
|
|
2092
|
-
const acctBalance = await
|
|
2221
|
+
const loanContract = new import_ethers2.Contract(loanTokenAddr, ERC20_ABI, this._signer);
|
|
2222
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
2093
2223
|
if (acctBalance < dustApproveAmount) {
|
|
2094
2224
|
const shortfall = dustApproveAmount - acctBalance;
|
|
2095
|
-
const eoaBalance = await
|
|
2225
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
2096
2226
|
if (eoaBalance >= shortfall) {
|
|
2097
|
-
|
|
2098
|
-
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);
|
|
2099
2231
|
await transferTx.wait();
|
|
2100
2232
|
this._refreshSigner();
|
|
2101
2233
|
}
|
|
2102
2234
|
}
|
|
2103
|
-
const targets = [
|
|
2235
|
+
const targets = [loanTokenAddr, morphoAddr, morphoAddr];
|
|
2104
2236
|
const values = [0n, 0n, 0n];
|
|
2105
2237
|
const datas = [
|
|
2106
2238
|
erc20Iface2.encodeFunctionData("approve", [morphoAddr, dustApproveAmount]),
|
|
@@ -2345,6 +2477,37 @@ var MorphoClient = class {
|
|
|
2345
2477
|
}
|
|
2346
2478
|
throw new AgetherError("No active supply position found", "NO_SUPPLY");
|
|
2347
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
|
+
}
|
|
2348
2511
|
/**
|
|
2349
2512
|
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
2350
2513
|
*
|
|
@@ -2361,8 +2524,8 @@ var MorphoClient = class {
|
|
|
2361
2524
|
const fromApi = this._tokenCache.get(key);
|
|
2362
2525
|
if (fromApi) return fromApi;
|
|
2363
2526
|
throw new AgetherError(
|
|
2364
|
-
`Unknown token: ${symbolOrAddress}. No Morpho market found with this
|
|
2365
|
-
"
|
|
2527
|
+
`Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
|
|
2528
|
+
"UNKNOWN_TOKEN"
|
|
2366
2529
|
);
|
|
2367
2530
|
}
|
|
2368
2531
|
/**
|