@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.mjs
CHANGED
|
@@ -54,6 +54,7 @@ var IDENTITY_REGISTRY_ABI = [
|
|
|
54
54
|
"function exists(uint256 agentId) view returns (bool)",
|
|
55
55
|
"function register() returns (uint256 agentId)",
|
|
56
56
|
"function register(string agentURI) returns (uint256 agentId)",
|
|
57
|
+
"function setAgentURI(uint256 agentId, string newURI)",
|
|
57
58
|
"event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
|
|
58
59
|
];
|
|
59
60
|
var AGETHER_4337_FACTORY_ABI = [
|
|
@@ -417,6 +418,23 @@ var AgetherClient = class _AgetherClient {
|
|
|
417
418
|
}
|
|
418
419
|
const acctAddr = await this.agether4337Factory.getAccount(agentId);
|
|
419
420
|
this.accountAddress = acctAddr;
|
|
421
|
+
if (!acctExists) {
|
|
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();
|
|
437
|
+
}
|
|
420
438
|
const kyaRequired = await this.isKyaRequired();
|
|
421
439
|
return {
|
|
422
440
|
agentId: agentId.toString(),
|
|
@@ -433,7 +451,7 @@ var AgetherClient = class _AgetherClient {
|
|
|
433
451
|
async _mintNewIdentity(name, description) {
|
|
434
452
|
const registrationFile = JSON.stringify({
|
|
435
453
|
type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
|
|
436
|
-
name: name || "
|
|
454
|
+
name: name || "Agether Agent",
|
|
437
455
|
description: description || "AI agent registered via @agether/sdk",
|
|
438
456
|
active: true,
|
|
439
457
|
registrations: [{
|
|
@@ -944,8 +962,9 @@ var morphoIface = new ethers2.Interface(MORPHO_BLUE_ABI);
|
|
|
944
962
|
var erc20Iface2 = new ethers2.Interface(ERC20_ABI);
|
|
945
963
|
var MorphoClient = class {
|
|
946
964
|
constructor(config) {
|
|
965
|
+
/** Market params cache: keyed by market uniqueKey (bytes32 hash) */
|
|
947
966
|
this._marketCache = /* @__PURE__ */ new Map();
|
|
948
|
-
/** Dynamic token registry: symbol (uppercase) → { address, symbol, decimals } */
|
|
967
|
+
/** Dynamic token registry: symbol (uppercase) or address (lowercase) → { address, symbol, decimals } */
|
|
949
968
|
this._tokenCache = /* @__PURE__ */ new Map();
|
|
950
969
|
this._discoveredAt = 0;
|
|
951
970
|
if (!config.agentId) {
|
|
@@ -1048,21 +1067,23 @@ var MorphoClient = class {
|
|
|
1048
1067
|
// Market Discovery (Morpho GraphQL API)
|
|
1049
1068
|
// ════════════════════════════════════════════════════════
|
|
1050
1069
|
/**
|
|
1051
|
-
* Fetch
|
|
1052
|
-
* 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
|
|
1053
1075
|
*/
|
|
1054
|
-
async getMarkets(forceRefresh = false) {
|
|
1076
|
+
async getMarkets(forceRefresh = false, filter) {
|
|
1055
1077
|
if (!forceRefresh && this._discoveredMarkets && Date.now() - this._discoveredAt < 3e5) {
|
|
1056
|
-
return this._discoveredMarkets;
|
|
1078
|
+
return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
|
|
1057
1079
|
}
|
|
1058
1080
|
const chainId = this.config.chainId;
|
|
1059
|
-
const usdcAddr = this.config.contracts.usdc.toLowerCase();
|
|
1060
1081
|
const query = `{
|
|
1061
1082
|
markets(
|
|
1062
1083
|
first: 50
|
|
1063
1084
|
orderBy: SupplyAssetsUsd
|
|
1064
1085
|
orderDirection: Desc
|
|
1065
|
-
where: { chainId_in: [${chainId}]
|
|
1086
|
+
where: { chainId_in: [${chainId}] }
|
|
1066
1087
|
) {
|
|
1067
1088
|
items {
|
|
1068
1089
|
uniqueKey
|
|
@@ -1095,14 +1116,14 @@ var MorphoClient = class {
|
|
|
1095
1116
|
}));
|
|
1096
1117
|
this._discoveredAt = Date.now();
|
|
1097
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
|
+
});
|
|
1098
1126
|
if (mi.collateralAsset.address !== ethers2.ZeroAddress) {
|
|
1099
|
-
this._marketCache.set(mi.collateralAsset.address.toLowerCase(), {
|
|
1100
|
-
loanToken: mi.loanAsset.address,
|
|
1101
|
-
collateralToken: mi.collateralAsset.address,
|
|
1102
|
-
oracle: mi.oracle,
|
|
1103
|
-
irm: mi.irm,
|
|
1104
|
-
lltv: mi.lltv
|
|
1105
|
-
});
|
|
1106
1127
|
this._tokenCache.set(mi.collateralAsset.symbol.toUpperCase(), {
|
|
1107
1128
|
address: mi.collateralAsset.address,
|
|
1108
1129
|
symbol: mi.collateralAsset.symbol,
|
|
@@ -1127,17 +1148,24 @@ var MorphoClient = class {
|
|
|
1127
1148
|
});
|
|
1128
1149
|
}
|
|
1129
1150
|
}
|
|
1130
|
-
return this._discoveredMarkets;
|
|
1151
|
+
return filter ? this._applyMarketFilter(this._discoveredMarkets, filter) : this._discoveredMarkets;
|
|
1131
1152
|
} catch (e) {
|
|
1132
1153
|
console.warn("[agether] getMarkets failed, using cache:", e instanceof Error ? e.message : e);
|
|
1133
|
-
|
|
1154
|
+
const cached = this._discoveredMarkets ?? [];
|
|
1155
|
+
return filter ? this._applyMarketFilter(cached, filter) : cached;
|
|
1134
1156
|
}
|
|
1135
1157
|
}
|
|
1136
1158
|
/**
|
|
1137
|
-
* Get MarketParams for a collateral token.
|
|
1138
|
-
* 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)
|
|
1139
1167
|
*/
|
|
1140
|
-
async findMarketForCollateral(collateralSymbolOrAddress) {
|
|
1168
|
+
async findMarketForCollateral(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
|
|
1141
1169
|
let colAddr;
|
|
1142
1170
|
if (collateralSymbolOrAddress.startsWith("0x")) {
|
|
1143
1171
|
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
@@ -1149,13 +1177,33 @@ var MorphoClient = class {
|
|
|
1149
1177
|
colAddr = collateralSymbolOrAddress.toLowerCase();
|
|
1150
1178
|
}
|
|
1151
1179
|
}
|
|
1152
|
-
|
|
1153
|
-
if (
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
+
}
|
|
1157
1205
|
throw new AgetherError(
|
|
1158
|
-
`No Morpho market found for collateral ${collateralSymbolOrAddress}
|
|
1206
|
+
`No Morpho market found for collateral ${collateralSymbolOrAddress}` + (loanTokenSymbolOrAddress ? ` with loan token ${loanTokenSymbolOrAddress}` : ""),
|
|
1159
1207
|
"MARKET_NOT_FOUND"
|
|
1160
1208
|
);
|
|
1161
1209
|
}
|
|
@@ -1190,12 +1238,13 @@ var MorphoClient = class {
|
|
|
1190
1238
|
const acctAddr = await this.getAccountAddress();
|
|
1191
1239
|
const markets = await this.getMarkets();
|
|
1192
1240
|
const positions = [];
|
|
1193
|
-
let
|
|
1241
|
+
let totalDebtFloat = 0;
|
|
1194
1242
|
for (const m of markets) {
|
|
1195
1243
|
if (!m.collateralAsset || m.collateralAsset.address === ethers2.ZeroAddress) continue;
|
|
1196
1244
|
try {
|
|
1197
1245
|
const pos = await this.morphoBlue.position(m.uniqueKey, acctAddr);
|
|
1198
1246
|
if (pos.collateral === 0n && pos.borrowShares === 0n && pos.supplyShares === 0n) continue;
|
|
1247
|
+
const loanDecimals = m.loanAsset.decimals;
|
|
1199
1248
|
let debt = 0n;
|
|
1200
1249
|
if (pos.borrowShares > 0n) {
|
|
1201
1250
|
try {
|
|
@@ -1203,7 +1252,7 @@ var MorphoClient = class {
|
|
|
1203
1252
|
const totalBorrowShares = BigInt(mkt.totalBorrowShares);
|
|
1204
1253
|
const totalBorrowAssets = BigInt(mkt.totalBorrowAssets);
|
|
1205
1254
|
debt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1206
|
-
|
|
1255
|
+
totalDebtFloat += parseFloat(ethers2.formatUnits(debt, loanDecimals));
|
|
1207
1256
|
} catch (e) {
|
|
1208
1257
|
console.warn(`[agether] debt calc failed for market ${m.uniqueKey}:`, e instanceof Error ? e.message : e);
|
|
1209
1258
|
}
|
|
@@ -1211,10 +1260,11 @@ var MorphoClient = class {
|
|
|
1211
1260
|
positions.push({
|
|
1212
1261
|
marketId: m.uniqueKey,
|
|
1213
1262
|
collateralToken: m.collateralAsset.symbol,
|
|
1263
|
+
loanToken: m.loanAsset.symbol,
|
|
1214
1264
|
collateral: ethers2.formatUnits(pos.collateral, m.collateralAsset.decimals),
|
|
1215
1265
|
borrowShares: pos.borrowShares.toString(),
|
|
1216
1266
|
supplyShares: pos.supplyShares.toString(),
|
|
1217
|
-
debt: ethers2.formatUnits(debt,
|
|
1267
|
+
debt: ethers2.formatUnits(debt, loanDecimals)
|
|
1218
1268
|
});
|
|
1219
1269
|
} catch (e) {
|
|
1220
1270
|
console.warn(`[agether] position read failed for market:`, e instanceof Error ? e.message : e);
|
|
@@ -1224,16 +1274,28 @@ var MorphoClient = class {
|
|
|
1224
1274
|
return {
|
|
1225
1275
|
agentId: this.agentId,
|
|
1226
1276
|
agentAccount: acctAddr,
|
|
1227
|
-
totalDebt:
|
|
1277
|
+
totalDebt: totalDebtFloat.toFixed(6),
|
|
1228
1278
|
positions
|
|
1229
1279
|
};
|
|
1230
1280
|
}
|
|
1231
1281
|
// ════════════════════════════════════════════════════════
|
|
1232
1282
|
// Balance & Borrowing Capacity
|
|
1233
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
|
+
}
|
|
1234
1295
|
/**
|
|
1235
1296
|
* Get the USDC balance of the AgentAccount.
|
|
1236
1297
|
* @returns USDC balance in raw units (6 decimals)
|
|
1298
|
+
* @deprecated Use `getTokenBalance('USDC')` instead.
|
|
1237
1299
|
*/
|
|
1238
1300
|
async getUsdcBalance() {
|
|
1239
1301
|
const acctAddr = await this.getAccountAddress();
|
|
@@ -1241,7 +1303,7 @@ var MorphoClient = class {
|
|
|
1241
1303
|
return usdc.balanceOf(acctAddr);
|
|
1242
1304
|
}
|
|
1243
1305
|
/**
|
|
1244
|
-
* Calculate the maximum additional
|
|
1306
|
+
* Calculate the maximum additional loan token that can be borrowed
|
|
1245
1307
|
* given the agent's current collateral and debt across all markets.
|
|
1246
1308
|
*
|
|
1247
1309
|
* For each market with collateral deposited:
|
|
@@ -1249,7 +1311,7 @@ var MorphoClient = class {
|
|
|
1249
1311
|
*
|
|
1250
1312
|
* Uses the Morpho oracle to price collateral → loan token.
|
|
1251
1313
|
*
|
|
1252
|
-
* @returns Maximum additional
|
|
1314
|
+
* @returns Maximum additional borrowable per market (raw units in each market's loan token)
|
|
1253
1315
|
*/
|
|
1254
1316
|
async getMaxBorrowable() {
|
|
1255
1317
|
const acctAddr = await this.getAccountAddress();
|
|
@@ -1282,6 +1344,8 @@ var MorphoClient = class {
|
|
|
1282
1344
|
totalAdditional += maxAdditional;
|
|
1283
1345
|
byMarket.push({
|
|
1284
1346
|
collateralToken: m.collateralAsset.symbol,
|
|
1347
|
+
loanToken: m.loanAsset.symbol,
|
|
1348
|
+
loanDecimals: m.loanAsset.decimals,
|
|
1285
1349
|
maxAdditional,
|
|
1286
1350
|
currentDebt,
|
|
1287
1351
|
collateralValue: collateralValueInLoan
|
|
@@ -1297,14 +1361,16 @@ var MorphoClient = class {
|
|
|
1297
1361
|
// Market Rates & Yield Estimation
|
|
1298
1362
|
// ════════════════════════════════════════════════════════
|
|
1299
1363
|
/**
|
|
1300
|
-
* Fetch current supply/borrow APY for
|
|
1364
|
+
* Fetch current supply/borrow APY for markets from Morpho GraphQL API.
|
|
1301
1365
|
*
|
|
1302
1366
|
* Note: On Morpho Blue, collateral does NOT earn yield directly. Supply APY
|
|
1303
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.
|
|
1304
1371
|
*/
|
|
1305
|
-
async getMarketRates(collateralSymbolOrAddress) {
|
|
1372
|
+
async getMarketRates(collateralSymbolOrAddress, loanTokenSymbolOrAddress) {
|
|
1306
1373
|
const chainId = this.config.chainId;
|
|
1307
|
-
const usdcAddr = this.config.contracts.usdc.toLowerCase();
|
|
1308
1374
|
let collateralFilter = "";
|
|
1309
1375
|
if (collateralSymbolOrAddress) {
|
|
1310
1376
|
let colAddr;
|
|
@@ -1320,12 +1386,27 @@ var MorphoClient = class {
|
|
|
1320
1386
|
}
|
|
1321
1387
|
collateralFilter = `, collateralAssetAddress_in: ["${colAddr}"]`;
|
|
1322
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
|
+
}
|
|
1323
1404
|
const query = `{
|
|
1324
1405
|
markets(
|
|
1325
1406
|
first: 50
|
|
1326
1407
|
orderBy: SupplyAssetsUsd
|
|
1327
1408
|
orderDirection: Desc
|
|
1328
|
-
where: { chainId_in: [${chainId}]
|
|
1409
|
+
where: { chainId_in: [${chainId}]${loanFilter}${collateralFilter} }
|
|
1329
1410
|
) {
|
|
1330
1411
|
items {
|
|
1331
1412
|
uniqueKey
|
|
@@ -1345,17 +1426,21 @@ var MorphoClient = class {
|
|
|
1345
1426
|
try {
|
|
1346
1427
|
const resp = await axios.post(MORPHO_API_URL, { query }, { timeout: 1e4 });
|
|
1347
1428
|
const items = resp.data?.data?.markets?.items ?? [];
|
|
1348
|
-
return items.filter((m) => m.collateralAsset?.address && m.collateralAsset.address !== ethers2.ZeroAddress).map((m) =>
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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
|
+
});
|
|
1359
1444
|
} catch (e) {
|
|
1360
1445
|
console.warn("[agether] getMarketRates failed:", e instanceof Error ? e.message : e);
|
|
1361
1446
|
return [];
|
|
@@ -1389,14 +1474,15 @@ var MorphoClient = class {
|
|
|
1389
1474
|
} else {
|
|
1390
1475
|
try {
|
|
1391
1476
|
const params = await this.findMarketForCollateral(collateralSymbol);
|
|
1477
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1392
1478
|
const oracleContract = new Contract2(params.oracle, [
|
|
1393
1479
|
"function price() view returns (uint256)"
|
|
1394
1480
|
], this.provider);
|
|
1395
1481
|
const oraclePrice = await oracleContract.price();
|
|
1396
1482
|
const ORACLE_PRICE_SCALE = 10n ** 36n;
|
|
1397
1483
|
const amountWei = ethers2.parseUnits(amount, colInfo.decimals);
|
|
1398
|
-
const
|
|
1399
|
-
collateralValueUsd = Number(
|
|
1484
|
+
const valueInLoan = amountWei * oraclePrice / ORACLE_PRICE_SCALE;
|
|
1485
|
+
collateralValueUsd = Number(valueInLoan) / 10 ** loanDecimals;
|
|
1400
1486
|
} catch (e) {
|
|
1401
1487
|
console.warn("[agether] oracle price fetch for yield estimation failed:", e instanceof Error ? e.message : e);
|
|
1402
1488
|
throw new AgetherError("Cannot determine collateral value. Provide ethPriceUsd.", "PRICE_UNAVAILABLE");
|
|
@@ -1417,61 +1503,65 @@ var MorphoClient = class {
|
|
|
1417
1503
|
// Supply-Side (Lending) — earn yield by supplying USDC
|
|
1418
1504
|
// ════════════════════════════════════════════════════════
|
|
1419
1505
|
/**
|
|
1420
|
-
* Supply
|
|
1506
|
+
* Supply loan token to a Morpho Blue market as a lender (earn yield).
|
|
1421
1507
|
*
|
|
1422
1508
|
* Unlike `supplyCollateral` (borrower-side), this is the **lender-side**:
|
|
1423
|
-
* you deposit the loanToken
|
|
1509
|
+
* you deposit the loanToken into the market's supply pool and earn
|
|
1424
1510
|
* interest paid by borrowers.
|
|
1425
1511
|
*
|
|
1426
|
-
* @param
|
|
1512
|
+
* @param amount - Amount of loan token to supply (e.g. '500' for 500 USDC, '0.5' for 0.5 WETH)
|
|
1427
1513
|
* @param collateralSymbol - Market collateral token to identify which market (e.g. 'WETH')
|
|
1428
1514
|
* Optional — defaults to highest-APY market
|
|
1515
|
+
* @param loanTokenSymbol - Loan token to filter market (e.g. 'USDC', 'WETH'). Optional.
|
|
1429
1516
|
*/
|
|
1430
|
-
async supplyAsset(
|
|
1517
|
+
async supplyAsset(amount, collateralSymbol, loanTokenSymbol) {
|
|
1431
1518
|
const acctAddr = await this.getAccountAddress();
|
|
1432
|
-
const amount = ethers2.parseUnits(usdcAmount, 6);
|
|
1433
1519
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1434
|
-
const usdcAddr = this.config.contracts.usdc;
|
|
1435
1520
|
let params;
|
|
1436
1521
|
let usedCollateral;
|
|
1437
1522
|
if (collateralSymbol) {
|
|
1438
|
-
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1523
|
+
params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
|
|
1439
1524
|
usedCollateral = collateralSymbol;
|
|
1440
1525
|
} else {
|
|
1441
|
-
const rates = await this.getMarketRates();
|
|
1526
|
+
const rates = await this.getMarketRates(void 0, loanTokenSymbol);
|
|
1442
1527
|
if (rates.length === 0) throw new AgetherError("No markets available", "NO_MARKETS");
|
|
1443
1528
|
const best = rates.reduce((a, b) => a.supplyApy > b.supplyApy ? a : b);
|
|
1444
|
-
params = await this.findMarketForCollateral(best.collateralToken);
|
|
1529
|
+
params = await this.findMarketForCollateral(best.collateralToken, loanTokenSymbol);
|
|
1445
1530
|
usedCollateral = best.collateralToken;
|
|
1446
1531
|
}
|
|
1532
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1533
|
+
const loanTokenAddr = params.loanToken;
|
|
1534
|
+
const parsedAmount = ethers2.parseUnits(amount, loanDecimals);
|
|
1447
1535
|
const marketId = ethers2.keccak256(
|
|
1448
1536
|
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
1449
1537
|
["address", "address", "address", "address", "uint256"],
|
|
1450
1538
|
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
1451
1539
|
)
|
|
1452
1540
|
);
|
|
1453
|
-
const
|
|
1454
|
-
const acctBalance = await
|
|
1455
|
-
if (acctBalance <
|
|
1456
|
-
const shortfall =
|
|
1457
|
-
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());
|
|
1458
1546
|
if (eoaBalance < shortfall) {
|
|
1547
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
1548
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
1459
1549
|
throw new AgetherError(
|
|
1460
|
-
`Insufficient
|
|
1550
|
+
`Insufficient ${loanSymbol}. Need ${amount}, AgentAccount has ${ethers2.formatUnits(acctBalance, loanDecimals)}, EOA has ${ethers2.formatUnits(eoaBalance, loanDecimals)}.`,
|
|
1461
1551
|
"INSUFFICIENT_BALANCE"
|
|
1462
1552
|
);
|
|
1463
1553
|
}
|
|
1464
|
-
const transferTx = await
|
|
1554
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
1465
1555
|
await transferTx.wait();
|
|
1466
1556
|
this._refreshSigner();
|
|
1467
1557
|
}
|
|
1468
|
-
const targets = [
|
|
1558
|
+
const targets = [loanTokenAddr, morphoAddr];
|
|
1469
1559
|
const values = [0n, 0n];
|
|
1470
1560
|
const datas = [
|
|
1471
|
-
erc20Iface2.encodeFunctionData("approve", [morphoAddr,
|
|
1561
|
+
erc20Iface2.encodeFunctionData("approve", [morphoAddr, parsedAmount]),
|
|
1472
1562
|
morphoIface.encodeFunctionData("supply", [
|
|
1473
1563
|
this._toTuple(params),
|
|
1474
|
-
|
|
1564
|
+
parsedAmount,
|
|
1475
1565
|
0n,
|
|
1476
1566
|
acctAddr,
|
|
1477
1567
|
"0x"
|
|
@@ -1480,7 +1570,7 @@ var MorphoClient = class {
|
|
|
1480
1570
|
const receipt = await this.batch(targets, values, datas);
|
|
1481
1571
|
return {
|
|
1482
1572
|
tx: receipt.hash,
|
|
1483
|
-
amount
|
|
1573
|
+
amount,
|
|
1484
1574
|
marketId,
|
|
1485
1575
|
collateralToken: usedCollateral,
|
|
1486
1576
|
agentAccount: acctAddr
|
|
@@ -1493,17 +1583,26 @@ var MorphoClient = class {
|
|
|
1493
1583
|
* @param collateralSymbol - Market collateral to identify which market
|
|
1494
1584
|
* @param receiver - Destination address (defaults to EOA)
|
|
1495
1585
|
*/
|
|
1496
|
-
|
|
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) {
|
|
1497
1595
|
const acctAddr = await this.getAccountAddress();
|
|
1498
1596
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1499
1597
|
const dest = receiver || await this.getSignerAddress();
|
|
1500
1598
|
let params;
|
|
1501
1599
|
if (collateralSymbol) {
|
|
1502
|
-
params = await this.findMarketForCollateral(collateralSymbol);
|
|
1600
|
+
params = await this.findMarketForCollateral(collateralSymbol, loanTokenSymbol);
|
|
1503
1601
|
} else {
|
|
1504
1602
|
const { params: p } = await this._findActiveSupplyMarket();
|
|
1505
1603
|
params = p;
|
|
1506
1604
|
}
|
|
1605
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1507
1606
|
const marketId = ethers2.keccak256(
|
|
1508
1607
|
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
1509
1608
|
["address", "address", "address", "address", "uint256"],
|
|
@@ -1512,13 +1611,13 @@ var MorphoClient = class {
|
|
|
1512
1611
|
);
|
|
1513
1612
|
let withdrawAssets;
|
|
1514
1613
|
let withdrawShares;
|
|
1515
|
-
if (
|
|
1614
|
+
if (amount === "all") {
|
|
1516
1615
|
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
1517
1616
|
withdrawShares = BigInt(pos.supplyShares);
|
|
1518
1617
|
withdrawAssets = 0n;
|
|
1519
1618
|
if (withdrawShares === 0n) throw new AgetherError("No supply position to withdraw", "NO_SUPPLY");
|
|
1520
1619
|
} else {
|
|
1521
|
-
withdrawAssets = ethers2.parseUnits(
|
|
1620
|
+
withdrawAssets = ethers2.parseUnits(amount, loanDecimals);
|
|
1522
1621
|
withdrawShares = 0n;
|
|
1523
1622
|
}
|
|
1524
1623
|
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
@@ -1536,13 +1635,13 @@ var MorphoClient = class {
|
|
|
1536
1635
|
const totalSupplyAssets = BigInt(mkt.totalSupplyAssets);
|
|
1537
1636
|
const totalSupplyShares = BigInt(mkt.totalSupplyShares);
|
|
1538
1637
|
const currentAssets = totalSupplyShares > 0n ? BigInt(pos.supplyShares) * totalSupplyAssets / totalSupplyShares : 0n;
|
|
1539
|
-
remainingSupply = ethers2.formatUnits(currentAssets,
|
|
1638
|
+
remainingSupply = ethers2.formatUnits(currentAssets, loanDecimals);
|
|
1540
1639
|
} catch (e) {
|
|
1541
1640
|
console.warn("[agether] failed to read remaining supply:", e instanceof Error ? e.message : e);
|
|
1542
1641
|
}
|
|
1543
1642
|
return {
|
|
1544
1643
|
tx: receipt.hash,
|
|
1545
|
-
amount
|
|
1644
|
+
amount,
|
|
1546
1645
|
remainingSupply,
|
|
1547
1646
|
destination: dest
|
|
1548
1647
|
};
|
|
@@ -1615,14 +1714,13 @@ var MorphoClient = class {
|
|
|
1615
1714
|
* Computes available yield, verifies the requested amount doesn't exceed it,
|
|
1616
1715
|
* then withdraws from the supply position and sends directly to the recipient.
|
|
1617
1716
|
*
|
|
1618
|
-
* @param recipient - Address to receive the
|
|
1619
|
-
* @param
|
|
1717
|
+
* @param recipient - Address to receive the loan token
|
|
1718
|
+
* @param amount - Amount to pay from yield (e.g. '5.50')
|
|
1620
1719
|
* @param collateralSymbol - Market collateral to identify which supply position
|
|
1621
1720
|
*/
|
|
1622
|
-
async payFromYield(recipient,
|
|
1721
|
+
async payFromYield(recipient, amount, collateralSymbol) {
|
|
1623
1722
|
const acctAddr = await this.getAccountAddress();
|
|
1624
1723
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1625
|
-
const amount = ethers2.parseUnits(usdcAmount, 6);
|
|
1626
1724
|
const positions = await this.getSupplyPositions(collateralSymbol);
|
|
1627
1725
|
if (positions.length === 0) {
|
|
1628
1726
|
throw new AgetherError("No supply position found", "NO_SUPPLY");
|
|
@@ -1630,17 +1728,20 @@ var MorphoClient = class {
|
|
|
1630
1728
|
const pos = positions.reduce(
|
|
1631
1729
|
(a, b) => parseFloat(a.earnedYield) > parseFloat(b.earnedYield) ? a : b
|
|
1632
1730
|
);
|
|
1633
|
-
const
|
|
1634
|
-
|
|
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;
|
|
1635
1737
|
throw new AgetherError(
|
|
1636
|
-
`Requested ${
|
|
1738
|
+
`Requested ${amount} ${loanSymbol} exceeds available yield of ${pos.earnedYield} ${loanSymbol}. Use withdrawSupply to withdraw principal.`,
|
|
1637
1739
|
"EXCEEDS_YIELD"
|
|
1638
1740
|
);
|
|
1639
1741
|
}
|
|
1640
|
-
const params = await this.findMarketForCollateral(pos.collateralToken);
|
|
1641
1742
|
const data = morphoIface.encodeFunctionData("withdraw", [
|
|
1642
1743
|
this._toTuple(params),
|
|
1643
|
-
|
|
1744
|
+
parsedAmount,
|
|
1644
1745
|
0n,
|
|
1645
1746
|
acctAddr,
|
|
1646
1747
|
recipient
|
|
@@ -1659,7 +1760,7 @@ var MorphoClient = class {
|
|
|
1659
1760
|
}
|
|
1660
1761
|
return {
|
|
1661
1762
|
tx: receipt.hash,
|
|
1662
|
-
yieldWithdrawn:
|
|
1763
|
+
yieldWithdrawn: amount,
|
|
1663
1764
|
recipient,
|
|
1664
1765
|
remainingYield,
|
|
1665
1766
|
remainingSupply
|
|
@@ -1717,28 +1818,31 @@ var MorphoClient = class {
|
|
|
1717
1818
|
};
|
|
1718
1819
|
}
|
|
1719
1820
|
/**
|
|
1720
|
-
* Borrow
|
|
1821
|
+
* Borrow loan token against existing collateral.
|
|
1721
1822
|
*
|
|
1722
1823
|
* AgentAccount.execute: Morpho.borrow(params, amount, 0, account, account)
|
|
1723
1824
|
*
|
|
1724
|
-
* @param
|
|
1825
|
+
* @param amount - Loan token amount (e.g. '100' for 100 USDC, '0.5' for 0.5 WETH)
|
|
1725
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')
|
|
1726
1829
|
*/
|
|
1727
|
-
async borrow(
|
|
1830
|
+
async borrow(amount, tokenSymbol, marketParams, loanTokenSymbol) {
|
|
1728
1831
|
const acctAddr = await this.getAccountAddress();
|
|
1729
|
-
const amount = ethers2.parseUnits(usdcAmount, 6);
|
|
1730
1832
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1731
1833
|
let params;
|
|
1732
1834
|
let usedToken = tokenSymbol || "WETH";
|
|
1733
1835
|
if (marketParams) {
|
|
1734
1836
|
params = marketParams;
|
|
1735
1837
|
} else if (tokenSymbol) {
|
|
1736
|
-
params = await this.findMarketForCollateral(tokenSymbol);
|
|
1838
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1737
1839
|
} else {
|
|
1738
1840
|
const { params: p, symbol } = await this._findActiveMarket();
|
|
1739
1841
|
params = p;
|
|
1740
1842
|
usedToken = symbol;
|
|
1741
1843
|
}
|
|
1844
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1845
|
+
const parsedAmount = ethers2.parseUnits(amount, loanDecimals);
|
|
1742
1846
|
try {
|
|
1743
1847
|
const marketId = ethers2.keccak256(
|
|
1744
1848
|
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
@@ -1762,11 +1866,14 @@ var MorphoClient = class {
|
|
|
1762
1866
|
const totalBorrowAssets = BigInt(mktState.totalBorrowAssets);
|
|
1763
1867
|
const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1764
1868
|
const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
|
|
1765
|
-
if (
|
|
1766
|
-
const
|
|
1767
|
-
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);
|
|
1768
1875
|
throw new AgetherError(
|
|
1769
|
-
`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.`,
|
|
1770
1877
|
"EXCEEDS_MAX_LTV"
|
|
1771
1878
|
);
|
|
1772
1879
|
}
|
|
@@ -1776,7 +1883,7 @@ var MorphoClient = class {
|
|
|
1776
1883
|
}
|
|
1777
1884
|
const data = morphoIface.encodeFunctionData("borrow", [
|
|
1778
1885
|
this._toTuple(params),
|
|
1779
|
-
|
|
1886
|
+
parsedAmount,
|
|
1780
1887
|
0n,
|
|
1781
1888
|
acctAddr,
|
|
1782
1889
|
acctAddr
|
|
@@ -1784,7 +1891,7 @@ var MorphoClient = class {
|
|
|
1784
1891
|
const receipt = await this.exec(morphoAddr, data);
|
|
1785
1892
|
return {
|
|
1786
1893
|
tx: receipt.hash,
|
|
1787
|
-
amount
|
|
1894
|
+
amount,
|
|
1788
1895
|
collateralToken: usedToken,
|
|
1789
1896
|
agentAccount: acctAddr
|
|
1790
1897
|
};
|
|
@@ -1792,17 +1899,27 @@ var MorphoClient = class {
|
|
|
1792
1899
|
/**
|
|
1793
1900
|
* Deposit collateral AND borrow USDC in one batched transaction.
|
|
1794
1901
|
*
|
|
1902
|
+
/**
|
|
1903
|
+
* Deposit collateral AND borrow loan token in one batched transaction.
|
|
1904
|
+
*
|
|
1795
1905
|
* AgentAccount.executeBatch:
|
|
1796
1906
|
* [collateral.approve, Morpho.supplyCollateral, Morpho.borrow]
|
|
1797
1907
|
*
|
|
1798
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)
|
|
1799
1915
|
*/
|
|
1800
|
-
async depositAndBorrow(tokenSymbol, collateralAmount,
|
|
1916
|
+
async depositAndBorrow(tokenSymbol, collateralAmount, borrowAmount, marketParams, loanTokenSymbol) {
|
|
1801
1917
|
const acctAddr = await this.getAccountAddress();
|
|
1802
1918
|
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1803
|
-
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1919
|
+
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1920
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1804
1921
|
const colWei = ethers2.parseUnits(collateralAmount, colInfo.decimals);
|
|
1805
|
-
const borrowWei = ethers2.parseUnits(
|
|
1922
|
+
const borrowWei = ethers2.parseUnits(borrowAmount, loanDecimals);
|
|
1806
1923
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1807
1924
|
try {
|
|
1808
1925
|
const marketId = ethers2.keccak256(
|
|
@@ -1823,9 +1940,11 @@ var MorphoClient = class {
|
|
|
1823
1940
|
const currentDebt = totalBorrowShares > 0n ? (BigInt(pos.borrowShares) * totalBorrowAssets + totalBorrowShares - 1n) / totalBorrowShares : 0n;
|
|
1824
1941
|
const maxAdditional = maxBorrowTotal > currentDebt ? maxBorrowTotal - currentDebt : 0n;
|
|
1825
1942
|
if (borrowWei > maxAdditional) {
|
|
1826
|
-
const
|
|
1943
|
+
const loanInfo = this._tokenCache.get(params.loanToken.toLowerCase());
|
|
1944
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
1945
|
+
const maxFormatted = ethers2.formatUnits(maxAdditional, loanDecimals);
|
|
1827
1946
|
throw new AgetherError(
|
|
1828
|
-
`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.`,
|
|
1829
1948
|
"EXCEEDS_MAX_LTV"
|
|
1830
1949
|
);
|
|
1831
1950
|
}
|
|
@@ -1871,36 +1990,42 @@ var MorphoClient = class {
|
|
|
1871
1990
|
tx: receipt.hash,
|
|
1872
1991
|
collateralToken: tokenSymbol,
|
|
1873
1992
|
collateralAmount,
|
|
1874
|
-
borrowAmount
|
|
1993
|
+
borrowAmount,
|
|
1875
1994
|
agentAccount: acctAddr
|
|
1876
1995
|
};
|
|
1877
1996
|
}
|
|
1878
1997
|
/**
|
|
1879
|
-
* Repay borrowed
|
|
1998
|
+
* Repay borrowed loan token from AgentAccount.
|
|
1880
1999
|
*
|
|
1881
2000
|
* AgentAccount.executeBatch:
|
|
1882
|
-
* [
|
|
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)
|
|
1883
2007
|
*/
|
|
1884
|
-
async repay(
|
|
2008
|
+
async repay(amount, tokenSymbol, marketParams, loanTokenSymbol) {
|
|
1885
2009
|
const acctAddr = await this.getAccountAddress();
|
|
1886
2010
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1887
|
-
const usdcAddr = this.config.contracts.usdc;
|
|
1888
2011
|
let params;
|
|
1889
2012
|
if (marketParams) {
|
|
1890
2013
|
params = marketParams;
|
|
1891
2014
|
} else if (tokenSymbol) {
|
|
1892
|
-
params = await this.findMarketForCollateral(tokenSymbol);
|
|
2015
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
1893
2016
|
} else {
|
|
1894
2017
|
const { params: p } = await this._findActiveMarket();
|
|
1895
2018
|
params = p;
|
|
1896
2019
|
}
|
|
2020
|
+
const loanTokenAddr = params.loanToken;
|
|
2021
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1897
2022
|
let repayAssets;
|
|
1898
2023
|
let repayShares;
|
|
1899
2024
|
let approveAmount;
|
|
1900
|
-
if (
|
|
2025
|
+
if (amount === "all") {
|
|
1901
2026
|
const markets = await this.getMarkets();
|
|
1902
2027
|
const mkt = markets.find(
|
|
1903
|
-
(m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase()
|
|
2028
|
+
(m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase() && m.loanAsset?.address.toLowerCase() === params.loanToken.toLowerCase()
|
|
1904
2029
|
);
|
|
1905
2030
|
if (mkt) {
|
|
1906
2031
|
const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
|
|
@@ -1910,33 +2035,35 @@ var MorphoClient = class {
|
|
|
1910
2035
|
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
1911
2036
|
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
1912
2037
|
const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
|
|
1913
|
-
approveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1",
|
|
2038
|
+
approveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1", loanDecimals);
|
|
1914
2039
|
} else {
|
|
1915
|
-
repayAssets = ethers2.parseUnits("999999",
|
|
2040
|
+
repayAssets = ethers2.parseUnits("999999", loanDecimals);
|
|
1916
2041
|
repayShares = 0n;
|
|
1917
2042
|
approveAmount = repayAssets;
|
|
1918
2043
|
}
|
|
1919
2044
|
} else {
|
|
1920
|
-
repayAssets = ethers2.parseUnits(
|
|
2045
|
+
repayAssets = ethers2.parseUnits(amount, loanDecimals);
|
|
1921
2046
|
repayShares = 0n;
|
|
1922
2047
|
approveAmount = repayAssets;
|
|
1923
2048
|
}
|
|
1924
|
-
const
|
|
1925
|
-
const acctBalance = await
|
|
2049
|
+
const loanContract = new Contract2(loanTokenAddr, ERC20_ABI, this._signer);
|
|
2050
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
1926
2051
|
if (acctBalance < approveAmount) {
|
|
1927
2052
|
const shortfall = approveAmount - acctBalance;
|
|
1928
|
-
const eoaBalance = await
|
|
2053
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
1929
2054
|
if (eoaBalance < shortfall) {
|
|
2055
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
2056
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
1930
2057
|
throw new AgetherError(
|
|
1931
|
-
`Insufficient
|
|
2058
|
+
`Insufficient ${loanSymbol} for repay. Need ${ethers2.formatUnits(approveAmount, loanDecimals)}, AgentAccount has ${ethers2.formatUnits(acctBalance, loanDecimals)}, EOA has ${ethers2.formatUnits(eoaBalance, loanDecimals)}.`,
|
|
1932
2059
|
"INSUFFICIENT_BALANCE"
|
|
1933
2060
|
);
|
|
1934
2061
|
}
|
|
1935
|
-
const transferTx = await
|
|
2062
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
1936
2063
|
await transferTx.wait();
|
|
1937
2064
|
this._refreshSigner();
|
|
1938
2065
|
}
|
|
1939
|
-
const targets = [
|
|
2066
|
+
const targets = [loanTokenAddr, morphoAddr];
|
|
1940
2067
|
const values = [0n, 0n];
|
|
1941
2068
|
const datas = [
|
|
1942
2069
|
erc20Iface2.encodeFunctionData("approve", [morphoAddr, approveAmount]),
|
|
@@ -1956,7 +2083,7 @@ var MorphoClient = class {
|
|
|
1956
2083
|
} catch (e) {
|
|
1957
2084
|
console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
|
|
1958
2085
|
}
|
|
1959
|
-
return { tx: receipt.hash, amount
|
|
2086
|
+
return { tx: receipt.hash, amount, remainingDebt };
|
|
1960
2087
|
}
|
|
1961
2088
|
/**
|
|
1962
2089
|
* Withdraw collateral from Morpho Blue.
|
|
@@ -1970,12 +2097,13 @@ var MorphoClient = class {
|
|
|
1970
2097
|
const colInfo = await this._resolveToken(tokenSymbol);
|
|
1971
2098
|
const params = marketParams ?? await this.findMarketForCollateral(tokenSymbol);
|
|
1972
2099
|
const morphoAddr = this.config.contracts.morphoBlue;
|
|
1973
|
-
const
|
|
2100
|
+
const loanTokenAddr = params.loanToken;
|
|
2101
|
+
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1974
2102
|
const dest = receiver || await this.getSignerAddress();
|
|
1975
2103
|
let weiAmount;
|
|
1976
2104
|
const markets = await this.getMarkets();
|
|
1977
2105
|
const market = markets.find(
|
|
1978
|
-
(m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase()
|
|
2106
|
+
(m) => m.collateralAsset?.address.toLowerCase() === colInfo.address.toLowerCase() && m.loanAsset?.address.toLowerCase() === loanTokenAddr.toLowerCase()
|
|
1979
2107
|
);
|
|
1980
2108
|
if (amount === "all") {
|
|
1981
2109
|
if (!market) throw new AgetherError("Market not found", "MARKET_NOT_FOUND");
|
|
@@ -1998,8 +2126,10 @@ var MorphoClient = class {
|
|
|
1998
2126
|
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
1999
2127
|
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
2000
2128
|
const estimated = totalBorrowShares > 0n ? dustBorrowShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
|
|
2001
|
-
dustApproveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1",
|
|
2002
|
-
|
|
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`);
|
|
2003
2133
|
}
|
|
2004
2134
|
} catch (e) {
|
|
2005
2135
|
console.warn("[agether] failed to check borrow shares before withdraw:", e instanceof Error ? e.message : e);
|
|
@@ -2013,19 +2143,21 @@ var MorphoClient = class {
|
|
|
2013
2143
|
]);
|
|
2014
2144
|
let receipt;
|
|
2015
2145
|
if (hasDustDebt) {
|
|
2016
|
-
const
|
|
2017
|
-
const acctBalance = await
|
|
2146
|
+
const loanContract = new Contract2(loanTokenAddr, ERC20_ABI, this._signer);
|
|
2147
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
2018
2148
|
if (acctBalance < dustApproveAmount) {
|
|
2019
2149
|
const shortfall = dustApproveAmount - acctBalance;
|
|
2020
|
-
const eoaBalance = await
|
|
2150
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
2021
2151
|
if (eoaBalance >= shortfall) {
|
|
2022
|
-
|
|
2023
|
-
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);
|
|
2024
2156
|
await transferTx.wait();
|
|
2025
2157
|
this._refreshSigner();
|
|
2026
2158
|
}
|
|
2027
2159
|
}
|
|
2028
|
-
const targets = [
|
|
2160
|
+
const targets = [loanTokenAddr, morphoAddr, morphoAddr];
|
|
2029
2161
|
const values = [0n, 0n, 0n];
|
|
2030
2162
|
const datas = [
|
|
2031
2163
|
erc20Iface2.encodeFunctionData("approve", [morphoAddr, dustApproveAmount]),
|
|
@@ -2270,6 +2402,37 @@ var MorphoClient = class {
|
|
|
2270
2402
|
}
|
|
2271
2403
|
throw new AgetherError("No active supply position found", "NO_SUPPLY");
|
|
2272
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
|
+
}
|
|
2273
2436
|
/**
|
|
2274
2437
|
* Resolve a token symbol or address to { address, symbol, decimals }.
|
|
2275
2438
|
*
|
|
@@ -2286,8 +2449,8 @@ var MorphoClient = class {
|
|
|
2286
2449
|
const fromApi = this._tokenCache.get(key);
|
|
2287
2450
|
if (fromApi) return fromApi;
|
|
2288
2451
|
throw new AgetherError(
|
|
2289
|
-
`Unknown token: ${symbolOrAddress}. No Morpho market found with this
|
|
2290
|
-
"
|
|
2452
|
+
`Unknown token: ${symbolOrAddress}. No Morpho market found with this token.`,
|
|
2453
|
+
"UNKNOWN_TOKEN"
|
|
2291
2454
|
);
|
|
2292
2455
|
}
|
|
2293
2456
|
/**
|