@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.d.ts
CHANGED
|
@@ -244,10 +244,18 @@ declare class AgetherClient {
|
|
|
244
244
|
/** Check whether the Safe account has been deployed. */
|
|
245
245
|
accountExists(): Promise<boolean>;
|
|
246
246
|
/**
|
|
247
|
-
*
|
|
247
|
+
* Discover token addresses from active Morpho Blue positions.
|
|
248
|
+
* Queries the Morpho GraphQL API for markets involving this agent's account,
|
|
249
|
+
* then extracts collateral and loan token info.
|
|
250
|
+
*/
|
|
251
|
+
private _discoverPositionTokens;
|
|
252
|
+
/**
|
|
253
|
+
* Get ETH, USDC, and all token balances for EOA and Safe account.
|
|
248
254
|
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
255
|
+
* Tokens are resolved from:
|
|
256
|
+
* 1. Built-in registry of well-known tokens (WETH, wstETH, cbETH)
|
|
257
|
+
* 2. Dynamic discovery from active Morpho positions (loan + collateral tokens)
|
|
258
|
+
* This ensures tokens like LCAP or any new Morpho market tokens appear in balances.
|
|
251
259
|
*/
|
|
252
260
|
getBalances(): Promise<BalancesResult>;
|
|
253
261
|
/**
|
|
@@ -561,6 +569,42 @@ interface PayFromYieldResult {
|
|
|
561
569
|
remainingYield: string;
|
|
562
570
|
remainingSupply: string;
|
|
563
571
|
}
|
|
572
|
+
/** Yield spread analysis result for a single market. */
|
|
573
|
+
interface YieldSpreadResult {
|
|
574
|
+
collateralToken: string;
|
|
575
|
+
loanToken: string;
|
|
576
|
+
collateralYield: number;
|
|
577
|
+
borrowRate: number;
|
|
578
|
+
netSpread: number;
|
|
579
|
+
profitable: boolean;
|
|
580
|
+
lltv: number;
|
|
581
|
+
maxSafeLeverage: number;
|
|
582
|
+
leveragedNetApy: number;
|
|
583
|
+
liquidity: number;
|
|
584
|
+
marketId: string;
|
|
585
|
+
}
|
|
586
|
+
/** Single iteration in a leverage loop. */
|
|
587
|
+
interface LeverageIteration {
|
|
588
|
+
iteration: number;
|
|
589
|
+
deposit: number;
|
|
590
|
+
borrow: number;
|
|
591
|
+
swapOutput: number;
|
|
592
|
+
}
|
|
593
|
+
/** Result of a leverage loop simulation. */
|
|
594
|
+
interface LeverageResult {
|
|
595
|
+
strategy: string;
|
|
596
|
+
collateralToken: string;
|
|
597
|
+
loanToken: string;
|
|
598
|
+
initialDeposit: string;
|
|
599
|
+
targetLeverage: number;
|
|
600
|
+
achievedLeverage: number;
|
|
601
|
+
totalCollateral: number;
|
|
602
|
+
totalDebt: number;
|
|
603
|
+
iterations: LeverageIteration[];
|
|
604
|
+
estimatedNetApy: number;
|
|
605
|
+
healthFactor: number;
|
|
606
|
+
warning: string;
|
|
607
|
+
}
|
|
564
608
|
declare class MorphoClient {
|
|
565
609
|
private _signer;
|
|
566
610
|
private provider;
|
|
@@ -890,12 +934,48 @@ declare class MorphoClient {
|
|
|
890
934
|
*/
|
|
891
935
|
repay(amount: string, tokenSymbol?: string, marketParams?: MorphoMarketParams, loanTokenSymbol?: string): Promise<RepayResult>;
|
|
892
936
|
/**
|
|
893
|
-
*
|
|
937
|
+
* Mapping from Morpho collateral token symbols to DeFi Llama project IDs.
|
|
938
|
+
* Used to look up native staking/restaking yields from the DeFi Llama Yields API.
|
|
939
|
+
*/
|
|
940
|
+
private static readonly LST_PROJECT_MAP;
|
|
941
|
+
/** Cache for LST yields fetched from DeFi Llama. TTL: 30 minutes. */
|
|
942
|
+
private static _lstYieldCache;
|
|
943
|
+
private static readonly LST_CACHE_TTL;
|
|
944
|
+
/**
|
|
945
|
+
* Fetch native staking/restaking yields for all known LST/LRT tokens
|
|
946
|
+
* from the DeFi Llama Yields API. Results are cached for 30 minutes.
|
|
894
947
|
*
|
|
895
|
-
*
|
|
948
|
+
* @returns Map of token symbol → APY (e.g. { wstETH: 4.56, cbETH: 3.61 })
|
|
949
|
+
*/
|
|
950
|
+
private _getLstYields;
|
|
951
|
+
/**
|
|
952
|
+
* Analyze yield spread between LST/LRT collateral yield and Morpho borrow rates.
|
|
953
|
+
* Positive spread = profitable carry trade (collateral earns more than debt costs).
|
|
896
954
|
*
|
|
897
|
-
* @
|
|
955
|
+
* @returns Array of markets with yield analysis, sorted by net spread descending.
|
|
898
956
|
*/
|
|
957
|
+
getYieldSpread(): Promise<YieldSpreadResult[]>;
|
|
958
|
+
/**
|
|
959
|
+
* Execute a leverage loop: deposit collateral → borrow → swap to collateral → deposit again.
|
|
960
|
+
* Repeats N times to achieve target leverage.
|
|
961
|
+
*
|
|
962
|
+
* Example: 1 wstETH at 3x leverage = deposit 1 → borrow → swap to ~0.77 wstETH → deposit →
|
|
963
|
+
* borrow → swap to ~0.6 wstETH → deposit. Total exposure: ~2.37 wstETH, debt: ~1.37 ETH worth.
|
|
964
|
+
*
|
|
965
|
+
* @param collateralToken - LST token to deposit (e.g. 'wstETH')
|
|
966
|
+
* @param amount - Initial deposit amount
|
|
967
|
+
* @param targetLeverage - Target leverage multiplier (e.g. 2.0 for 2x)
|
|
968
|
+
* @param loanToken - Token to borrow (default: inferred from market)
|
|
969
|
+
* @param maxIterations - Max loop iterations (default: 5, safety cap)
|
|
970
|
+
*/
|
|
971
|
+
leverageLoop(collateralToken: string, amount: string, targetLeverage: number, loanToken?: string, maxIterations?: number): Promise<LeverageResult>;
|
|
972
|
+
/**
|
|
973
|
+
* Withdraw collateral from Morpho Blue.
|
|
974
|
+
*
|
|
975
|
+
* AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
|
|
976
|
+
*
|
|
977
|
+
* @param receiver - defaults to EOA wallet
|
|
978
|
+
*/
|
|
899
979
|
withdrawCollateral(tokenSymbol: string, amount: string, marketParams?: MorphoMarketParams, receiver?: string): Promise<WithdrawResult>;
|
|
900
980
|
/**
|
|
901
981
|
* Refresh the signer and re-bind contract instances.
|
|
@@ -1589,4 +1669,4 @@ interface RetryOptions {
|
|
|
1589
1669
|
*/
|
|
1590
1670
|
declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
1591
1671
|
|
|
1592
|
-
export { ACCOUNT_FACTORY_ABI, AGENT_REPUTATION_ABI, AGETHER_4337_FACTORY_ABI, AGETHER_8004_SCORER_ABI, AGETHER_8004_VALIDATION_MODULE_ABI, AGETHER_HOOK_MULTIPLEXER_ABI, AgentIdentityClient, type AgentIdentityClientOptions, AgentNotApprovedError, AgetherClient, type AgetherClientOptions, type AgetherConfig, AgetherError, type AgetherSigner, type AgetherViemWallet, type BalancesResult, type BorrowResult, ChainId, type ContractAddresses, type DepositAndBorrowResult, type DepositResult, ENTRYPOINT_V07_ABI, ERC20_ABI, ERC8004_VALIDATION_MODULE_ABI, HOOK_MULTIPLEXER_ABI, IDENTITY_REGISTRY_ABI, InsufficientBalanceError, MORPHO_BLUE_ABI, type MarketFilter, MorphoClient, type MorphoClientConfig, type MorphoMarketInfo, type MorphoMarketParams, type MorphoPosition, type PayFromYieldResult, type PaymentRequirements, type PositionResult, type RegisterResult, type RepayResult, type RetryOptions, SAFE7579_ACCOUNT_ABI, SAFE_AGENT_FACTORY_ABI, type ScoreAttestation, type ScoreResult, ScoringClient, type ScoringClientConfig, ScoringRejectedError, type SpendingTracker, type StatusResult, type SupplyAssetResult, type SupplyPositionResult, type TransactionResult, VALIDATION_REGISTRY_ABI, type WithdrawFromAccountResult, type WithdrawResult, type WithdrawSupplyResult, X402Client, type X402Config, type X402PaymentRequest, type X402PaymentResult, type X402Response, bpsToRate, createConfig, formatAPR, formatAddress, formatHealthFactor, formatPercent, formatTimestamp, formatUSD, formatUnits, getContractAddresses, getDefaultConfig, getMorphoBlueAddress, getUSDCAddress, parseUnits, rateToBps, withRetry };
|
|
1672
|
+
export { ACCOUNT_FACTORY_ABI, AGENT_REPUTATION_ABI, AGETHER_4337_FACTORY_ABI, AGETHER_8004_SCORER_ABI, AGETHER_8004_VALIDATION_MODULE_ABI, AGETHER_HOOK_MULTIPLEXER_ABI, AgentIdentityClient, type AgentIdentityClientOptions, AgentNotApprovedError, AgetherClient, type AgetherClientOptions, type AgetherConfig, AgetherError, type AgetherSigner, type AgetherViemWallet, type BalancesResult, type BorrowResult, ChainId, type ContractAddresses, type DepositAndBorrowResult, type DepositResult, ENTRYPOINT_V07_ABI, ERC20_ABI, ERC8004_VALIDATION_MODULE_ABI, HOOK_MULTIPLEXER_ABI, IDENTITY_REGISTRY_ABI, InsufficientBalanceError, type LeverageIteration, type LeverageResult, MORPHO_BLUE_ABI, type MarketFilter, MorphoClient, type MorphoClientConfig, type MorphoMarketInfo, type MorphoMarketParams, type MorphoPosition, type PayFromYieldResult, type PaymentRequirements, type PositionResult, type RegisterResult, type RepayResult, type RetryOptions, SAFE7579_ACCOUNT_ABI, SAFE_AGENT_FACTORY_ABI, type ScoreAttestation, type ScoreResult, ScoringClient, type ScoringClientConfig, ScoringRejectedError, type SpendingTracker, type StatusResult, type SupplyAssetResult, type SupplyPositionResult, type TransactionResult, VALIDATION_REGISTRY_ABI, type WithdrawFromAccountResult, type WithdrawResult, type WithdrawSupplyResult, X402Client, type X402Config, type X402PaymentRequest, type X402PaymentResult, type X402Response, type YieldSpreadResult, bpsToRate, createConfig, formatAPR, formatAddress, formatHealthFactor, formatPercent, formatTimestamp, formatUSD, formatUnits, getContractAddresses, getDefaultConfig, getMorphoBlueAddress, getUSDCAddress, parseUnits, rateToBps, withRetry };
|
package/dist/index.js
CHANGED
|
@@ -619,13 +619,64 @@ var AgetherClient = class _AgetherClient {
|
|
|
619
619
|
return this.agether4337Factory.accountExists(id);
|
|
620
620
|
}
|
|
621
621
|
// ════════════════════════════════════════════════════════
|
|
622
|
+
// Token Discovery (for dynamic balance queries)
|
|
623
|
+
// ════════════════════════════════════════════════════════
|
|
624
|
+
/**
|
|
625
|
+
* Discover token addresses from active Morpho Blue positions.
|
|
626
|
+
* Queries the Morpho GraphQL API for markets involving this agent's account,
|
|
627
|
+
* then extracts collateral and loan token info.
|
|
628
|
+
*/
|
|
629
|
+
async _discoverPositionTokens() {
|
|
630
|
+
const tokens = {};
|
|
631
|
+
try {
|
|
632
|
+
const acctAddr = await this.getAccountAddress();
|
|
633
|
+
const chainId = this.config.chainId;
|
|
634
|
+
const query = `{
|
|
635
|
+
marketPositions(
|
|
636
|
+
where: { userAddress_in: ["${acctAddr.toLowerCase()}"], chainId_in: [${chainId}] }
|
|
637
|
+
first: 20
|
|
638
|
+
) {
|
|
639
|
+
items {
|
|
640
|
+
market {
|
|
641
|
+
collateralAsset { address symbol decimals }
|
|
642
|
+
loanAsset { address symbol decimals }
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}`;
|
|
647
|
+
const resp = await fetch("https://blue-api.morpho.org/graphql", {
|
|
648
|
+
method: "POST",
|
|
649
|
+
headers: { "Content-Type": "application/json" },
|
|
650
|
+
body: JSON.stringify({ query }),
|
|
651
|
+
signal: AbortSignal.timeout(5e3)
|
|
652
|
+
});
|
|
653
|
+
if (!resp.ok) return tokens;
|
|
654
|
+
const data = await resp.json();
|
|
655
|
+
const items = data?.data?.marketPositions?.items ?? [];
|
|
656
|
+
for (const item of items) {
|
|
657
|
+
const col = item?.market?.collateralAsset;
|
|
658
|
+
const loan = item?.market?.loanAsset;
|
|
659
|
+
if (col?.symbol && col?.address) {
|
|
660
|
+
tokens[col.symbol] = { address: col.address, symbol: col.symbol, decimals: col.decimals ?? 18 };
|
|
661
|
+
}
|
|
662
|
+
if (loan?.symbol && loan?.address) {
|
|
663
|
+
tokens[loan.symbol] = { address: loan.address, symbol: loan.symbol, decimals: loan.decimals ?? 18 };
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
} catch {
|
|
667
|
+
}
|
|
668
|
+
return tokens;
|
|
669
|
+
}
|
|
670
|
+
// ════════════════════════════════════════════════════════
|
|
622
671
|
// Balances
|
|
623
672
|
// ════════════════════════════════════════════════════════
|
|
624
673
|
/**
|
|
625
|
-
* Get ETH, USDC, and
|
|
674
|
+
* Get ETH, USDC, and all token balances for EOA and Safe account.
|
|
626
675
|
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
676
|
+
* Tokens are resolved from:
|
|
677
|
+
* 1. Built-in registry of well-known tokens (WETH, wstETH, cbETH)
|
|
678
|
+
* 2. Dynamic discovery from active Morpho positions (loan + collateral tokens)
|
|
679
|
+
* This ensures tokens like LCAP or any new Morpho market tokens appear in balances.
|
|
629
680
|
*/
|
|
630
681
|
async getBalances() {
|
|
631
682
|
const provider = this.signer.provider;
|
|
@@ -633,7 +684,18 @@ var AgetherClient = class _AgetherClient {
|
|
|
633
684
|
const usdc = new import_ethers.Contract(this.config.contracts.usdc, ERC20_ABI, provider);
|
|
634
685
|
const ethBal = await provider.getBalance(eoaAddr);
|
|
635
686
|
const usdcBal = await usdc.balanceOf(eoaAddr);
|
|
636
|
-
const knownTokens =
|
|
687
|
+
const knownTokens = {
|
|
688
|
+
...KNOWN_TOKENS[this.config.chainId] ?? {}
|
|
689
|
+
};
|
|
690
|
+
try {
|
|
691
|
+
const positions = await this._discoverPositionTokens();
|
|
692
|
+
for (const [symbol, info] of Object.entries(positions)) {
|
|
693
|
+
if (!knownTokens[symbol]) {
|
|
694
|
+
knownTokens[symbol] = info;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
} catch {
|
|
698
|
+
}
|
|
637
699
|
const eoaCollateral = {};
|
|
638
700
|
for (const [symbol, info] of Object.entries(knownTokens)) {
|
|
639
701
|
try {
|
|
@@ -1253,7 +1315,7 @@ var MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000
|
|
|
1253
1315
|
var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
|
|
1254
1316
|
var morphoIface = new import_ethers2.ethers.Interface(MORPHO_BLUE_ABI);
|
|
1255
1317
|
var erc20Iface2 = new import_ethers2.ethers.Interface(ERC20_ABI);
|
|
1256
|
-
var
|
|
1318
|
+
var _MorphoClient = class _MorphoClient {
|
|
1257
1319
|
constructor(config) {
|
|
1258
1320
|
/** Market params cache: keyed by market uniqueKey (bytes32 hash) */
|
|
1259
1321
|
this._marketCache = /* @__PURE__ */ new Map();
|
|
@@ -2629,9 +2691,17 @@ var MorphoClient = class {
|
|
|
2629
2691
|
const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
|
|
2630
2692
|
approveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1", loanDecimals);
|
|
2631
2693
|
} else {
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2694
|
+
const marketId = import_ethers2.ethers.keccak256(import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
|
|
2695
|
+
["address", "address", "address", "address", "uint256"],
|
|
2696
|
+
[params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
|
|
2697
|
+
));
|
|
2698
|
+
const pos = await this.morphoBlue.position(marketId, acctAddr);
|
|
2699
|
+
repayShares = BigInt(pos.borrowShares);
|
|
2700
|
+
repayAssets = 0n;
|
|
2701
|
+
const onChainMkt = await this.morphoBlue.market(marketId);
|
|
2702
|
+
const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
2703
|
+
const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
|
|
2704
|
+
approveAmount = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : import_ethers2.ethers.parseUnits("1", loanDecimals);
|
|
2635
2705
|
}
|
|
2636
2706
|
} else {
|
|
2637
2707
|
repayAssets = import_ethers2.ethers.parseUnits(amount, loanDecimals);
|
|
@@ -2640,20 +2710,32 @@ var MorphoClient = class {
|
|
|
2640
2710
|
}
|
|
2641
2711
|
const loanContract = new import_ethers2.Contract(loanTokenAddr, ERC20_ABI, this._signer);
|
|
2642
2712
|
const acctBalance = await loanContract.balanceOf(acctAddr);
|
|
2643
|
-
|
|
2644
|
-
|
|
2713
|
+
const checkAmount = repayShares > 0n && repayAssets === 0n ? approveAmount * 1005n / 1000n : approveAmount;
|
|
2714
|
+
if (acctBalance < checkAmount) {
|
|
2715
|
+
const shortfall = checkAmount - acctBalance;
|
|
2645
2716
|
const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
|
|
2646
2717
|
if (eoaBalance < shortfall) {
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2718
|
+
if (repayShares > 0n && acctBalance + eoaBalance > 0n) {
|
|
2719
|
+
if (eoaBalance > 0n) {
|
|
2720
|
+
const transferTx = await loanContract.transfer(acctAddr, eoaBalance);
|
|
2721
|
+
await transferTx.wait();
|
|
2722
|
+
this._refreshSigner();
|
|
2723
|
+
}
|
|
2724
|
+
const newBalance = await loanContract.balanceOf(acctAddr);
|
|
2725
|
+
approveAmount = newBalance;
|
|
2726
|
+
} else {
|
|
2727
|
+
const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
|
|
2728
|
+
const loanSymbol = loanInfo?.symbol ?? "loan token";
|
|
2729
|
+
throw new AgetherError(
|
|
2730
|
+
`Insufficient ${loanSymbol} for repay. Need ~${import_ethers2.ethers.formatUnits(checkAmount, loanDecimals)}, AgentAccount has ${import_ethers2.ethers.formatUnits(acctBalance, loanDecimals)}, EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, loanDecimals)}.`,
|
|
2731
|
+
"INSUFFICIENT_BALANCE"
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
} else {
|
|
2735
|
+
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
2736
|
+
await transferTx.wait();
|
|
2737
|
+
this._refreshSigner();
|
|
2653
2738
|
}
|
|
2654
|
-
const transferTx = await loanContract.transfer(acctAddr, shortfall);
|
|
2655
|
-
await transferTx.wait();
|
|
2656
|
-
this._refreshSigner();
|
|
2657
2739
|
}
|
|
2658
2740
|
const targets = [loanTokenAddr, morphoAddr];
|
|
2659
2741
|
const values = [0n, 0n];
|
|
@@ -2670,20 +2752,220 @@ var MorphoClient = class {
|
|
|
2670
2752
|
const receipt = await this.batch(targets, values, datas);
|
|
2671
2753
|
let remainingDebt = "0";
|
|
2672
2754
|
try {
|
|
2673
|
-
const
|
|
2674
|
-
|
|
2755
|
+
const markets = await this.getMarkets(true);
|
|
2756
|
+
const mkt = markets.find(
|
|
2757
|
+
(m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase() && m.loanAsset?.address.toLowerCase() === params.loanToken.toLowerCase()
|
|
2758
|
+
);
|
|
2759
|
+
if (mkt) {
|
|
2760
|
+
const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
|
|
2761
|
+
const shares = BigInt(pos.borrowShares);
|
|
2762
|
+
if (shares > 0n) {
|
|
2763
|
+
const onChainMkt = await this.morphoBlue.market(mkt.uniqueKey);
|
|
2764
|
+
const totalAssets = BigInt(onChainMkt.totalBorrowAssets);
|
|
2765
|
+
const totalShares = BigInt(onChainMkt.totalBorrowShares);
|
|
2766
|
+
const debtWei = totalShares > 0n ? shares * totalAssets / totalShares : 0n;
|
|
2767
|
+
remainingDebt = import_ethers2.ethers.formatUnits(debtWei, loanDecimals);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2675
2770
|
} catch (e) {
|
|
2676
2771
|
console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
|
|
2677
2772
|
}
|
|
2678
2773
|
return { tx: receipt.hash, amount, remainingDebt };
|
|
2679
2774
|
}
|
|
2775
|
+
// 30 min
|
|
2776
|
+
/**
|
|
2777
|
+
* Fetch native staking/restaking yields for all known LST/LRT tokens
|
|
2778
|
+
* from the DeFi Llama Yields API. Results are cached for 30 minutes.
|
|
2779
|
+
*
|
|
2780
|
+
* @returns Map of token symbol → APY (e.g. { wstETH: 4.56, cbETH: 3.61 })
|
|
2781
|
+
*/
|
|
2782
|
+
async _getLstYields() {
|
|
2783
|
+
if (_MorphoClient._lstYieldCache && Date.now() - _MorphoClient._lstYieldCache.ts < _MorphoClient.LST_CACHE_TTL) {
|
|
2784
|
+
return _MorphoClient._lstYieldCache.data;
|
|
2785
|
+
}
|
|
2786
|
+
const yields = {};
|
|
2787
|
+
try {
|
|
2788
|
+
const resp = await fetch("https://yields.llama.fi/pools", {
|
|
2789
|
+
headers: { "User-Agent": "agether-sdk/1.0" },
|
|
2790
|
+
signal: AbortSignal.timeout(1e4)
|
|
2791
|
+
});
|
|
2792
|
+
if (!resp.ok) {
|
|
2793
|
+
console.warn("[agether] DeFi Llama API returned", resp.status);
|
|
2794
|
+
return yields;
|
|
2795
|
+
}
|
|
2796
|
+
const data = await resp.json();
|
|
2797
|
+
const pools = data?.data ?? [];
|
|
2798
|
+
for (const [morphoSymbol, mapping] of Object.entries(_MorphoClient.LST_PROJECT_MAP)) {
|
|
2799
|
+
const matchingPools = pools.filter(
|
|
2800
|
+
(p) => p.project === mapping.project && p.symbol?.toUpperCase() === mapping.symbol && ["Ethereum", "Base"].includes(p.chain)
|
|
2801
|
+
);
|
|
2802
|
+
if (matchingPools.length > 0) {
|
|
2803
|
+
const best = matchingPools.reduce(
|
|
2804
|
+
(a, b) => (b.tvlUsd ?? 0) > (a.tvlUsd ?? 0) ? b : a
|
|
2805
|
+
);
|
|
2806
|
+
if (best.apy !== void 0 && best.apy > 0) {
|
|
2807
|
+
yields[morphoSymbol] = parseFloat(best.apy.toFixed(4));
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
const knownProjects = new Set(Object.values(_MorphoClient.LST_PROJECT_MAP).map((m) => m.project));
|
|
2812
|
+
const stakingProjects = pools.filter(
|
|
2813
|
+
(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)
|
|
2814
|
+
);
|
|
2815
|
+
for (const p of stakingProjects) {
|
|
2816
|
+
const sym = p.symbol;
|
|
2817
|
+
if (!yields[sym]) {
|
|
2818
|
+
yields[sym] = parseFloat(p.apy.toFixed(4));
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
} catch (e) {
|
|
2822
|
+
console.warn("[agether] Failed to fetch LST yields:", e instanceof Error ? e.message : e);
|
|
2823
|
+
}
|
|
2824
|
+
_MorphoClient._lstYieldCache = { data: yields, ts: Date.now() };
|
|
2825
|
+
return yields;
|
|
2826
|
+
}
|
|
2827
|
+
/**
|
|
2828
|
+
* Analyze yield spread between LST/LRT collateral yield and Morpho borrow rates.
|
|
2829
|
+
* Positive spread = profitable carry trade (collateral earns more than debt costs).
|
|
2830
|
+
*
|
|
2831
|
+
* @returns Array of markets with yield analysis, sorted by net spread descending.
|
|
2832
|
+
*/
|
|
2833
|
+
async getYieldSpread() {
|
|
2834
|
+
const lstYields = await this._getLstYields();
|
|
2835
|
+
if (Object.keys(lstYields).length === 0) {
|
|
2836
|
+
console.warn("[agether] No LST yield data available \u2014 DeFi Llama may be unreachable");
|
|
2837
|
+
return [];
|
|
2838
|
+
}
|
|
2839
|
+
const lstTokens = Object.keys(lstYields);
|
|
2840
|
+
const allMarkets = [];
|
|
2841
|
+
for (const token of lstTokens) {
|
|
2842
|
+
try {
|
|
2843
|
+
const markets2 = await this.getMarketRates(token);
|
|
2844
|
+
allMarkets.push(...markets2);
|
|
2845
|
+
} catch {
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2849
|
+
const markets = allMarkets.filter((m) => {
|
|
2850
|
+
if (seen.has(m.marketId)) return false;
|
|
2851
|
+
seen.add(m.marketId);
|
|
2852
|
+
return true;
|
|
2853
|
+
});
|
|
2854
|
+
const results = [];
|
|
2855
|
+
for (const mkt of markets) {
|
|
2856
|
+
const collateralYield = lstYields[mkt.collateralToken];
|
|
2857
|
+
if (collateralYield === void 0) continue;
|
|
2858
|
+
const borrowRate = mkt.borrowApy;
|
|
2859
|
+
const netSpread = collateralYield - borrowRate;
|
|
2860
|
+
const lltv = parseFloat(mkt.lltv) / 100;
|
|
2861
|
+
const safeLtv = lltv * 0.8;
|
|
2862
|
+
const maxLeverage = 1 / (1 - safeLtv);
|
|
2863
|
+
const leveragedNetApy = collateralYield * maxLeverage - borrowRate * (maxLeverage - 1);
|
|
2864
|
+
results.push({
|
|
2865
|
+
collateralToken: mkt.collateralToken,
|
|
2866
|
+
loanToken: mkt.loanToken,
|
|
2867
|
+
collateralYield,
|
|
2868
|
+
borrowRate,
|
|
2869
|
+
netSpread,
|
|
2870
|
+
profitable: netSpread > 0,
|
|
2871
|
+
lltv,
|
|
2872
|
+
maxSafeLeverage: parseFloat(maxLeverage.toFixed(2)),
|
|
2873
|
+
leveragedNetApy: parseFloat(leveragedNetApy.toFixed(2)),
|
|
2874
|
+
liquidity: mkt.totalSupplyUsd - mkt.totalBorrowUsd,
|
|
2875
|
+
marketId: mkt.marketId
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
return results.sort((a, b) => b.netSpread - a.netSpread);
|
|
2879
|
+
}
|
|
2680
2880
|
/**
|
|
2681
|
-
*
|
|
2881
|
+
* Execute a leverage loop: deposit collateral → borrow → swap to collateral → deposit again.
|
|
2882
|
+
* Repeats N times to achieve target leverage.
|
|
2682
2883
|
*
|
|
2683
|
-
*
|
|
2884
|
+
* Example: 1 wstETH at 3x leverage = deposit 1 → borrow → swap to ~0.77 wstETH → deposit →
|
|
2885
|
+
* borrow → swap to ~0.6 wstETH → deposit. Total exposure: ~2.37 wstETH, debt: ~1.37 ETH worth.
|
|
2684
2886
|
*
|
|
2685
|
-
* @param
|
|
2887
|
+
* @param collateralToken - LST token to deposit (e.g. 'wstETH')
|
|
2888
|
+
* @param amount - Initial deposit amount
|
|
2889
|
+
* @param targetLeverage - Target leverage multiplier (e.g. 2.0 for 2x)
|
|
2890
|
+
* @param loanToken - Token to borrow (default: inferred from market)
|
|
2891
|
+
* @param maxIterations - Max loop iterations (default: 5, safety cap)
|
|
2686
2892
|
*/
|
|
2893
|
+
async leverageLoop(collateralToken, amount, targetLeverage, loanToken, maxIterations = 5) {
|
|
2894
|
+
if (targetLeverage < 1.1 || targetLeverage > 10) {
|
|
2895
|
+
throw new AgetherError(
|
|
2896
|
+
"Target leverage must be between 1.1x and 10x",
|
|
2897
|
+
"INVALID_LEVERAGE"
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
const spreads = await this.getYieldSpread();
|
|
2901
|
+
const matchingMarket = spreads.find(
|
|
2902
|
+
(s) => s.collateralToken.toLowerCase() === collateralToken.toLowerCase() && (!loanToken || s.loanToken.toLowerCase() === loanToken.toLowerCase())
|
|
2903
|
+
);
|
|
2904
|
+
if (!matchingMarket) {
|
|
2905
|
+
throw new AgetherError(
|
|
2906
|
+
`No LST market found for ${collateralToken}`,
|
|
2907
|
+
"MARKET_NOT_FOUND"
|
|
2908
|
+
);
|
|
2909
|
+
}
|
|
2910
|
+
if (!matchingMarket) {
|
|
2911
|
+
throw new AgetherError(
|
|
2912
|
+
`No yield data available for ${collateralToken}. Ensure DeFi Llama API is reachable.`,
|
|
2913
|
+
"NO_YIELD_DATA"
|
|
2914
|
+
);
|
|
2915
|
+
}
|
|
2916
|
+
if (targetLeverage > matchingMarket.maxSafeLeverage) {
|
|
2917
|
+
throw new AgetherError(
|
|
2918
|
+
`Target leverage ${targetLeverage}x exceeds max safe leverage ${matchingMarket.maxSafeLeverage}x for ${collateralToken}. Max LLTV is ${(matchingMarket.lltv * 100).toFixed(0)}%.`,
|
|
2919
|
+
"LEVERAGE_TOO_HIGH"
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
const initialAmount = parseFloat(amount);
|
|
2923
|
+
const iterations = [];
|
|
2924
|
+
let totalCollateral = initialAmount;
|
|
2925
|
+
let totalDebt = 0;
|
|
2926
|
+
let currentAmount = initialAmount;
|
|
2927
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
2928
|
+
const currentLeverage = totalDebt > 0 ? totalCollateral / (totalCollateral - totalDebt) : 1;
|
|
2929
|
+
if (currentLeverage >= targetLeverage * 0.98) break;
|
|
2930
|
+
const borrowAmount = currentAmount * matchingMarket.lltv * 0.8;
|
|
2931
|
+
const swapOutput = borrowAmount * 0.997;
|
|
2932
|
+
iterations.push({
|
|
2933
|
+
iteration: i + 1,
|
|
2934
|
+
deposit: currentAmount,
|
|
2935
|
+
borrow: borrowAmount,
|
|
2936
|
+
swapOutput
|
|
2937
|
+
});
|
|
2938
|
+
totalDebt += borrowAmount;
|
|
2939
|
+
totalCollateral += swapOutput;
|
|
2940
|
+
currentAmount = swapOutput;
|
|
2941
|
+
}
|
|
2942
|
+
const finalLeverage = totalDebt > 0 ? totalCollateral / (totalCollateral - totalDebt) : 1;
|
|
2943
|
+
return {
|
|
2944
|
+
strategy: "leverage_loop",
|
|
2945
|
+
collateralToken: matchingMarket.collateralToken,
|
|
2946
|
+
loanToken: matchingMarket.loanToken,
|
|
2947
|
+
initialDeposit: amount,
|
|
2948
|
+
targetLeverage,
|
|
2949
|
+
achievedLeverage: parseFloat(finalLeverage.toFixed(2)),
|
|
2950
|
+
totalCollateral: parseFloat(totalCollateral.toFixed(6)),
|
|
2951
|
+
totalDebt: parseFloat(totalDebt.toFixed(6)),
|
|
2952
|
+
iterations,
|
|
2953
|
+
estimatedNetApy: parseFloat(
|
|
2954
|
+
(matchingMarket.collateralYield * finalLeverage - matchingMarket.borrowRate * (finalLeverage - 1)).toFixed(2)
|
|
2955
|
+
),
|
|
2956
|
+
healthFactor: parseFloat(
|
|
2957
|
+
(totalCollateral * matchingMarket.lltv / totalDebt).toFixed(2)
|
|
2958
|
+
),
|
|
2959
|
+
warning: "This is a simulation. Actual execution requires DEX swap integration. Real results may differ due to slippage and price movements."
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
/**
|
|
2963
|
+
* Withdraw collateral from Morpho Blue.
|
|
2964
|
+
*
|
|
2965
|
+
* AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
|
|
2966
|
+
*
|
|
2967
|
+
* @param receiver - defaults to EOA wallet
|
|
2968
|
+
*/
|
|
2687
2969
|
async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
|
|
2688
2970
|
const acctAddr = await this.getAccountAddress();
|
|
2689
2971
|
const colInfo = await this._resolveToken(tokenSymbol);
|
|
@@ -3205,6 +3487,31 @@ var MorphoClient = class {
|
|
|
3205
3487
|
return result;
|
|
3206
3488
|
}
|
|
3207
3489
|
};
|
|
3490
|
+
// ════════════════════════════════════════════════════════
|
|
3491
|
+
// Yield Spread Analysis & Leverage
|
|
3492
|
+
// ════════════════════════════════════════════════════════
|
|
3493
|
+
/**
|
|
3494
|
+
* Mapping from Morpho collateral token symbols to DeFi Llama project IDs.
|
|
3495
|
+
* Used to look up native staking/restaking yields from the DeFi Llama Yields API.
|
|
3496
|
+
*/
|
|
3497
|
+
_MorphoClient.LST_PROJECT_MAP = {
|
|
3498
|
+
"wstETH": { project: "lido", symbol: "STETH" },
|
|
3499
|
+
"cbETH": { project: "coinbase-wrapped-staked-eth", symbol: "CBETH" },
|
|
3500
|
+
"rETH": { project: "rocket-pool", symbol: "RETH" },
|
|
3501
|
+
"weETH": { project: "ether.fi-stake", symbol: "WEETH" },
|
|
3502
|
+
"ezETH": { project: "renzo", symbol: "EZETH" },
|
|
3503
|
+
"rsETH": { project: "kelp", symbol: "RSETH" },
|
|
3504
|
+
"swETH": { project: "swell-liquid-staking", symbol: "SWETH" },
|
|
3505
|
+
"mETH": { project: "meth-protocol", symbol: "METH" },
|
|
3506
|
+
"sfrxETH": { project: "frax-ether", symbol: "SFRXETH" },
|
|
3507
|
+
"oETH": { project: "origin-ether", symbol: "OETH" },
|
|
3508
|
+
"ETHx": { project: "stader", symbol: "ETHX" },
|
|
3509
|
+
"wBETH": { project: "binance-staked-eth", symbol: "WBETH" }
|
|
3510
|
+
};
|
|
3511
|
+
/** Cache for LST yields fetched from DeFi Llama. TTL: 30 minutes. */
|
|
3512
|
+
_MorphoClient._lstYieldCache = null;
|
|
3513
|
+
_MorphoClient.LST_CACHE_TTL = 30 * 60 * 1e3;
|
|
3514
|
+
var MorphoClient = _MorphoClient;
|
|
3208
3515
|
|
|
3209
3516
|
// src/clients/ScoringClient.ts
|
|
3210
3517
|
var import_axios3 = __toESM(require("axios"));
|