@agether/sdk 2.16.0 → 2.17.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 +336 -24
- package/dist/index.d.mts +87 -7
- package/dist/index.d.ts +87 -7
- package/dist/index.js +331 -24
- package/dist/index.mjs +331 -24
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -543,13 +543,64 @@ var AgetherClient = class _AgetherClient {
|
|
|
543
543
|
return this.agether4337Factory.accountExists(id);
|
|
544
544
|
}
|
|
545
545
|
// ════════════════════════════════════════════════════════
|
|
546
|
+
// Token Discovery (for dynamic balance queries)
|
|
547
|
+
// ════════════════════════════════════════════════════════
|
|
548
|
+
/**
|
|
549
|
+
* Discover token addresses from active Morpho Blue positions.
|
|
550
|
+
* Queries the Morpho GraphQL API for markets involving this agent's account,
|
|
551
|
+
* then extracts collateral and loan token info.
|
|
552
|
+
*/
|
|
553
|
+
async _discoverPositionTokens() {
|
|
554
|
+
const tokens = {};
|
|
555
|
+
try {
|
|
556
|
+
const acctAddr = await this.getAccountAddress();
|
|
557
|
+
const chainId = this.config.chainId;
|
|
558
|
+
const query = `{
|
|
559
|
+
marketPositions(
|
|
560
|
+
where: { userAddress_in: ["${acctAddr.toLowerCase()}"], chainId_in: [${chainId}] }
|
|
561
|
+
first: 20
|
|
562
|
+
) {
|
|
563
|
+
items {
|
|
564
|
+
market {
|
|
565
|
+
collateralAsset { address symbol decimals }
|
|
566
|
+
loanAsset { address symbol decimals }
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}`;
|
|
571
|
+
const resp = await fetch("https://blue-api.morpho.org/graphql", {
|
|
572
|
+
method: "POST",
|
|
573
|
+
headers: { "Content-Type": "application/json" },
|
|
574
|
+
body: JSON.stringify({ query }),
|
|
575
|
+
signal: AbortSignal.timeout(5e3)
|
|
576
|
+
});
|
|
577
|
+
if (!resp.ok) return tokens;
|
|
578
|
+
const data = await resp.json();
|
|
579
|
+
const items = data?.data?.marketPositions?.items ?? [];
|
|
580
|
+
for (const item of items) {
|
|
581
|
+
const col = item?.market?.collateralAsset;
|
|
582
|
+
const loan = item?.market?.loanAsset;
|
|
583
|
+
if (col?.symbol && col?.address) {
|
|
584
|
+
tokens[col.symbol] = { address: col.address, symbol: col.symbol, decimals: col.decimals ?? 18 };
|
|
585
|
+
}
|
|
586
|
+
if (loan?.symbol && loan?.address) {
|
|
587
|
+
tokens[loan.symbol] = { address: loan.address, symbol: loan.symbol, decimals: loan.decimals ?? 18 };
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
} catch {
|
|
591
|
+
}
|
|
592
|
+
return tokens;
|
|
593
|
+
}
|
|
594
|
+
// ════════════════════════════════════════════════════════
|
|
546
595
|
// Balances
|
|
547
596
|
// ════════════════════════════════════════════════════════
|
|
548
597
|
/**
|
|
549
|
-
* Get ETH, USDC, and
|
|
598
|
+
* Get ETH, USDC, and all token balances for EOA and Safe account.
|
|
550
599
|
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
600
|
+
* Tokens are resolved from:
|
|
601
|
+
* 1. Built-in registry of well-known tokens (WETH, wstETH, cbETH)
|
|
602
|
+
* 2. Dynamic discovery from active Morpho positions (loan + collateral tokens)
|
|
603
|
+
* This ensures tokens like LCAP or any new Morpho market tokens appear in balances.
|
|
553
604
|
*/
|
|
554
605
|
async getBalances() {
|
|
555
606
|
const provider = this.signer.provider;
|
|
@@ -557,7 +608,18 @@ var AgetherClient = class _AgetherClient {
|
|
|
557
608
|
const usdc = new Contract(this.config.contracts.usdc, ERC20_ABI, provider);
|
|
558
609
|
const ethBal = await provider.getBalance(eoaAddr);
|
|
559
610
|
const usdcBal = await usdc.balanceOf(eoaAddr);
|
|
560
|
-
const knownTokens =
|
|
611
|
+
const knownTokens = {
|
|
612
|
+
...KNOWN_TOKENS[this.config.chainId] ?? {}
|
|
613
|
+
};
|
|
614
|
+
try {
|
|
615
|
+
const positions = await this._discoverPositionTokens();
|
|
616
|
+
for (const [symbol, info] of Object.entries(positions)) {
|
|
617
|
+
if (!knownTokens[symbol]) {
|
|
618
|
+
knownTokens[symbol] = info;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
561
623
|
const eoaCollateral = {};
|
|
562
624
|
for (const [symbol, info] of Object.entries(knownTokens)) {
|
|
563
625
|
try {
|
|
@@ -1177,7 +1239,7 @@ var MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000
|
|
|
1177
1239
|
var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
|
|
1178
1240
|
var morphoIface = new ethers2.Interface(MORPHO_BLUE_ABI);
|
|
1179
1241
|
var erc20Iface2 = new ethers2.Interface(ERC20_ABI);
|
|
1180
|
-
var
|
|
1242
|
+
var _MorphoClient = class _MorphoClient {
|
|
1181
1243
|
constructor(config) {
|
|
1182
1244
|
/** Market params cache: keyed by market uniqueKey (bytes32 hash) */
|
|
1183
1245
|
this._marketCache = /* @__PURE__ */ new Map();
|
|
@@ -2553,9 +2615,17 @@ var MorphoClient = class {
|
|
|
2553
2615
|
const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
|
|
2554
2616
|
approveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1", loanDecimals);
|
|
2555
2617
|
} else {
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2618
|
+
const marketId = ethers2.keccak256(ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
2619
|
+
["address", "address", "address", "address", "uint256"],
|
|
2620
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
2621
|
+
));
|
|
2622
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
2623
|
+
repayShares = BigInt(pos.borrowShares);
|
|
2624
|
+
repayAssets = 0n;
|
|
2625
|
+
const onChainMkt = await this.morphoBlue.market(marketId);
|
|
2626
|
+
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
2627
|
+
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
2628
|
+
approveAmount = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : ethers2.parseUnits("1", loanDecimals);
|
|
2559
2629
|
}
|
|
2560
2630
|
} else {
|
|
2561
2631
|
repayAssets = ethers2.parseUnits(amount, loanDecimals);
|
|
@@ -2564,20 +2634,32 @@ var MorphoClient = class {
|
|
|
2564
2634
|
}
|
|
2565
2635
|
const loanContract = new Contract2(loanTokenAddr, ERC20_ABI, this._signer);
|
|
2566
2636
|
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
2567
|
-
|
|
2568
|
-
|
|
2637
|
+
const checkAmount = repayShares > 0n && repayAssets === 0n ? approveAmount * 1005n / 1000n : approveAmount;
|
|
2638
|
+
if (acctBalance < checkAmount) {
|
|
2639
|
+
const shortfall = checkAmount - acctBalance;
|
|
2569
2640
|
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
2570
2641
|
if (eoaBalance < shortfall) {
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2642
|
+
if (repayShares > 0n && acctBalance + eoaBalance > 0n) {
|
|
2643
|
+
if (eoaBalance > 0n) {
|
|
2644
|
+
const transferTx = await loanContract.transfer(acctAddr, eoaBalance);
|
|
2645
|
+
await transferTx.wait();
|
|
2646
|
+
this._refreshSigner();
|
|
2647
|
+
}
|
|
2648
|
+
const newBalance = await loanContract.balanceOf(acctAddr);
|
|
2649
|
+
approveAmount = newBalance;
|
|
2650
|
+
} else {
|
|
2651
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
2652
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
2653
|
+
throw new AgetherError(
|
|
2654
|
+
`Insufficient ${loanSymbol} for repay. Need ~${ethers2.formatUnits(checkAmount, loanDecimals)}, AgentAccount has ${ethers2.formatUnits(acctBalance, loanDecimals)}, EOA has ${ethers2.formatUnits(eoaBalance, loanDecimals)}.`,
|
|
2655
|
+
"INSUFFICIENT_BALANCE"
|
|
2656
|
+
);
|
|
2657
|
+
}
|
|
2658
|
+
} else {
|
|
2659
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
2660
|
+
await transferTx.wait();
|
|
2661
|
+
this._refreshSigner();
|
|
2577
2662
|
}
|
|
2578
|
-
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
2579
|
-
await transferTx.wait();
|
|
2580
|
-
this._refreshSigner();
|
|
2581
2663
|
}
|
|
2582
2664
|
const targets = [loanTokenAddr, morphoAddr];
|
|
2583
2665
|
const values = [0n, 0n];
|
|
@@ -2594,20 +2676,220 @@ var MorphoClient = class {
|
|
|
2594
2676
|
const receipt = await this.batch(targets, values, datas);
|
|
2595
2677
|
let remainingDebt = "0";
|
|
2596
2678
|
try {
|
|
2597
|
-
const
|
|
2598
|
-
|
|
2679
|
+
const markets = await this.getMarkets(true);
|
|
2680
|
+
const mkt = markets.find(
|
|
2681
|
+
(m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase() && m.loanAsset?.address.toLowerCase() === params.loanToken.toLowerCase()
|
|
2682
|
+
);
|
|
2683
|
+
if (mkt) {
|
|
2684
|
+
const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
|
|
2685
|
+
const shares = BigInt(pos.borrowShares);
|
|
2686
|
+
if (shares > 0n) {
|
|
2687
|
+
const onChainMkt = await this.morphoBlue.market(mkt.uniqueKey);
|
|
2688
|
+
const totalAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
2689
|
+
const totalShares = BigInt(onChainMkt.totalBorrowShares);
|
|
2690
|
+
const debtWei = totalShares > 0n ? shares * totalAssets / totalShares : 0n;
|
|
2691
|
+
remainingDebt = ethers2.formatUnits(debtWei, loanDecimals);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2599
2694
|
} catch (e) {
|
|
2600
2695
|
console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
|
|
2601
2696
|
}
|
|
2602
2697
|
return { tx: receipt.hash, amount, remainingDebt };
|
|
2603
2698
|
}
|
|
2699
|
+
// 30 min
|
|
2700
|
+
/**
|
|
2701
|
+
* Fetch native staking/restaking yields for all known LST/LRT tokens
|
|
2702
|
+
* from the DeFi Llama Yields API. Results are cached for 30 minutes.
|
|
2703
|
+
*
|
|
2704
|
+
* @returns Map of token symbol → APY (e.g. { wstETH: 4.56, cbETH: 3.61 })
|
|
2705
|
+
*/
|
|
2706
|
+
async _getLstYields() {
|
|
2707
|
+
if (_MorphoClient._lstYieldCache && Date.now() - _MorphoClient._lstYieldCache.ts < _MorphoClient.LST_CACHE_TTL) {
|
|
2708
|
+
return _MorphoClient._lstYieldCache.data;
|
|
2709
|
+
}
|
|
2710
|
+
const yields = {};
|
|
2711
|
+
try {
|
|
2712
|
+
const resp = await fetch("https://yields.llama.fi/pools", {
|
|
2713
|
+
headers: { "User-Agent": "agether-sdk/1.0" },
|
|
2714
|
+
signal: AbortSignal.timeout(1e4)
|
|
2715
|
+
});
|
|
2716
|
+
if (!resp.ok) {
|
|
2717
|
+
console.warn("[agether] DeFi Llama API returned", resp.status);
|
|
2718
|
+
return yields;
|
|
2719
|
+
}
|
|
2720
|
+
const data = await resp.json();
|
|
2721
|
+
const pools = data?.data ?? [];
|
|
2722
|
+
for (const [morphoSymbol, mapping] of Object.entries(_MorphoClient.LST_PROJECT_MAP)) {
|
|
2723
|
+
const matchingPools = pools.filter(
|
|
2724
|
+
(p) => p.project === mapping.project && p.symbol?.toUpperCase() === mapping.symbol && ["Ethereum", "Base"].includes(p.chain)
|
|
2725
|
+
);
|
|
2726
|
+
if (matchingPools.length > 0) {
|
|
2727
|
+
const best = matchingPools.reduce(
|
|
2728
|
+
(a, b) => (b.tvlUsd ?? 0) > (a.tvlUsd ?? 0) ? b : a
|
|
2729
|
+
);
|
|
2730
|
+
if (best.apy !== void 0 && best.apy > 0) {
|
|
2731
|
+
yields[morphoSymbol] = parseFloat(best.apy.toFixed(4));
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
const knownProjects = new Set(Object.values(_MorphoClient.LST_PROJECT_MAP).map((m) => m.project));
|
|
2736
|
+
const stakingProjects = pools.filter(
|
|
2737
|
+
(p) => p.symbol?.match(/^(ST|WST|CB|R|WE|EZ|RS|SW|M|OS|SFR|O|W?B)ETH$/i) && !knownProjects.has(p.project) && p.tvlUsd > 1e7 && p.apy > 0.5 && ["Ethereum", "Base"].includes(p.chain)
|
|
2738
|
+
);
|
|
2739
|
+
for (const p of stakingProjects) {
|
|
2740
|
+
const sym = p.symbol;
|
|
2741
|
+
if (!yields[sym]) {
|
|
2742
|
+
yields[sym] = parseFloat(p.apy.toFixed(4));
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
} catch (e) {
|
|
2746
|
+
console.warn("[agether] Failed to fetch LST yields:", e instanceof Error ? e.message : e);
|
|
2747
|
+
}
|
|
2748
|
+
_MorphoClient._lstYieldCache = { data: yields, ts: Date.now() };
|
|
2749
|
+
return yields;
|
|
2750
|
+
}
|
|
2751
|
+
/**
|
|
2752
|
+
* Analyze yield spread between LST/LRT collateral yield and Morpho borrow rates.
|
|
2753
|
+
* Positive spread = profitable carry trade (collateral earns more than debt costs).
|
|
2754
|
+
*
|
|
2755
|
+
* @returns Array of markets with yield analysis, sorted by net spread descending.
|
|
2756
|
+
*/
|
|
2757
|
+
async getYieldSpread() {
|
|
2758
|
+
const lstYields = await this._getLstYields();
|
|
2759
|
+
if (Object.keys(lstYields).length === 0) {
|
|
2760
|
+
console.warn("[agether] No LST yield data available \u2014 DeFi Llama may be unreachable");
|
|
2761
|
+
return [];
|
|
2762
|
+
}
|
|
2763
|
+
const lstTokens = Object.keys(lstYields);
|
|
2764
|
+
const allMarkets = [];
|
|
2765
|
+
for (const token of lstTokens) {
|
|
2766
|
+
try {
|
|
2767
|
+
const markets2 = await this.getMarketRates(token);
|
|
2768
|
+
allMarkets.push(...markets2);
|
|
2769
|
+
} catch {
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2773
|
+
const markets = allMarkets.filter((m) => {
|
|
2774
|
+
if (seen.has(m.marketId)) return false;
|
|
2775
|
+
seen.add(m.marketId);
|
|
2776
|
+
return true;
|
|
2777
|
+
});
|
|
2778
|
+
const results = [];
|
|
2779
|
+
for (const mkt of markets) {
|
|
2780
|
+
const collateralYield = lstYields[mkt.collateralToken];
|
|
2781
|
+
if (collateralYield === void 0) continue;
|
|
2782
|
+
const borrowRate = mkt.borrowApy;
|
|
2783
|
+
const netSpread = collateralYield - borrowRate;
|
|
2784
|
+
const lltv = parseFloat(mkt.lltv) / 100;
|
|
2785
|
+
const safeLtv = lltv * 0.8;
|
|
2786
|
+
const maxLeverage = 1 / (1 - safeLtv);
|
|
2787
|
+
const leveragedNetApy = collateralYield * maxLeverage - borrowRate * (maxLeverage - 1);
|
|
2788
|
+
results.push({
|
|
2789
|
+
collateralToken: mkt.collateralToken,
|
|
2790
|
+
loanToken: mkt.loanToken,
|
|
2791
|
+
collateralYield,
|
|
2792
|
+
borrowRate,
|
|
2793
|
+
netSpread,
|
|
2794
|
+
profitable: netSpread > 0,
|
|
2795
|
+
lltv,
|
|
2796
|
+
maxSafeLeverage: parseFloat(maxLeverage.toFixed(2)),
|
|
2797
|
+
leveragedNetApy: parseFloat(leveragedNetApy.toFixed(2)),
|
|
2798
|
+
liquidity: mkt.totalSupplyUsd - mkt.totalBorrowUsd,
|
|
2799
|
+
marketId: mkt.marketId
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
return results.sort((a, b) => b.netSpread - a.netSpread);
|
|
2803
|
+
}
|
|
2604
2804
|
/**
|
|
2605
|
-
*
|
|
2805
|
+
* Execute a leverage loop: deposit collateral → borrow → swap to collateral → deposit again.
|
|
2806
|
+
* Repeats N times to achieve target leverage.
|
|
2606
2807
|
*
|
|
2607
|
-
*
|
|
2808
|
+
* Example: 1 wstETH at 3x leverage = deposit 1 → borrow → swap to ~0.77 wstETH → deposit →
|
|
2809
|
+
* borrow → swap to ~0.6 wstETH → deposit. Total exposure: ~2.37 wstETH, debt: ~1.37 ETH worth.
|
|
2608
2810
|
*
|
|
2609
|
-
* @param
|
|
2811
|
+
* @param collateralToken - LST token to deposit (e.g. 'wstETH')
|
|
2812
|
+
* @param amount - Initial deposit amount
|
|
2813
|
+
* @param targetLeverage - Target leverage multiplier (e.g. 2.0 for 2x)
|
|
2814
|
+
* @param loanToken - Token to borrow (default: inferred from market)
|
|
2815
|
+
* @param maxIterations - Max loop iterations (default: 5, safety cap)
|
|
2610
2816
|
*/
|
|
2817
|
+
async leverageLoop(collateralToken, amount, targetLeverage, loanToken, maxIterations = 5) {
|
|
2818
|
+
if (targetLeverage < 1.1 || targetLeverage > 10) {
|
|
2819
|
+
throw new AgetherError(
|
|
2820
|
+
"Target leverage must be between 1.1x and 10x",
|
|
2821
|
+
"INVALID_LEVERAGE"
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2824
|
+
const spreads = await this.getYieldSpread();
|
|
2825
|
+
const matchingMarket = spreads.find(
|
|
2826
|
+
(s) => s.collateralToken.toLowerCase() === collateralToken.toLowerCase() && (!loanToken || s.loanToken.toLowerCase() === loanToken.toLowerCase())
|
|
2827
|
+
);
|
|
2828
|
+
if (!matchingMarket) {
|
|
2829
|
+
throw new AgetherError(
|
|
2830
|
+
`No LST market found for ${collateralToken}`,
|
|
2831
|
+
"MARKET_NOT_FOUND"
|
|
2832
|
+
);
|
|
2833
|
+
}
|
|
2834
|
+
if (!matchingMarket) {
|
|
2835
|
+
throw new AgetherError(
|
|
2836
|
+
`No yield data available for ${collateralToken}. Ensure DeFi Llama API is reachable.`,
|
|
2837
|
+
"NO_YIELD_DATA"
|
|
2838
|
+
);
|
|
2839
|
+
}
|
|
2840
|
+
if (targetLeverage > matchingMarket.maxSafeLeverage) {
|
|
2841
|
+
throw new AgetherError(
|
|
2842
|
+
`Target leverage ${targetLeverage}x exceeds max safe leverage ${matchingMarket.maxSafeLeverage}x for ${collateralToken}. Max LLTV is ${(matchingMarket.lltv * 100).toFixed(0)}%.`,
|
|
2843
|
+
"LEVERAGE_TOO_HIGH"
|
|
2844
|
+
);
|
|
2845
|
+
}
|
|
2846
|
+
const initialAmount = parseFloat(amount);
|
|
2847
|
+
const iterations = [];
|
|
2848
|
+
let totalCollateral = initialAmount;
|
|
2849
|
+
let totalDebt = 0;
|
|
2850
|
+
let currentAmount = initialAmount;
|
|
2851
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
2852
|
+
const currentLeverage = totalDebt > 0 ? totalCollateral / (totalCollateral - totalDebt) : 1;
|
|
2853
|
+
if (currentLeverage >= targetLeverage * 0.98) break;
|
|
2854
|
+
const borrowAmount = currentAmount * matchingMarket.lltv * 0.8;
|
|
2855
|
+
const swapOutput = borrowAmount * 0.997;
|
|
2856
|
+
iterations.push({
|
|
2857
|
+
iteration: i + 1,
|
|
2858
|
+
deposit: currentAmount,
|
|
2859
|
+
borrow: borrowAmount,
|
|
2860
|
+
swapOutput
|
|
2861
|
+
});
|
|
2862
|
+
totalDebt += borrowAmount;
|
|
2863
|
+
totalCollateral += swapOutput;
|
|
2864
|
+
currentAmount = swapOutput;
|
|
2865
|
+
}
|
|
2866
|
+
const finalLeverage = totalDebt > 0 ? totalCollateral / (totalCollateral - totalDebt) : 1;
|
|
2867
|
+
return {
|
|
2868
|
+
strategy: "leverage_loop",
|
|
2869
|
+
collateralToken: matchingMarket.collateralToken,
|
|
2870
|
+
loanToken: matchingMarket.loanToken,
|
|
2871
|
+
initialDeposit: amount,
|
|
2872
|
+
targetLeverage,
|
|
2873
|
+
achievedLeverage: parseFloat(finalLeverage.toFixed(2)),
|
|
2874
|
+
totalCollateral: parseFloat(totalCollateral.toFixed(6)),
|
|
2875
|
+
totalDebt: parseFloat(totalDebt.toFixed(6)),
|
|
2876
|
+
iterations,
|
|
2877
|
+
estimatedNetApy: parseFloat(
|
|
2878
|
+
(matchingMarket.collateralYield * finalLeverage - matchingMarket.borrowRate * (finalLeverage - 1)).toFixed(2)
|
|
2879
|
+
),
|
|
2880
|
+
healthFactor: parseFloat(
|
|
2881
|
+
(totalCollateral * matchingMarket.lltv / totalDebt).toFixed(2)
|
|
2882
|
+
),
|
|
2883
|
+
warning: "This is a simulation. Actual execution requires DEX swap integration. Real results may differ due to slippage and price movements."
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
/**
|
|
2887
|
+
* Withdraw collateral from Morpho Blue.
|
|
2888
|
+
*
|
|
2889
|
+
* AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
|
|
2890
|
+
*
|
|
2891
|
+
* @param receiver - defaults to EOA wallet
|
|
2892
|
+
*/
|
|
2611
2893
|
async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
|
|
2612
2894
|
const acctAddr = await this.getAccountAddress();
|
|
2613
2895
|
const colInfo = await this._resolveToken(tokenSymbol);
|
|
@@ -3129,6 +3411,31 @@ var MorphoClient = class {
|
|
|
3129
3411
|
return result;
|
|
3130
3412
|
}
|
|
3131
3413
|
};
|
|
3414
|
+
// ════════════════════════════════════════════════════════
|
|
3415
|
+
// Yield Spread Analysis & Leverage
|
|
3416
|
+
// ════════════════════════════════════════════════════════
|
|
3417
|
+
/**
|
|
3418
|
+
* Mapping from Morpho collateral token symbols to DeFi Llama project IDs.
|
|
3419
|
+
* Used to look up native staking/restaking yields from the DeFi Llama Yields API.
|
|
3420
|
+
*/
|
|
3421
|
+
_MorphoClient.LST_PROJECT_MAP = {
|
|
3422
|
+
"wstETH": { project: "lido", symbol: "STETH" },
|
|
3423
|
+
"cbETH": { project: "coinbase-wrapped-staked-eth", symbol: "CBETH" },
|
|
3424
|
+
"rETH": { project: "rocket-pool", symbol: "RETH" },
|
|
3425
|
+
"weETH": { project: "ether.fi-stake", symbol: "WEETH" },
|
|
3426
|
+
"ezETH": { project: "renzo", symbol: "EZETH" },
|
|
3427
|
+
"rsETH": { project: "kelp", symbol: "RSETH" },
|
|
3428
|
+
"swETH": { project: "swell-liquid-staking", symbol: "SWETH" },
|
|
3429
|
+
"mETH": { project: "meth-protocol", symbol: "METH" },
|
|
3430
|
+
"sfrxETH": { project: "frax-ether", symbol: "SFRXETH" },
|
|
3431
|
+
"oETH": { project: "origin-ether", symbol: "OETH" },
|
|
3432
|
+
"ETHx": { project: "stader", symbol: "ETHX" },
|
|
3433
|
+
"wBETH": { project: "binance-staked-eth", symbol: "WBETH" }
|
|
3434
|
+
};
|
|
3435
|
+
/** Cache for LST yields fetched from DeFi Llama. TTL: 30 minutes. */
|
|
3436
|
+
_MorphoClient._lstYieldCache = null;
|
|
3437
|
+
_MorphoClient.LST_CACHE_TTL = 30 * 60 * 1e3;
|
|
3438
|
+
var MorphoClient = _MorphoClient;
|
|
3132
3439
|
|
|
3133
3440
|
// src/clients/ScoringClient.ts
|
|
3134
3441
|
import axios3 from "axios";
|