@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 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 = await this._getLoanTokenDecimals(params);
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 = await this._getLoanTokenDecimals(params);
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 = await this._getLoanTokenDecimals(params);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/sdk",
3
- "version": "2.18.0",
3
+ "version": "2.18.1",
4
4
  "description": "TypeScript SDK for Agether - autonomous credit for AI agents on Ethereum & Base",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",