@agether/sdk 2.18.0 → 2.18.1
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 +80 -2
- package/dist/index.d.mts +72 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +290 -2
- package/dist/index.mjs +290 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1338,7 +1338,7 @@ var init_MorphoClient = __esm({
|
|
|
1338
1338
|
} else {
|
|
1339
1339
|
try {
|
|
1340
1340
|
const params = await this.findMarketForCollateral(collateralSymbol);
|
|
1341
|
-
const loanDecimals =
|
|
1341
|
+
const loanDecimals = market.loanDecimals;
|
|
1342
1342
|
const oracleContract = new import_ethers2.Contract(params.oracle, [
|
|
1343
1343
|
"function price() view returns (uint256)"
|
|
1344
1344
|
], this.provider);
|
|
@@ -1881,8 +1881,8 @@ var init_MorphoClient = __esm({
|
|
|
1881
1881
|
const { params: p } = await this._findActiveMarket();
|
|
1882
1882
|
params = p;
|
|
1883
1883
|
}
|
|
1884
|
-
const loanTokenAddr = params.loanToken;
|
|
1885
1884
|
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
1885
|
+
const loanTokenAddr = params.loanToken;
|
|
1886
1886
|
let repayAssets;
|
|
1887
1887
|
let repayShares;
|
|
1888
1888
|
let approveAmount;
|
|
@@ -2260,6 +2260,84 @@ var init_MorphoClient = __esm({
|
|
|
2260
2260
|
// ────────────────────────────────────────────────────────────
|
|
2261
2261
|
// ERC-4337 UserOp helpers (Safe + Safe7579 + EntryPoint v0.7)
|
|
2262
2262
|
// ────────────────────────────────────────────────────────────
|
|
2263
|
+
/**
|
|
2264
|
+
* Repay all debt and withdraw all collateral in one atomic batch.
|
|
2265
|
+
* Full position exit: approve + repay(shares) + withdrawCollateral → 1 UserOp.
|
|
2266
|
+
*/
|
|
2267
|
+
async repayAndWithdraw(tokenSymbol, loanTokenSymbol, toEoa = true) {
|
|
2268
|
+
const acctAddr = await this.getAccountAddress();
|
|
2269
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
2270
|
+
let params;
|
|
2271
|
+
let colSymbol;
|
|
2272
|
+
if (tokenSymbol) {
|
|
2273
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
2274
|
+
colSymbol = tokenSymbol;
|
|
2275
|
+
} else {
|
|
2276
|
+
const active = await this._findActiveMarket();
|
|
2277
|
+
params = active.params;
|
|
2278
|
+
colSymbol = active.symbol;
|
|
2279
|
+
}
|
|
2280
|
+
const colInfo = await this._resolveToken(colSymbol);
|
|
2281
|
+
const dest = toEoa ? await this.getSignerAddress() : acctAddr;
|
|
2282
|
+
const marketId = import_ethers2.ethers.keccak256(import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
2283
|
+
["address", "address", "address", "address", "uint256"],
|
|
2284
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
2285
|
+
));
|
|
2286
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
2287
|
+
const borrowShares = BigInt(pos.borrowShares);
|
|
2288
|
+
const collateralAmount = BigInt(pos.collateral);
|
|
2289
|
+
if (collateralAmount === 0n) {
|
|
2290
|
+
throw new AgetherError("No collateral to withdraw", "NO_COLLATERAL");
|
|
2291
|
+
}
|
|
2292
|
+
const targets = [];
|
|
2293
|
+
const values = [];
|
|
2294
|
+
const datas = [];
|
|
2295
|
+
if (borrowShares > 0n) {
|
|
2296
|
+
const onChainMkt = await this.morphoBlue.market(marketId);
|
|
2297
|
+
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
2298
|
+
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
2299
|
+
const estimatedDebt = totalBorrowShares > 0n ? borrowShares * totalBorrowAssets / totalBorrowShares + 100n : 0n;
|
|
2300
|
+
const loanContract = new import_ethers2.Contract(params.loanToken, ERC20_ABI, this._signer);
|
|
2301
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
2302
|
+
if (acctBalance < estimatedDebt) {
|
|
2303
|
+
const shortfall = estimatedDebt - acctBalance;
|
|
2304
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
2305
|
+
if (eoaBalance >= shortfall) {
|
|
2306
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
2307
|
+
await transferTx.wait();
|
|
2308
|
+
this._refreshSigner();
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
targets.push(params.loanToken);
|
|
2312
|
+
values.push(0n);
|
|
2313
|
+
datas.push(erc20Iface.encodeFunctionData("approve", [morphoAddr, estimatedDebt]));
|
|
2314
|
+
targets.push(morphoAddr);
|
|
2315
|
+
values.push(0n);
|
|
2316
|
+
datas.push(morphoIface.encodeFunctionData("repay", [
|
|
2317
|
+
this._toTuple(params),
|
|
2318
|
+
0n,
|
|
2319
|
+
borrowShares,
|
|
2320
|
+
acctAddr,
|
|
2321
|
+
"0x"
|
|
2322
|
+
]));
|
|
2323
|
+
}
|
|
2324
|
+
targets.push(morphoAddr);
|
|
2325
|
+
values.push(0n);
|
|
2326
|
+
datas.push(morphoIface.encodeFunctionData("withdrawCollateral", [
|
|
2327
|
+
this._toTuple(params),
|
|
2328
|
+
collateralAmount,
|
|
2329
|
+
acctAddr,
|
|
2330
|
+
dest
|
|
2331
|
+
]));
|
|
2332
|
+
const receipt = await this.executeBatch(targets, values, datas);
|
|
2333
|
+
return {
|
|
2334
|
+
tx: receipt.hash,
|
|
2335
|
+
repaid: borrowShares > 0n ? "all" : "0",
|
|
2336
|
+
withdrawn: import_ethers2.ethers.formatUnits(collateralAmount, colInfo.decimals),
|
|
2337
|
+
collateralToken: colSymbol,
|
|
2338
|
+
loanToken: this._tokenCache.get(params.loanToken.toLowerCase())?.symbol ?? "unknown"
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2263
2341
|
/** Convert MorphoMarketParams to Solidity tuple. */
|
|
2264
2342
|
_toTuple(p) {
|
|
2265
2343
|
return [p.loanToken, p.collateralToken, p.oracle, p.irm, p.lltv];
|
package/dist/index.d.mts
CHANGED
|
@@ -1002,6 +1002,17 @@ declare class MorphoClient extends AgentAccountClient {
|
|
|
1002
1002
|
* responsibility.
|
|
1003
1003
|
*/
|
|
1004
1004
|
private _refreshSigner;
|
|
1005
|
+
/**
|
|
1006
|
+
* Repay all debt and withdraw all collateral in one atomic batch.
|
|
1007
|
+
* Full position exit: approve + repay(shares) + withdrawCollateral → 1 UserOp.
|
|
1008
|
+
*/
|
|
1009
|
+
repayAndWithdraw(tokenSymbol?: string, loanTokenSymbol?: string, toEoa?: boolean): Promise<{
|
|
1010
|
+
tx: string;
|
|
1011
|
+
repaid: string;
|
|
1012
|
+
withdrawn: string;
|
|
1013
|
+
collateralToken: string;
|
|
1014
|
+
loanToken: string;
|
|
1015
|
+
}>;
|
|
1005
1016
|
/** Convert MorphoMarketParams to Solidity tuple. */
|
|
1006
1017
|
private _toTuple;
|
|
1007
1018
|
/** Find the first market where the agent has collateral deposited. */
|
|
@@ -1128,6 +1139,67 @@ declare class AaveClient extends AgentAccountClient {
|
|
|
1128
1139
|
* Supply an asset and borrow in one atomic batch operation.
|
|
1129
1140
|
*/
|
|
1130
1141
|
supplyAndBorrow(supplyAsset: string, supplyAmount: string, borrowAsset: string, borrowAmount: string): Promise<ethers.TransactionReceipt>;
|
|
1142
|
+
/**
|
|
1143
|
+
* Repay all debt and withdraw all collateral in one atomic batch.
|
|
1144
|
+
* Full position exit: approve + repay + withdraw → 1 UserOp.
|
|
1145
|
+
*/
|
|
1146
|
+
repayAndWithdraw(repayAsset: string, withdrawAsset: string, to?: string): Promise<ethers.TransactionReceipt>;
|
|
1147
|
+
/**
|
|
1148
|
+
* Search reserves by token symbol (case-insensitive partial match).
|
|
1149
|
+
*/
|
|
1150
|
+
searchReserves(query: string): Promise<AaveReserveInfo[]>;
|
|
1151
|
+
/**
|
|
1152
|
+
* Get borrowing options: which tokens can be borrowed, with what collateral.
|
|
1153
|
+
* If collateral symbol given, shows what you can borrow using that as collateral.
|
|
1154
|
+
*/
|
|
1155
|
+
getBorrowingOptions(collateral?: string): Promise<Array<{
|
|
1156
|
+
collateral: string;
|
|
1157
|
+
borrowable: AaveReserveInfo[];
|
|
1158
|
+
collateralBalance: number;
|
|
1159
|
+
collateralValueUsd: number;
|
|
1160
|
+
maxBorrowUsd: number;
|
|
1161
|
+
}>>;
|
|
1162
|
+
/**
|
|
1163
|
+
* Calculate max additional borrowable in USD given current positions.
|
|
1164
|
+
*/
|
|
1165
|
+
getMaxBorrowable(): Promise<{
|
|
1166
|
+
availableBorrowUsd: number;
|
|
1167
|
+
currentLtv: number;
|
|
1168
|
+
liquidationThreshold: number;
|
|
1169
|
+
totalCollateralUsd: number;
|
|
1170
|
+
totalDebtUsd: number;
|
|
1171
|
+
}>;
|
|
1172
|
+
/**
|
|
1173
|
+
* Scan wallet for tokens that exist as Aave reserves.
|
|
1174
|
+
*/
|
|
1175
|
+
getWalletTokens(): Promise<Array<{
|
|
1176
|
+
symbol: string;
|
|
1177
|
+
address: string;
|
|
1178
|
+
eoaBalance: number;
|
|
1179
|
+
safeBalance: number;
|
|
1180
|
+
priceUsd: number;
|
|
1181
|
+
valueUsd: number;
|
|
1182
|
+
canSupply: boolean;
|
|
1183
|
+
canBorrow: boolean;
|
|
1184
|
+
}>>;
|
|
1185
|
+
/**
|
|
1186
|
+
* Get yield spread for LST carry trades on Aave.
|
|
1187
|
+
* Compares LST native yield vs Aave borrow rates.
|
|
1188
|
+
*/
|
|
1189
|
+
getYieldSpread(_minLiquidity?: number): Promise<Array<{
|
|
1190
|
+
collateralToken: string;
|
|
1191
|
+
loanToken: string;
|
|
1192
|
+
collateralYield: number;
|
|
1193
|
+
borrowRate: number;
|
|
1194
|
+
netSpread: number;
|
|
1195
|
+
profitable: boolean;
|
|
1196
|
+
collateralLtv: number;
|
|
1197
|
+
maxSafeLeverage: number;
|
|
1198
|
+
}>>;
|
|
1199
|
+
private static readonly LST_PROJECT_MAP;
|
|
1200
|
+
private static _lstYieldCache;
|
|
1201
|
+
private static readonly LST_CACHE_TTL;
|
|
1202
|
+
private _getLstYields;
|
|
1131
1203
|
private _resolveReserve;
|
|
1132
1204
|
}
|
|
1133
1205
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1002,6 +1002,17 @@ declare class MorphoClient extends AgentAccountClient {
|
|
|
1002
1002
|
* responsibility.
|
|
1003
1003
|
*/
|
|
1004
1004
|
private _refreshSigner;
|
|
1005
|
+
/**
|
|
1006
|
+
* Repay all debt and withdraw all collateral in one atomic batch.
|
|
1007
|
+
* Full position exit: approve + repay(shares) + withdrawCollateral → 1 UserOp.
|
|
1008
|
+
*/
|
|
1009
|
+
repayAndWithdraw(tokenSymbol?: string, loanTokenSymbol?: string, toEoa?: boolean): Promise<{
|
|
1010
|
+
tx: string;
|
|
1011
|
+
repaid: string;
|
|
1012
|
+
withdrawn: string;
|
|
1013
|
+
collateralToken: string;
|
|
1014
|
+
loanToken: string;
|
|
1015
|
+
}>;
|
|
1005
1016
|
/** Convert MorphoMarketParams to Solidity tuple. */
|
|
1006
1017
|
private _toTuple;
|
|
1007
1018
|
/** Find the first market where the agent has collateral deposited. */
|
|
@@ -1128,6 +1139,67 @@ declare class AaveClient extends AgentAccountClient {
|
|
|
1128
1139
|
* Supply an asset and borrow in one atomic batch operation.
|
|
1129
1140
|
*/
|
|
1130
1141
|
supplyAndBorrow(supplyAsset: string, supplyAmount: string, borrowAsset: string, borrowAmount: string): Promise<ethers.TransactionReceipt>;
|
|
1142
|
+
/**
|
|
1143
|
+
* Repay all debt and withdraw all collateral in one atomic batch.
|
|
1144
|
+
* Full position exit: approve + repay + withdraw → 1 UserOp.
|
|
1145
|
+
*/
|
|
1146
|
+
repayAndWithdraw(repayAsset: string, withdrawAsset: string, to?: string): Promise<ethers.TransactionReceipt>;
|
|
1147
|
+
/**
|
|
1148
|
+
* Search reserves by token symbol (case-insensitive partial match).
|
|
1149
|
+
*/
|
|
1150
|
+
searchReserves(query: string): Promise<AaveReserveInfo[]>;
|
|
1151
|
+
/**
|
|
1152
|
+
* Get borrowing options: which tokens can be borrowed, with what collateral.
|
|
1153
|
+
* If collateral symbol given, shows what you can borrow using that as collateral.
|
|
1154
|
+
*/
|
|
1155
|
+
getBorrowingOptions(collateral?: string): Promise<Array<{
|
|
1156
|
+
collateral: string;
|
|
1157
|
+
borrowable: AaveReserveInfo[];
|
|
1158
|
+
collateralBalance: number;
|
|
1159
|
+
collateralValueUsd: number;
|
|
1160
|
+
maxBorrowUsd: number;
|
|
1161
|
+
}>>;
|
|
1162
|
+
/**
|
|
1163
|
+
* Calculate max additional borrowable in USD given current positions.
|
|
1164
|
+
*/
|
|
1165
|
+
getMaxBorrowable(): Promise<{
|
|
1166
|
+
availableBorrowUsd: number;
|
|
1167
|
+
currentLtv: number;
|
|
1168
|
+
liquidationThreshold: number;
|
|
1169
|
+
totalCollateralUsd: number;
|
|
1170
|
+
totalDebtUsd: number;
|
|
1171
|
+
}>;
|
|
1172
|
+
/**
|
|
1173
|
+
* Scan wallet for tokens that exist as Aave reserves.
|
|
1174
|
+
*/
|
|
1175
|
+
getWalletTokens(): Promise<Array<{
|
|
1176
|
+
symbol: string;
|
|
1177
|
+
address: string;
|
|
1178
|
+
eoaBalance: number;
|
|
1179
|
+
safeBalance: number;
|
|
1180
|
+
priceUsd: number;
|
|
1181
|
+
valueUsd: number;
|
|
1182
|
+
canSupply: boolean;
|
|
1183
|
+
canBorrow: boolean;
|
|
1184
|
+
}>>;
|
|
1185
|
+
/**
|
|
1186
|
+
* Get yield spread for LST carry trades on Aave.
|
|
1187
|
+
* Compares LST native yield vs Aave borrow rates.
|
|
1188
|
+
*/
|
|
1189
|
+
getYieldSpread(_minLiquidity?: number): Promise<Array<{
|
|
1190
|
+
collateralToken: string;
|
|
1191
|
+
loanToken: string;
|
|
1192
|
+
collateralYield: number;
|
|
1193
|
+
borrowRate: number;
|
|
1194
|
+
netSpread: number;
|
|
1195
|
+
profitable: boolean;
|
|
1196
|
+
collateralLtv: number;
|
|
1197
|
+
maxSafeLeverage: number;
|
|
1198
|
+
}>>;
|
|
1199
|
+
private static readonly LST_PROJECT_MAP;
|
|
1200
|
+
private static _lstYieldCache;
|
|
1201
|
+
private static readonly LST_CACHE_TTL;
|
|
1202
|
+
private _getLstYields;
|
|
1131
1203
|
private _resolveReserve;
|
|
1132
1204
|
}
|
|
1133
1205
|
|
package/dist/index.js
CHANGED
|
@@ -2308,7 +2308,7 @@ var _MorphoClient = class _MorphoClient extends AgentAccountClient {
|
|
|
2308
2308
|
} else {
|
|
2309
2309
|
try {
|
|
2310
2310
|
const params = await this.findMarketForCollateral(collateralSymbol);
|
|
2311
|
-
const loanDecimals =
|
|
2311
|
+
const loanDecimals = market.loanDecimals;
|
|
2312
2312
|
const oracleContract = new import_ethers3.Contract(params.oracle, [
|
|
2313
2313
|
"function price() view returns (uint256)"
|
|
2314
2314
|
], this.provider);
|
|
@@ -2851,8 +2851,8 @@ var _MorphoClient = class _MorphoClient extends AgentAccountClient {
|
|
|
2851
2851
|
const { params: p } = await this._findActiveMarket();
|
|
2852
2852
|
params = p;
|
|
2853
2853
|
}
|
|
2854
|
-
const loanTokenAddr = params.loanToken;
|
|
2855
2854
|
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
2855
|
+
const loanTokenAddr = params.loanToken;
|
|
2856
2856
|
let repayAssets;
|
|
2857
2857
|
let repayShares;
|
|
2858
2858
|
let approveAmount;
|
|
@@ -3200,6 +3200,84 @@ var _MorphoClient = class _MorphoClient extends AgentAccountClient {
|
|
|
3200
3200
|
// ────────────────────────────────────────────────────────────
|
|
3201
3201
|
// ERC-4337 UserOp helpers (Safe + Safe7579 + EntryPoint v0.7)
|
|
3202
3202
|
// ────────────────────────────────────────────────────────────
|
|
3203
|
+
/**
|
|
3204
|
+
* Repay all debt and withdraw all collateral in one atomic batch.
|
|
3205
|
+
* Full position exit: approve + repay(shares) + withdrawCollateral → 1 UserOp.
|
|
3206
|
+
*/
|
|
3207
|
+
async repayAndWithdraw(tokenSymbol, loanTokenSymbol, toEoa = true) {
|
|
3208
|
+
const acctAddr = await this.getAccountAddress();
|
|
3209
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
3210
|
+
let params;
|
|
3211
|
+
let colSymbol;
|
|
3212
|
+
if (tokenSymbol) {
|
|
3213
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
3214
|
+
colSymbol = tokenSymbol;
|
|
3215
|
+
} else {
|
|
3216
|
+
const active = await this._findActiveMarket();
|
|
3217
|
+
params = active.params;
|
|
3218
|
+
colSymbol = active.symbol;
|
|
3219
|
+
}
|
|
3220
|
+
const colInfo = await this._resolveToken(colSymbol);
|
|
3221
|
+
const dest = toEoa ? await this.getSignerAddress() : acctAddr;
|
|
3222
|
+
const marketId = import_ethers3.ethers.keccak256(import_ethers3.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
3223
|
+
["address", "address", "address", "address", "uint256"],
|
|
3224
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
3225
|
+
));
|
|
3226
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
3227
|
+
const borrowShares = BigInt(pos.borrowShares);
|
|
3228
|
+
const collateralAmount = BigInt(pos.collateral);
|
|
3229
|
+
if (collateralAmount === 0n) {
|
|
3230
|
+
throw new AgetherError("No collateral to withdraw", "NO_COLLATERAL");
|
|
3231
|
+
}
|
|
3232
|
+
const targets = [];
|
|
3233
|
+
const values = [];
|
|
3234
|
+
const datas = [];
|
|
3235
|
+
if (borrowShares > 0n) {
|
|
3236
|
+
const onChainMkt = await this.morphoBlue.market(marketId);
|
|
3237
|
+
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
3238
|
+
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
3239
|
+
const estimatedDebt = totalBorrowShares > 0n ? borrowShares * totalBorrowAssets / totalBorrowShares + 100n : 0n;
|
|
3240
|
+
const loanContract = new import_ethers3.Contract(params.loanToken, ERC20_ABI, this._signer);
|
|
3241
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
3242
|
+
if (acctBalance < estimatedDebt) {
|
|
3243
|
+
const shortfall = estimatedDebt - acctBalance;
|
|
3244
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
3245
|
+
if (eoaBalance >= shortfall) {
|
|
3246
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
3247
|
+
await transferTx.wait();
|
|
3248
|
+
this._refreshSigner();
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
targets.push(params.loanToken);
|
|
3252
|
+
values.push(0n);
|
|
3253
|
+
datas.push(erc20Iface2.encodeFunctionData("approve", [morphoAddr, estimatedDebt]));
|
|
3254
|
+
targets.push(morphoAddr);
|
|
3255
|
+
values.push(0n);
|
|
3256
|
+
datas.push(morphoIface.encodeFunctionData("repay", [
|
|
3257
|
+
this._toTuple(params),
|
|
3258
|
+
0n,
|
|
3259
|
+
borrowShares,
|
|
3260
|
+
acctAddr,
|
|
3261
|
+
"0x"
|
|
3262
|
+
]));
|
|
3263
|
+
}
|
|
3264
|
+
targets.push(morphoAddr);
|
|
3265
|
+
values.push(0n);
|
|
3266
|
+
datas.push(morphoIface.encodeFunctionData("withdrawCollateral", [
|
|
3267
|
+
this._toTuple(params),
|
|
3268
|
+
collateralAmount,
|
|
3269
|
+
acctAddr,
|
|
3270
|
+
dest
|
|
3271
|
+
]));
|
|
3272
|
+
const receipt = await this.executeBatch(targets, values, datas);
|
|
3273
|
+
return {
|
|
3274
|
+
tx: receipt.hash,
|
|
3275
|
+
repaid: borrowShares > 0n ? "all" : "0",
|
|
3276
|
+
withdrawn: import_ethers3.ethers.formatUnits(collateralAmount, colInfo.decimals),
|
|
3277
|
+
collateralToken: colSymbol,
|
|
3278
|
+
loanToken: this._tokenCache.get(params.loanToken.toLowerCase())?.symbol ?? "unknown"
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3203
3281
|
/** Convert MorphoMarketParams to Solidity tuple. */
|
|
3204
3282
|
_toTuple(p) {
|
|
3205
3283
|
return [p.loanToken, p.collateralToken, p.oracle, p.irm, p.lltv];
|
|
@@ -3791,6 +3869,199 @@ var _AaveClient = class _AaveClient extends AgentAccountClient {
|
|
|
3791
3869
|
);
|
|
3792
3870
|
}
|
|
3793
3871
|
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
3872
|
+
/**
|
|
3873
|
+
* Repay all debt and withdraw all collateral in one atomic batch.
|
|
3874
|
+
* Full position exit: approve + repay + withdraw → 1 UserOp.
|
|
3875
|
+
*/
|
|
3876
|
+
async repayAndWithdraw(repayAsset, withdrawAsset, to) {
|
|
3877
|
+
const repayReserve = this._resolveReserve(repayAsset);
|
|
3878
|
+
const withdrawReserve = this._resolveReserve(withdrawAsset);
|
|
3879
|
+
const safeAddr = await this.getAccountAddress();
|
|
3880
|
+
const poolAddr = await this._pool.getAddress();
|
|
3881
|
+
const recipient = to ?? safeAddr;
|
|
3882
|
+
const targets = [];
|
|
3883
|
+
const values = [];
|
|
3884
|
+
const datas = [];
|
|
3885
|
+
targets.push(repayReserve.address);
|
|
3886
|
+
values.push(0n);
|
|
3887
|
+
datas.push(this._erc20Iface.encodeFunctionData("approve", [poolAddr, import_ethers4.ethers.MaxUint256]));
|
|
3888
|
+
targets.push(poolAddr);
|
|
3889
|
+
values.push(0n);
|
|
3890
|
+
datas.push(this._poolIface.encodeFunctionData("repay", [
|
|
3891
|
+
repayReserve.address,
|
|
3892
|
+
import_ethers4.ethers.MaxUint256,
|
|
3893
|
+
2,
|
|
3894
|
+
safeAddr
|
|
3895
|
+
]));
|
|
3896
|
+
targets.push(poolAddr);
|
|
3897
|
+
values.push(0n);
|
|
3898
|
+
datas.push(this._poolIface.encodeFunctionData("withdraw", [
|
|
3899
|
+
withdrawReserve.address,
|
|
3900
|
+
import_ethers4.ethers.MaxUint256,
|
|
3901
|
+
recipient
|
|
3902
|
+
]));
|
|
3903
|
+
return this.executeBatch(targets, values, datas);
|
|
3904
|
+
}
|
|
3905
|
+
// ─── Market Discovery ─────────────────────────────────────────────────
|
|
3906
|
+
/**
|
|
3907
|
+
* Search reserves by token symbol (case-insensitive partial match).
|
|
3908
|
+
*/
|
|
3909
|
+
async searchReserves(query) {
|
|
3910
|
+
const reserves = await this.getReserves();
|
|
3911
|
+
const q = query.toLowerCase();
|
|
3912
|
+
return reserves.filter(
|
|
3913
|
+
(r) => r.symbol.toLowerCase().includes(q) || r.address.toLowerCase() === q
|
|
3914
|
+
);
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Get borrowing options: which tokens can be borrowed, with what collateral.
|
|
3918
|
+
* If collateral symbol given, shows what you can borrow using that as collateral.
|
|
3919
|
+
*/
|
|
3920
|
+
async getBorrowingOptions(collateral) {
|
|
3921
|
+
const reserves = await this.getReserves();
|
|
3922
|
+
const safeAddr = await this.getAccountAddress();
|
|
3923
|
+
const results = [];
|
|
3924
|
+
const eoaAddr = this.getWalletAddress();
|
|
3925
|
+
for (const reserve of reserves) {
|
|
3926
|
+
if (!reserve.isActive || reserve.ltv === 0) continue;
|
|
3927
|
+
if (collateral && reserve.symbol.toLowerCase() !== collateral.toLowerCase()) continue;
|
|
3928
|
+
const token = new import_ethers4.Contract(reserve.address, [
|
|
3929
|
+
"function balanceOf(address) view returns (uint256)"
|
|
3930
|
+
], this.provider);
|
|
3931
|
+
const [eoaBal, safeBal] = await Promise.all([
|
|
3932
|
+
token.balanceOf(eoaAddr).catch(() => 0n),
|
|
3933
|
+
token.balanceOf(safeAddr).catch(() => 0n)
|
|
3934
|
+
]);
|
|
3935
|
+
const totalBal = Number(eoaBal + safeBal) / 10 ** reserve.decimals;
|
|
3936
|
+
if (totalBal <= 0 && !collateral) continue;
|
|
3937
|
+
const collateralValueUsd = totalBal * reserve.priceUsd;
|
|
3938
|
+
const maxBorrowUsd = collateralValueUsd * (reserve.ltv / 100);
|
|
3939
|
+
const borrowable = reserves.filter((r) => r.borrowingEnabled && r.isActive && r.symbol !== reserve.symbol);
|
|
3940
|
+
results.push({
|
|
3941
|
+
collateral: reserve.symbol,
|
|
3942
|
+
borrowable,
|
|
3943
|
+
collateralBalance: totalBal,
|
|
3944
|
+
collateralValueUsd,
|
|
3945
|
+
maxBorrowUsd
|
|
3946
|
+
});
|
|
3947
|
+
}
|
|
3948
|
+
return results;
|
|
3949
|
+
}
|
|
3950
|
+
/**
|
|
3951
|
+
* Calculate max additional borrowable in USD given current positions.
|
|
3952
|
+
*/
|
|
3953
|
+
async getMaxBorrowable() {
|
|
3954
|
+
const data = await this.getAccountData();
|
|
3955
|
+
return {
|
|
3956
|
+
availableBorrowUsd: data.availableBorrowUsd,
|
|
3957
|
+
currentLtv: data.currentLtv,
|
|
3958
|
+
liquidationThreshold: data.liquidationThreshold,
|
|
3959
|
+
totalCollateralUsd: data.totalCollateralUsd,
|
|
3960
|
+
totalDebtUsd: data.totalDebtUsd
|
|
3961
|
+
};
|
|
3962
|
+
}
|
|
3963
|
+
/**
|
|
3964
|
+
* Scan wallet for tokens that exist as Aave reserves.
|
|
3965
|
+
*/
|
|
3966
|
+
async getWalletTokens() {
|
|
3967
|
+
const reserves = await this.getReserves();
|
|
3968
|
+
const safeAddr = await this.getAccountAddress();
|
|
3969
|
+
const eoaAddr = this.getWalletAddress();
|
|
3970
|
+
const results = [];
|
|
3971
|
+
const promises = reserves.map(async (reserve) => {
|
|
3972
|
+
const token = new import_ethers4.Contract(reserve.address, [
|
|
3973
|
+
"function balanceOf(address) view returns (uint256)"
|
|
3974
|
+
], this.provider);
|
|
3975
|
+
const [eoaBal, safeBal] = await Promise.all([
|
|
3976
|
+
token.balanceOf(eoaAddr).catch(() => 0n),
|
|
3977
|
+
token.balanceOf(safeAddr).catch(() => 0n)
|
|
3978
|
+
]);
|
|
3979
|
+
const eoaBalance = Number(eoaBal) / 10 ** reserve.decimals;
|
|
3980
|
+
const safeBalance = Number(safeBal) / 10 ** reserve.decimals;
|
|
3981
|
+
if (eoaBalance > 0 || safeBalance > 0) {
|
|
3982
|
+
return {
|
|
3983
|
+
symbol: reserve.symbol,
|
|
3984
|
+
address: reserve.address,
|
|
3985
|
+
eoaBalance,
|
|
3986
|
+
safeBalance,
|
|
3987
|
+
priceUsd: reserve.priceUsd,
|
|
3988
|
+
valueUsd: (eoaBalance + safeBalance) * reserve.priceUsd,
|
|
3989
|
+
canSupply: reserve.isActive,
|
|
3990
|
+
canBorrow: reserve.borrowingEnabled
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
return null;
|
|
3994
|
+
});
|
|
3995
|
+
const settled = await Promise.all(promises);
|
|
3996
|
+
for (const r of settled) {
|
|
3997
|
+
if (r) results.push(r);
|
|
3998
|
+
}
|
|
3999
|
+
return results;
|
|
4000
|
+
}
|
|
4001
|
+
/**
|
|
4002
|
+
* Get yield spread for LST carry trades on Aave.
|
|
4003
|
+
* Compares LST native yield vs Aave borrow rates.
|
|
4004
|
+
*/
|
|
4005
|
+
async getYieldSpread(_minLiquidity = 0) {
|
|
4006
|
+
const reserves = await this.getReserves();
|
|
4007
|
+
const lstYields = await this._getLstYields();
|
|
4008
|
+
if (Object.keys(lstYields).length === 0) return [];
|
|
4009
|
+
const results = [];
|
|
4010
|
+
for (const [symbol, yieldApy] of Object.entries(lstYields)) {
|
|
4011
|
+
const collateralReserve = reserves.find((r) => r.symbol.toLowerCase() === symbol.toLowerCase());
|
|
4012
|
+
if (!collateralReserve || !collateralReserve.isActive || collateralReserve.ltv === 0) continue;
|
|
4013
|
+
for (const loanReserve of reserves) {
|
|
4014
|
+
if (!loanReserve.borrowingEnabled || !loanReserve.isActive) continue;
|
|
4015
|
+
if (loanReserve.symbol === collateralReserve.symbol) continue;
|
|
4016
|
+
const netSpread = yieldApy - loanReserve.borrowApy;
|
|
4017
|
+
const safeLtv = collateralReserve.ltv / 100 * 0.8;
|
|
4018
|
+
const maxLeverage = 1 / (1 - safeLtv);
|
|
4019
|
+
results.push({
|
|
4020
|
+
collateralToken: collateralReserve.symbol,
|
|
4021
|
+
loanToken: loanReserve.symbol,
|
|
4022
|
+
collateralYield: yieldApy,
|
|
4023
|
+
borrowRate: loanReserve.borrowApy,
|
|
4024
|
+
netSpread,
|
|
4025
|
+
profitable: netSpread > 0,
|
|
4026
|
+
collateralLtv: collateralReserve.ltv,
|
|
4027
|
+
maxSafeLeverage: parseFloat(maxLeverage.toFixed(2))
|
|
4028
|
+
});
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
return results.sort((a, b) => b.netSpread - a.netSpread);
|
|
4032
|
+
}
|
|
4033
|
+
async _getLstYields() {
|
|
4034
|
+
if (_AaveClient._lstYieldCache && Date.now() - _AaveClient._lstYieldCache.ts < _AaveClient.LST_CACHE_TTL) {
|
|
4035
|
+
return _AaveClient._lstYieldCache.data;
|
|
4036
|
+
}
|
|
4037
|
+
const yields = {};
|
|
4038
|
+
try {
|
|
4039
|
+
const resp = await fetch("https://yields.llama.fi/pools", {
|
|
4040
|
+
headers: { "User-Agent": "agether-sdk/1.0" },
|
|
4041
|
+
signal: AbortSignal.timeout(1e4)
|
|
4042
|
+
});
|
|
4043
|
+
if (!resp.ok) return yields;
|
|
4044
|
+
const data = await resp.json();
|
|
4045
|
+
const pools = data?.data ?? [];
|
|
4046
|
+
for (const [morphoSymbol, mapping] of Object.entries(_AaveClient.LST_PROJECT_MAP)) {
|
|
4047
|
+
const matchingPools = pools.filter(
|
|
4048
|
+
(p) => p.project === mapping.project && p.symbol?.toUpperCase() === mapping.symbol && ["Ethereum", "Base"].includes(p.chain)
|
|
4049
|
+
);
|
|
4050
|
+
if (matchingPools.length > 0) {
|
|
4051
|
+
const best = matchingPools.reduce(
|
|
4052
|
+
(a, b) => (b.tvlUsd ?? 0) > (a.tvlUsd ?? 0) ? b : a
|
|
4053
|
+
);
|
|
4054
|
+
if (best.apy !== void 0 && best.apy > 0) {
|
|
4055
|
+
yields[morphoSymbol] = parseFloat(best.apy.toFixed(4));
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
} catch (e) {
|
|
4060
|
+
console.warn("[aave] Failed to fetch LST yields:", e instanceof Error ? e.message : e);
|
|
4061
|
+
}
|
|
4062
|
+
_AaveClient._lstYieldCache = { data: yields, ts: Date.now() };
|
|
4063
|
+
return yields;
|
|
4064
|
+
}
|
|
3794
4065
|
_resolveReserve(symbolOrAddress) {
|
|
3795
4066
|
const reserves = KNOWN_RESERVES[this._aaveChainId] ?? [];
|
|
3796
4067
|
const bySymbol = reserves.find((r) => r.symbol.toLowerCase() === symbolOrAddress.toLowerCase());
|
|
@@ -3804,6 +4075,23 @@ var _AaveClient = class _AaveClient extends AgentAccountClient {
|
|
|
3804
4075
|
}
|
|
3805
4076
|
};
|
|
3806
4077
|
_AaveClient.CACHE_TTL = 6e4;
|
|
4078
|
+
// ─── DeFi Llama LST Yields (shared logic) ─────────────────────────────
|
|
4079
|
+
_AaveClient.LST_PROJECT_MAP = {
|
|
4080
|
+
"wstETH": { project: "lido", symbol: "STETH" },
|
|
4081
|
+
"cbETH": { project: "coinbase-wrapped-staked-eth", symbol: "CBETH" },
|
|
4082
|
+
"rETH": { project: "rocket-pool", symbol: "RETH" },
|
|
4083
|
+
"weETH": { project: "ether.fi-stake", symbol: "WEETH" },
|
|
4084
|
+
"ezETH": { project: "renzo", symbol: "EZETH" },
|
|
4085
|
+
"rsETH": { project: "kelp", symbol: "RSETH" },
|
|
4086
|
+
"swETH": { project: "swell-liquid-staking", symbol: "SWETH" },
|
|
4087
|
+
"mETH": { project: "meth-protocol", symbol: "METH" },
|
|
4088
|
+
"sfrxETH": { project: "frax-ether", symbol: "SFRXETH" },
|
|
4089
|
+
"oETH": { project: "origin-ether", symbol: "OETH" },
|
|
4090
|
+
"ETHx": { project: "stader", symbol: "ETHX" },
|
|
4091
|
+
"wBETH": { project: "binance-staked-eth", symbol: "WBETH" }
|
|
4092
|
+
};
|
|
4093
|
+
_AaveClient._lstYieldCache = null;
|
|
4094
|
+
_AaveClient.LST_CACHE_TTL = 30 * 60 * 1e3;
|
|
3807
4095
|
var AaveClient = _AaveClient;
|
|
3808
4096
|
|
|
3809
4097
|
// src/clients/ScoringClient.ts
|
package/dist/index.mjs
CHANGED
|
@@ -2230,7 +2230,7 @@ var _MorphoClient = class _MorphoClient extends AgentAccountClient {
|
|
|
2230
2230
|
} else {
|
|
2231
2231
|
try {
|
|
2232
2232
|
const params = await this.findMarketForCollateral(collateralSymbol);
|
|
2233
|
-
const loanDecimals =
|
|
2233
|
+
const loanDecimals = market.loanDecimals;
|
|
2234
2234
|
const oracleContract = new Contract3(params.oracle, [
|
|
2235
2235
|
"function price() view returns (uint256)"
|
|
2236
2236
|
], this.provider);
|
|
@@ -2773,8 +2773,8 @@ var _MorphoClient = class _MorphoClient extends AgentAccountClient {
|
|
|
2773
2773
|
const { params: p } = await this._findActiveMarket();
|
|
2774
2774
|
params = p;
|
|
2775
2775
|
}
|
|
2776
|
-
const loanTokenAddr = params.loanToken;
|
|
2777
2776
|
const loanDecimals = await this._getLoanTokenDecimals(params);
|
|
2777
|
+
const loanTokenAddr = params.loanToken;
|
|
2778
2778
|
let repayAssets;
|
|
2779
2779
|
let repayShares;
|
|
2780
2780
|
let approveAmount;
|
|
@@ -3122,6 +3122,84 @@ var _MorphoClient = class _MorphoClient extends AgentAccountClient {
|
|
|
3122
3122
|
// ────────────────────────────────────────────────────────────
|
|
3123
3123
|
// ERC-4337 UserOp helpers (Safe + Safe7579 + EntryPoint v0.7)
|
|
3124
3124
|
// ────────────────────────────────────────────────────────────
|
|
3125
|
+
/**
|
|
3126
|
+
* Repay all debt and withdraw all collateral in one atomic batch.
|
|
3127
|
+
* Full position exit: approve + repay(shares) + withdrawCollateral → 1 UserOp.
|
|
3128
|
+
*/
|
|
3129
|
+
async repayAndWithdraw(tokenSymbol, loanTokenSymbol, toEoa = true) {
|
|
3130
|
+
const acctAddr = await this.getAccountAddress();
|
|
3131
|
+
const morphoAddr = this.config.contracts.morphoBlue;
|
|
3132
|
+
let params;
|
|
3133
|
+
let colSymbol;
|
|
3134
|
+
if (tokenSymbol) {
|
|
3135
|
+
params = await this.findMarketForCollateral(tokenSymbol, loanTokenSymbol);
|
|
3136
|
+
colSymbol = tokenSymbol;
|
|
3137
|
+
} else {
|
|
3138
|
+
const active = await this._findActiveMarket();
|
|
3139
|
+
params = active.params;
|
|
3140
|
+
colSymbol = active.symbol;
|
|
3141
|
+
}
|
|
3142
|
+
const colInfo = await this._resolveToken(colSymbol);
|
|
3143
|
+
const dest = toEoa ? await this.getSignerAddress() : acctAddr;
|
|
3144
|
+
const marketId = ethers3.keccak256(ethers3.AbiCoder.defaultAbiCoder().encode(
|
|
3145
|
+
["address", "address", "address", "address", "uint256"],
|
|
3146
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
3147
|
+
));
|
|
3148
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
3149
|
+
const borrowShares = BigInt(pos.borrowShares);
|
|
3150
|
+
const collateralAmount = BigInt(pos.collateral);
|
|
3151
|
+
if (collateralAmount === 0n) {
|
|
3152
|
+
throw new AgetherError("No collateral to withdraw", "NO_COLLATERAL");
|
|
3153
|
+
}
|
|
3154
|
+
const targets = [];
|
|
3155
|
+
const values = [];
|
|
3156
|
+
const datas = [];
|
|
3157
|
+
if (borrowShares > 0n) {
|
|
3158
|
+
const onChainMkt = await this.morphoBlue.market(marketId);
|
|
3159
|
+
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
3160
|
+
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
3161
|
+
const estimatedDebt = totalBorrowShares > 0n ? borrowShares * totalBorrowAssets / totalBorrowShares + 100n : 0n;
|
|
3162
|
+
const loanContract = new Contract3(params.loanToken, ERC20_ABI, this._signer);
|
|
3163
|
+
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
3164
|
+
if (acctBalance < estimatedDebt) {
|
|
3165
|
+
const shortfall = estimatedDebt - acctBalance;
|
|
3166
|
+
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
3167
|
+
if (eoaBalance >= shortfall) {
|
|
3168
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
3169
|
+
await transferTx.wait();
|
|
3170
|
+
this._refreshSigner();
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
targets.push(params.loanToken);
|
|
3174
|
+
values.push(0n);
|
|
3175
|
+
datas.push(erc20Iface2.encodeFunctionData("approve", [morphoAddr, estimatedDebt]));
|
|
3176
|
+
targets.push(morphoAddr);
|
|
3177
|
+
values.push(0n);
|
|
3178
|
+
datas.push(morphoIface.encodeFunctionData("repay", [
|
|
3179
|
+
this._toTuple(params),
|
|
3180
|
+
0n,
|
|
3181
|
+
borrowShares,
|
|
3182
|
+
acctAddr,
|
|
3183
|
+
"0x"
|
|
3184
|
+
]));
|
|
3185
|
+
}
|
|
3186
|
+
targets.push(morphoAddr);
|
|
3187
|
+
values.push(0n);
|
|
3188
|
+
datas.push(morphoIface.encodeFunctionData("withdrawCollateral", [
|
|
3189
|
+
this._toTuple(params),
|
|
3190
|
+
collateralAmount,
|
|
3191
|
+
acctAddr,
|
|
3192
|
+
dest
|
|
3193
|
+
]));
|
|
3194
|
+
const receipt = await this.executeBatch(targets, values, datas);
|
|
3195
|
+
return {
|
|
3196
|
+
tx: receipt.hash,
|
|
3197
|
+
repaid: borrowShares > 0n ? "all" : "0",
|
|
3198
|
+
withdrawn: ethers3.formatUnits(collateralAmount, colInfo.decimals),
|
|
3199
|
+
collateralToken: colSymbol,
|
|
3200
|
+
loanToken: this._tokenCache.get(params.loanToken.toLowerCase())?.symbol ?? "unknown"
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
3125
3203
|
/** Convert MorphoMarketParams to Solidity tuple. */
|
|
3126
3204
|
_toTuple(p) {
|
|
3127
3205
|
return [p.loanToken, p.collateralToken, p.oracle, p.irm, p.lltv];
|
|
@@ -3713,6 +3791,199 @@ var _AaveClient = class _AaveClient extends AgentAccountClient {
|
|
|
3713
3791
|
);
|
|
3714
3792
|
}
|
|
3715
3793
|
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
3794
|
+
/**
|
|
3795
|
+
* Repay all debt and withdraw all collateral in one atomic batch.
|
|
3796
|
+
* Full position exit: approve + repay + withdraw → 1 UserOp.
|
|
3797
|
+
*/
|
|
3798
|
+
async repayAndWithdraw(repayAsset, withdrawAsset, to) {
|
|
3799
|
+
const repayReserve = this._resolveReserve(repayAsset);
|
|
3800
|
+
const withdrawReserve = this._resolveReserve(withdrawAsset);
|
|
3801
|
+
const safeAddr = await this.getAccountAddress();
|
|
3802
|
+
const poolAddr = await this._pool.getAddress();
|
|
3803
|
+
const recipient = to ?? safeAddr;
|
|
3804
|
+
const targets = [];
|
|
3805
|
+
const values = [];
|
|
3806
|
+
const datas = [];
|
|
3807
|
+
targets.push(repayReserve.address);
|
|
3808
|
+
values.push(0n);
|
|
3809
|
+
datas.push(this._erc20Iface.encodeFunctionData("approve", [poolAddr, ethers4.MaxUint256]));
|
|
3810
|
+
targets.push(poolAddr);
|
|
3811
|
+
values.push(0n);
|
|
3812
|
+
datas.push(this._poolIface.encodeFunctionData("repay", [
|
|
3813
|
+
repayReserve.address,
|
|
3814
|
+
ethers4.MaxUint256,
|
|
3815
|
+
2,
|
|
3816
|
+
safeAddr
|
|
3817
|
+
]));
|
|
3818
|
+
targets.push(poolAddr);
|
|
3819
|
+
values.push(0n);
|
|
3820
|
+
datas.push(this._poolIface.encodeFunctionData("withdraw", [
|
|
3821
|
+
withdrawReserve.address,
|
|
3822
|
+
ethers4.MaxUint256,
|
|
3823
|
+
recipient
|
|
3824
|
+
]));
|
|
3825
|
+
return this.executeBatch(targets, values, datas);
|
|
3826
|
+
}
|
|
3827
|
+
// ─── Market Discovery ─────────────────────────────────────────────────
|
|
3828
|
+
/**
|
|
3829
|
+
* Search reserves by token symbol (case-insensitive partial match).
|
|
3830
|
+
*/
|
|
3831
|
+
async searchReserves(query) {
|
|
3832
|
+
const reserves = await this.getReserves();
|
|
3833
|
+
const q = query.toLowerCase();
|
|
3834
|
+
return reserves.filter(
|
|
3835
|
+
(r) => r.symbol.toLowerCase().includes(q) || r.address.toLowerCase() === q
|
|
3836
|
+
);
|
|
3837
|
+
}
|
|
3838
|
+
/**
|
|
3839
|
+
* Get borrowing options: which tokens can be borrowed, with what collateral.
|
|
3840
|
+
* If collateral symbol given, shows what you can borrow using that as collateral.
|
|
3841
|
+
*/
|
|
3842
|
+
async getBorrowingOptions(collateral) {
|
|
3843
|
+
const reserves = await this.getReserves();
|
|
3844
|
+
const safeAddr = await this.getAccountAddress();
|
|
3845
|
+
const results = [];
|
|
3846
|
+
const eoaAddr = this.getWalletAddress();
|
|
3847
|
+
for (const reserve of reserves) {
|
|
3848
|
+
if (!reserve.isActive || reserve.ltv === 0) continue;
|
|
3849
|
+
if (collateral && reserve.symbol.toLowerCase() !== collateral.toLowerCase()) continue;
|
|
3850
|
+
const token = new Contract4(reserve.address, [
|
|
3851
|
+
"function balanceOf(address) view returns (uint256)"
|
|
3852
|
+
], this.provider);
|
|
3853
|
+
const [eoaBal, safeBal] = await Promise.all([
|
|
3854
|
+
token.balanceOf(eoaAddr).catch(() => 0n),
|
|
3855
|
+
token.balanceOf(safeAddr).catch(() => 0n)
|
|
3856
|
+
]);
|
|
3857
|
+
const totalBal = Number(eoaBal + safeBal) / 10 ** reserve.decimals;
|
|
3858
|
+
if (totalBal <= 0 && !collateral) continue;
|
|
3859
|
+
const collateralValueUsd = totalBal * reserve.priceUsd;
|
|
3860
|
+
const maxBorrowUsd = collateralValueUsd * (reserve.ltv / 100);
|
|
3861
|
+
const borrowable = reserves.filter((r) => r.borrowingEnabled && r.isActive && r.symbol !== reserve.symbol);
|
|
3862
|
+
results.push({
|
|
3863
|
+
collateral: reserve.symbol,
|
|
3864
|
+
borrowable,
|
|
3865
|
+
collateralBalance: totalBal,
|
|
3866
|
+
collateralValueUsd,
|
|
3867
|
+
maxBorrowUsd
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
return results;
|
|
3871
|
+
}
|
|
3872
|
+
/**
|
|
3873
|
+
* Calculate max additional borrowable in USD given current positions.
|
|
3874
|
+
*/
|
|
3875
|
+
async getMaxBorrowable() {
|
|
3876
|
+
const data = await this.getAccountData();
|
|
3877
|
+
return {
|
|
3878
|
+
availableBorrowUsd: data.availableBorrowUsd,
|
|
3879
|
+
currentLtv: data.currentLtv,
|
|
3880
|
+
liquidationThreshold: data.liquidationThreshold,
|
|
3881
|
+
totalCollateralUsd: data.totalCollateralUsd,
|
|
3882
|
+
totalDebtUsd: data.totalDebtUsd
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
/**
|
|
3886
|
+
* Scan wallet for tokens that exist as Aave reserves.
|
|
3887
|
+
*/
|
|
3888
|
+
async getWalletTokens() {
|
|
3889
|
+
const reserves = await this.getReserves();
|
|
3890
|
+
const safeAddr = await this.getAccountAddress();
|
|
3891
|
+
const eoaAddr = this.getWalletAddress();
|
|
3892
|
+
const results = [];
|
|
3893
|
+
const promises = reserves.map(async (reserve) => {
|
|
3894
|
+
const token = new Contract4(reserve.address, [
|
|
3895
|
+
"function balanceOf(address) view returns (uint256)"
|
|
3896
|
+
], this.provider);
|
|
3897
|
+
const [eoaBal, safeBal] = await Promise.all([
|
|
3898
|
+
token.balanceOf(eoaAddr).catch(() => 0n),
|
|
3899
|
+
token.balanceOf(safeAddr).catch(() => 0n)
|
|
3900
|
+
]);
|
|
3901
|
+
const eoaBalance = Number(eoaBal) / 10 ** reserve.decimals;
|
|
3902
|
+
const safeBalance = Number(safeBal) / 10 ** reserve.decimals;
|
|
3903
|
+
if (eoaBalance > 0 || safeBalance > 0) {
|
|
3904
|
+
return {
|
|
3905
|
+
symbol: reserve.symbol,
|
|
3906
|
+
address: reserve.address,
|
|
3907
|
+
eoaBalance,
|
|
3908
|
+
safeBalance,
|
|
3909
|
+
priceUsd: reserve.priceUsd,
|
|
3910
|
+
valueUsd: (eoaBalance + safeBalance) * reserve.priceUsd,
|
|
3911
|
+
canSupply: reserve.isActive,
|
|
3912
|
+
canBorrow: reserve.borrowingEnabled
|
|
3913
|
+
};
|
|
3914
|
+
}
|
|
3915
|
+
return null;
|
|
3916
|
+
});
|
|
3917
|
+
const settled = await Promise.all(promises);
|
|
3918
|
+
for (const r of settled) {
|
|
3919
|
+
if (r) results.push(r);
|
|
3920
|
+
}
|
|
3921
|
+
return results;
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* Get yield spread for LST carry trades on Aave.
|
|
3925
|
+
* Compares LST native yield vs Aave borrow rates.
|
|
3926
|
+
*/
|
|
3927
|
+
async getYieldSpread(_minLiquidity = 0) {
|
|
3928
|
+
const reserves = await this.getReserves();
|
|
3929
|
+
const lstYields = await this._getLstYields();
|
|
3930
|
+
if (Object.keys(lstYields).length === 0) return [];
|
|
3931
|
+
const results = [];
|
|
3932
|
+
for (const [symbol, yieldApy] of Object.entries(lstYields)) {
|
|
3933
|
+
const collateralReserve = reserves.find((r) => r.symbol.toLowerCase() === symbol.toLowerCase());
|
|
3934
|
+
if (!collateralReserve || !collateralReserve.isActive || collateralReserve.ltv === 0) continue;
|
|
3935
|
+
for (const loanReserve of reserves) {
|
|
3936
|
+
if (!loanReserve.borrowingEnabled || !loanReserve.isActive) continue;
|
|
3937
|
+
if (loanReserve.symbol === collateralReserve.symbol) continue;
|
|
3938
|
+
const netSpread = yieldApy - loanReserve.borrowApy;
|
|
3939
|
+
const safeLtv = collateralReserve.ltv / 100 * 0.8;
|
|
3940
|
+
const maxLeverage = 1 / (1 - safeLtv);
|
|
3941
|
+
results.push({
|
|
3942
|
+
collateralToken: collateralReserve.symbol,
|
|
3943
|
+
loanToken: loanReserve.symbol,
|
|
3944
|
+
collateralYield: yieldApy,
|
|
3945
|
+
borrowRate: loanReserve.borrowApy,
|
|
3946
|
+
netSpread,
|
|
3947
|
+
profitable: netSpread > 0,
|
|
3948
|
+
collateralLtv: collateralReserve.ltv,
|
|
3949
|
+
maxSafeLeverage: parseFloat(maxLeverage.toFixed(2))
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
return results.sort((a, b) => b.netSpread - a.netSpread);
|
|
3954
|
+
}
|
|
3955
|
+
async _getLstYields() {
|
|
3956
|
+
if (_AaveClient._lstYieldCache && Date.now() - _AaveClient._lstYieldCache.ts < _AaveClient.LST_CACHE_TTL) {
|
|
3957
|
+
return _AaveClient._lstYieldCache.data;
|
|
3958
|
+
}
|
|
3959
|
+
const yields = {};
|
|
3960
|
+
try {
|
|
3961
|
+
const resp = await fetch("https://yields.llama.fi/pools", {
|
|
3962
|
+
headers: { "User-Agent": "agether-sdk/1.0" },
|
|
3963
|
+
signal: AbortSignal.timeout(1e4)
|
|
3964
|
+
});
|
|
3965
|
+
if (!resp.ok) return yields;
|
|
3966
|
+
const data = await resp.json();
|
|
3967
|
+
const pools = data?.data ?? [];
|
|
3968
|
+
for (const [morphoSymbol, mapping] of Object.entries(_AaveClient.LST_PROJECT_MAP)) {
|
|
3969
|
+
const matchingPools = pools.filter(
|
|
3970
|
+
(p) => p.project === mapping.project && p.symbol?.toUpperCase() === mapping.symbol && ["Ethereum", "Base"].includes(p.chain)
|
|
3971
|
+
);
|
|
3972
|
+
if (matchingPools.length > 0) {
|
|
3973
|
+
const best = matchingPools.reduce(
|
|
3974
|
+
(a, b) => (b.tvlUsd ?? 0) > (a.tvlUsd ?? 0) ? b : a
|
|
3975
|
+
);
|
|
3976
|
+
if (best.apy !== void 0 && best.apy > 0) {
|
|
3977
|
+
yields[morphoSymbol] = parseFloat(best.apy.toFixed(4));
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
} catch (e) {
|
|
3982
|
+
console.warn("[aave] Failed to fetch LST yields:", e instanceof Error ? e.message : e);
|
|
3983
|
+
}
|
|
3984
|
+
_AaveClient._lstYieldCache = { data: yields, ts: Date.now() };
|
|
3985
|
+
return yields;
|
|
3986
|
+
}
|
|
3716
3987
|
_resolveReserve(symbolOrAddress) {
|
|
3717
3988
|
const reserves = KNOWN_RESERVES[this._aaveChainId] ?? [];
|
|
3718
3989
|
const bySymbol = reserves.find((r) => r.symbol.toLowerCase() === symbolOrAddress.toLowerCase());
|
|
@@ -3726,6 +3997,23 @@ var _AaveClient = class _AaveClient extends AgentAccountClient {
|
|
|
3726
3997
|
}
|
|
3727
3998
|
};
|
|
3728
3999
|
_AaveClient.CACHE_TTL = 6e4;
|
|
4000
|
+
// ─── DeFi Llama LST Yields (shared logic) ─────────────────────────────
|
|
4001
|
+
_AaveClient.LST_PROJECT_MAP = {
|
|
4002
|
+
"wstETH": { project: "lido", symbol: "STETH" },
|
|
4003
|
+
"cbETH": { project: "coinbase-wrapped-staked-eth", symbol: "CBETH" },
|
|
4004
|
+
"rETH": { project: "rocket-pool", symbol: "RETH" },
|
|
4005
|
+
"weETH": { project: "ether.fi-stake", symbol: "WEETH" },
|
|
4006
|
+
"ezETH": { project: "renzo", symbol: "EZETH" },
|
|
4007
|
+
"rsETH": { project: "kelp", symbol: "RSETH" },
|
|
4008
|
+
"swETH": { project: "swell-liquid-staking", symbol: "SWETH" },
|
|
4009
|
+
"mETH": { project: "meth-protocol", symbol: "METH" },
|
|
4010
|
+
"sfrxETH": { project: "frax-ether", symbol: "SFRXETH" },
|
|
4011
|
+
"oETH": { project: "origin-ether", symbol: "OETH" },
|
|
4012
|
+
"ETHx": { project: "stader", symbol: "ETHX" },
|
|
4013
|
+
"wBETH": { project: "binance-staked-eth", symbol: "WBETH" }
|
|
4014
|
+
};
|
|
4015
|
+
_AaveClient._lstYieldCache = null;
|
|
4016
|
+
_AaveClient.LST_CACHE_TTL = 30 * 60 * 1e3;
|
|
3729
4017
|
var AaveClient = _AaveClient;
|
|
3730
4018
|
|
|
3731
4019
|
// src/clients/ScoringClient.ts
|