@agether/sdk 2.16.1 → 2.17.2

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
@@ -340,7 +340,7 @@ var init_MorphoClient = __esm({
340
340
  MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
341
341
  morphoIface = new import_ethers.ethers.Interface(MORPHO_BLUE_ABI);
342
342
  erc20Iface = new import_ethers.ethers.Interface(ERC20_ABI);
343
- MorphoClient = class {
343
+ MorphoClient = class _MorphoClient {
344
344
  constructor(config) {
345
345
  /** Market params cache: keyed by market uniqueKey (bytes32 hash) */
346
346
  this._marketCache = /* @__PURE__ */ new Map();
@@ -1716,9 +1716,17 @@ var init_MorphoClient = __esm({
1716
1716
  const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
1717
1717
  approveAmount = estimated > 0n ? estimated : import_ethers.ethers.parseUnits("1", loanDecimals);
1718
1718
  } else {
1719
- repayAssets = import_ethers.ethers.parseUnits("999999", loanDecimals);
1720
- repayShares = 0n;
1721
- approveAmount = repayAssets;
1719
+ const marketId = import_ethers.ethers.keccak256(import_ethers.ethers.AbiCoder.defaultAbiCoder().encode(
1720
+ ["address", "address", "address", "address", "uint256"],
1721
+ [params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv]
1722
+ ));
1723
+ const pos = await this.morphoBlue.position(marketId, acctAddr);
1724
+ repayShares = BigInt(pos.borrowShares);
1725
+ repayAssets = 0n;
1726
+ const onChainMkt = await this.morphoBlue.market(marketId);
1727
+ const totalBorrowAssets = BigInt(onChainMkt.totalBorrowAssets);
1728
+ const totalBorrowShares = BigInt(onChainMkt.totalBorrowShares);
1729
+ approveAmount = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : import_ethers.ethers.parseUnits("1", loanDecimals);
1722
1730
  }
1723
1731
  } else {
1724
1732
  repayAssets = import_ethers.ethers.parseUnits(amount, loanDecimals);
@@ -1727,20 +1735,32 @@ var init_MorphoClient = __esm({
1727
1735
  }
1728
1736
  const loanContract = new import_ethers.Contract(loanTokenAddr, ERC20_ABI, this._signer);
1729
1737
  const acctBalance = await loanContract.balanceOf(acctAddr);
1730
- if (acctBalance < approveAmount) {
1731
- const shortfall = approveAmount - acctBalance;
1738
+ const checkAmount = repayShares > 0n && repayAssets === 0n ? approveAmount * 1005n / 1000n : approveAmount;
1739
+ if (acctBalance < checkAmount) {
1740
+ const shortfall = checkAmount - acctBalance;
1732
1741
  const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
1733
1742
  if (eoaBalance < shortfall) {
1734
- const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
1735
- const loanSymbol = loanInfo?.symbol ?? "loan token";
1736
- throw new AgetherError(
1737
- `Insufficient ${loanSymbol} for repay. Need ${import_ethers.ethers.formatUnits(approveAmount, loanDecimals)}, AgentAccount has ${import_ethers.ethers.formatUnits(acctBalance, loanDecimals)}, EOA has ${import_ethers.ethers.formatUnits(eoaBalance, loanDecimals)}.`,
1738
- "INSUFFICIENT_BALANCE"
1739
- );
1743
+ if (repayShares > 0n && acctBalance + eoaBalance > 0n) {
1744
+ if (eoaBalance > 0n) {
1745
+ const transferTx = await loanContract.transfer(acctAddr, eoaBalance);
1746
+ await transferTx.wait();
1747
+ this._refreshSigner();
1748
+ }
1749
+ const newBalance = await loanContract.balanceOf(acctAddr);
1750
+ approveAmount = newBalance;
1751
+ } else {
1752
+ const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
1753
+ const loanSymbol = loanInfo?.symbol ?? "loan token";
1754
+ throw new AgetherError(
1755
+ `Insufficient ${loanSymbol} for repay. Need ~${import_ethers.ethers.formatUnits(checkAmount, loanDecimals)}, AgentAccount has ${import_ethers.ethers.formatUnits(acctBalance, loanDecimals)}, EOA has ${import_ethers.ethers.formatUnits(eoaBalance, loanDecimals)}.`,
1756
+ "INSUFFICIENT_BALANCE"
1757
+ );
1758
+ }
1759
+ } else {
1760
+ const transferTx = await loanContract.transfer(acctAddr, shortfall);
1761
+ await transferTx.wait();
1762
+ this._refreshSigner();
1740
1763
  }
1741
- const transferTx = await loanContract.transfer(acctAddr, shortfall);
1742
- await transferTx.wait();
1743
- this._refreshSigner();
1744
1764
  }
1745
1765
  const targets = [loanTokenAddr, morphoAddr];
1746
1766
  const values = [0n, 0n];
@@ -1757,20 +1777,168 @@ var init_MorphoClient = __esm({
1757
1777
  const receipt = await this.batch(targets, values, datas);
1758
1778
  let remainingDebt = "0";
1759
1779
  try {
1760
- const status = await this.getStatus();
1761
- remainingDebt = status.totalDebt;
1780
+ const markets = await this.getMarkets(true);
1781
+ const mkt = markets.find(
1782
+ (m) => m.collateralAsset?.address.toLowerCase() === params.collateralToken.toLowerCase() && m.loanAsset?.address.toLowerCase() === params.loanToken.toLowerCase()
1783
+ );
1784
+ if (mkt) {
1785
+ const pos = await this.morphoBlue.position(mkt.uniqueKey, acctAddr);
1786
+ const shares = BigInt(pos.borrowShares);
1787
+ if (shares > 0n) {
1788
+ const onChainMkt = await this.morphoBlue.market(mkt.uniqueKey);
1789
+ const totalAssets = BigInt(onChainMkt.totalBorrowAssets);
1790
+ const totalShares = BigInt(onChainMkt.totalBorrowShares);
1791
+ const debtWei = totalShares > 0n ? shares * totalAssets / totalShares : 0n;
1792
+ remainingDebt = import_ethers.ethers.formatUnits(debtWei, loanDecimals);
1793
+ }
1794
+ }
1762
1795
  } catch (e) {
1763
1796
  console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
1764
1797
  }
1765
1798
  return { tx: receipt.hash, amount, remainingDebt };
1766
1799
  }
1800
+ static {
1801
+ // ════════════════════════════════════════════════════════
1802
+ // Yield Spread Analysis & Leverage
1803
+ // ════════════════════════════════════════════════════════
1804
+ /**
1805
+ * Mapping from Morpho collateral token symbols to DeFi Llama project IDs.
1806
+ * Used to look up native staking/restaking yields from the DeFi Llama Yields API.
1807
+ */
1808
+ this.LST_PROJECT_MAP = {
1809
+ "wstETH": { project: "lido", symbol: "STETH" },
1810
+ "cbETH": { project: "coinbase-wrapped-staked-eth", symbol: "CBETH" },
1811
+ "rETH": { project: "rocket-pool", symbol: "RETH" },
1812
+ "weETH": { project: "ether.fi-stake", symbol: "WEETH" },
1813
+ "ezETH": { project: "renzo", symbol: "EZETH" },
1814
+ "rsETH": { project: "kelp", symbol: "RSETH" },
1815
+ "swETH": { project: "swell-liquid-staking", symbol: "SWETH" },
1816
+ "mETH": { project: "meth-protocol", symbol: "METH" },
1817
+ "sfrxETH": { project: "frax-ether", symbol: "SFRXETH" },
1818
+ "oETH": { project: "origin-ether", symbol: "OETH" },
1819
+ "ETHx": { project: "stader", symbol: "ETHX" },
1820
+ "wBETH": { project: "binance-staked-eth", symbol: "WBETH" }
1821
+ };
1822
+ }
1823
+ static {
1824
+ /** Cache for LST yields fetched from DeFi Llama. TTL: 30 minutes. */
1825
+ this._lstYieldCache = null;
1826
+ }
1827
+ static {
1828
+ this.LST_CACHE_TTL = 30 * 60 * 1e3;
1829
+ }
1830
+ // 30 min
1767
1831
  /**
1768
- * Withdraw collateral from Morpho Blue.
1832
+ * Fetch native staking/restaking yields for all known LST/LRT tokens
1833
+ * from the DeFi Llama Yields API. Results are cached for 30 minutes.
1769
1834
  *
1770
- * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
1835
+ * @returns Map of token symbol → APY (e.g. { wstETH: 4.56, cbETH: 3.61 })
1836
+ */
1837
+ async _getLstYields() {
1838
+ if (_MorphoClient._lstYieldCache && Date.now() - _MorphoClient._lstYieldCache.ts < _MorphoClient.LST_CACHE_TTL) {
1839
+ return _MorphoClient._lstYieldCache.data;
1840
+ }
1841
+ const yields = {};
1842
+ try {
1843
+ const resp = await fetch("https://yields.llama.fi/pools", {
1844
+ headers: { "User-Agent": "agether-sdk/1.0" },
1845
+ signal: AbortSignal.timeout(1e4)
1846
+ });
1847
+ if (!resp.ok) {
1848
+ console.warn("[agether] DeFi Llama API returned", resp.status);
1849
+ return yields;
1850
+ }
1851
+ const data = await resp.json();
1852
+ const pools = data?.data ?? [];
1853
+ for (const [morphoSymbol, mapping] of Object.entries(_MorphoClient.LST_PROJECT_MAP)) {
1854
+ const matchingPools = pools.filter(
1855
+ (p) => p.project === mapping.project && p.symbol?.toUpperCase() === mapping.symbol && ["Ethereum", "Base"].includes(p.chain)
1856
+ );
1857
+ if (matchingPools.length > 0) {
1858
+ const best = matchingPools.reduce(
1859
+ (a, b) => (b.tvlUsd ?? 0) > (a.tvlUsd ?? 0) ? b : a
1860
+ );
1861
+ if (best.apy !== void 0 && best.apy > 0) {
1862
+ yields[morphoSymbol] = parseFloat(best.apy.toFixed(4));
1863
+ }
1864
+ }
1865
+ }
1866
+ const knownProjects = new Set(Object.values(_MorphoClient.LST_PROJECT_MAP).map((m) => m.project));
1867
+ const stakingProjects = pools.filter(
1868
+ (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)
1869
+ );
1870
+ for (const p of stakingProjects) {
1871
+ const sym = p.symbol;
1872
+ if (!yields[sym]) {
1873
+ yields[sym] = parseFloat(p.apy.toFixed(4));
1874
+ }
1875
+ }
1876
+ } catch (e) {
1877
+ console.warn("[agether] Failed to fetch LST yields:", e instanceof Error ? e.message : e);
1878
+ }
1879
+ _MorphoClient._lstYieldCache = { data: yields, ts: Date.now() };
1880
+ return yields;
1881
+ }
1882
+ /**
1883
+ * Analyze yield spread between LST/LRT collateral yield and Morpho borrow rates.
1884
+ * Positive spread = profitable carry trade (collateral earns more than debt costs).
1771
1885
  *
1772
- * @param receiver - defaults to EOA wallet
1886
+ * @returns Array of markets with yield analysis, sorted by net spread descending.
1773
1887
  */
1888
+ async getYieldSpread() {
1889
+ const lstYields = await this._getLstYields();
1890
+ if (Object.keys(lstYields).length === 0) {
1891
+ console.warn("[agether] No LST yield data available \u2014 DeFi Llama may be unreachable");
1892
+ return [];
1893
+ }
1894
+ const lstTokens = Object.keys(lstYields);
1895
+ const allMarkets = [];
1896
+ for (const token of lstTokens) {
1897
+ try {
1898
+ const markets2 = await this.getMarketRates(token);
1899
+ allMarkets.push(...markets2);
1900
+ } catch {
1901
+ }
1902
+ }
1903
+ const seen = /* @__PURE__ */ new Set();
1904
+ const markets = allMarkets.filter((m) => {
1905
+ if (seen.has(m.marketId)) return false;
1906
+ seen.add(m.marketId);
1907
+ return true;
1908
+ });
1909
+ const results = [];
1910
+ for (const mkt of markets) {
1911
+ const collateralYield = lstYields[mkt.collateralToken];
1912
+ if (collateralYield === void 0) continue;
1913
+ const borrowRate = mkt.borrowApy;
1914
+ const netSpread = collateralYield - borrowRate;
1915
+ const lltv = parseFloat(mkt.lltv) / 100;
1916
+ const safeLtv = lltv * 0.8;
1917
+ const maxLeverage = 1 / (1 - safeLtv);
1918
+ const leveragedNetApy = collateralYield * maxLeverage - borrowRate * (maxLeverage - 1);
1919
+ results.push({
1920
+ collateralToken: mkt.collateralToken,
1921
+ loanToken: mkt.loanToken,
1922
+ collateralYield,
1923
+ borrowRate,
1924
+ netSpread,
1925
+ profitable: netSpread > 0,
1926
+ lltv,
1927
+ maxSafeLeverage: parseFloat(maxLeverage.toFixed(2)),
1928
+ leveragedNetApy: parseFloat(leveragedNetApy.toFixed(2)),
1929
+ liquidity: mkt.totalSupplyUsd - mkt.totalBorrowUsd,
1930
+ marketId: mkt.marketId
1931
+ });
1932
+ }
1933
+ return results.sort((a, b) => b.netSpread - a.netSpread);
1934
+ }
1935
+ /**
1936
+ * Withdraw collateral from Morpho Blue.
1937
+ *
1938
+ * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
1939
+ *
1940
+ * @param receiver - defaults to EOA wallet
1941
+ */
1774
1942
  async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
1775
1943
  const acctAddr = await this.getAccountAddress();
1776
1944
  const colInfo = await this._resolveToken(tokenSymbol);
package/dist/index.d.mts CHANGED
@@ -569,6 +569,20 @@ interface PayFromYieldResult {
569
569
  remainingYield: string;
570
570
  remainingSupply: string;
571
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
+ }
572
586
  declare class MorphoClient {
573
587
  private _signer;
574
588
  private provider;
@@ -898,12 +912,34 @@ declare class MorphoClient {
898
912
  */
899
913
  repay(amount: string, tokenSymbol?: string, marketParams?: MorphoMarketParams, loanTokenSymbol?: string): Promise<RepayResult>;
900
914
  /**
901
- * Withdraw collateral from Morpho Blue.
915
+ * Mapping from Morpho collateral token symbols to DeFi Llama project IDs.
916
+ * Used to look up native staking/restaking yields from the DeFi Llama Yields API.
917
+ */
918
+ private static readonly LST_PROJECT_MAP;
919
+ /** Cache for LST yields fetched from DeFi Llama. TTL: 30 minutes. */
920
+ private static _lstYieldCache;
921
+ private static readonly LST_CACHE_TTL;
922
+ /**
923
+ * Fetch native staking/restaking yields for all known LST/LRT tokens
924
+ * from the DeFi Llama Yields API. Results are cached for 30 minutes.
902
925
  *
903
- * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
926
+ * @returns Map of token symbol → APY (e.g. { wstETH: 4.56, cbETH: 3.61 })
927
+ */
928
+ private _getLstYields;
929
+ /**
930
+ * Analyze yield spread between LST/LRT collateral yield and Morpho borrow rates.
931
+ * Positive spread = profitable carry trade (collateral earns more than debt costs).
904
932
  *
905
- * @param receiver - defaults to EOA wallet
933
+ * @returns Array of markets with yield analysis, sorted by net spread descending.
906
934
  */
935
+ getYieldSpread(): Promise<YieldSpreadResult[]>;
936
+ /**
937
+ * Withdraw collateral from Morpho Blue.
938
+ *
939
+ * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
940
+ *
941
+ * @param receiver - defaults to EOA wallet
942
+ */
907
943
  withdrawCollateral(tokenSymbol: string, amount: string, marketParams?: MorphoMarketParams, receiver?: string): Promise<WithdrawResult>;
908
944
  /**
909
945
  * Refresh the signer and re-bind contract instances.
@@ -1597,4 +1633,4 @@ interface RetryOptions {
1597
1633
  */
1598
1634
  declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
1599
1635
 
1600
- 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 };
1636
+ 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, type YieldSpreadResult, bpsToRate, createConfig, formatAPR, formatAddress, formatHealthFactor, formatPercent, formatTimestamp, formatUSD, formatUnits, getContractAddresses, getDefaultConfig, getMorphoBlueAddress, getUSDCAddress, parseUnits, rateToBps, withRetry };
package/dist/index.d.ts CHANGED
@@ -569,6 +569,20 @@ interface PayFromYieldResult {
569
569
  remainingYield: string;
570
570
  remainingSupply: string;
571
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
+ }
572
586
  declare class MorphoClient {
573
587
  private _signer;
574
588
  private provider;
@@ -898,12 +912,34 @@ declare class MorphoClient {
898
912
  */
899
913
  repay(amount: string, tokenSymbol?: string, marketParams?: MorphoMarketParams, loanTokenSymbol?: string): Promise<RepayResult>;
900
914
  /**
901
- * Withdraw collateral from Morpho Blue.
915
+ * Mapping from Morpho collateral token symbols to DeFi Llama project IDs.
916
+ * Used to look up native staking/restaking yields from the DeFi Llama Yields API.
917
+ */
918
+ private static readonly LST_PROJECT_MAP;
919
+ /** Cache for LST yields fetched from DeFi Llama. TTL: 30 minutes. */
920
+ private static _lstYieldCache;
921
+ private static readonly LST_CACHE_TTL;
922
+ /**
923
+ * Fetch native staking/restaking yields for all known LST/LRT tokens
924
+ * from the DeFi Llama Yields API. Results are cached for 30 minutes.
902
925
  *
903
- * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
926
+ * @returns Map of token symbol → APY (e.g. { wstETH: 4.56, cbETH: 3.61 })
927
+ */
928
+ private _getLstYields;
929
+ /**
930
+ * Analyze yield spread between LST/LRT collateral yield and Morpho borrow rates.
931
+ * Positive spread = profitable carry trade (collateral earns more than debt costs).
904
932
  *
905
- * @param receiver - defaults to EOA wallet
933
+ * @returns Array of markets with yield analysis, sorted by net spread descending.
906
934
  */
935
+ getYieldSpread(): Promise<YieldSpreadResult[]>;
936
+ /**
937
+ * Withdraw collateral from Morpho Blue.
938
+ *
939
+ * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
940
+ *
941
+ * @param receiver - defaults to EOA wallet
942
+ */
907
943
  withdrawCollateral(tokenSymbol: string, amount: string, marketParams?: MorphoMarketParams, receiver?: string): Promise<WithdrawResult>;
908
944
  /**
909
945
  * Refresh the signer and re-bind contract instances.
@@ -1597,4 +1633,4 @@ interface RetryOptions {
1597
1633
  */
1598
1634
  declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
1599
1635
 
1600
- 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 };
1636
+ 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, type YieldSpreadResult, bpsToRate, createConfig, formatAPR, formatAddress, formatHealthFactor, formatPercent, formatTimestamp, formatUSD, formatUnits, getContractAddresses, getDefaultConfig, getMorphoBlueAddress, getUSDCAddress, parseUnits, rateToBps, withRetry };
package/dist/index.js CHANGED
@@ -1315,7 +1315,7 @@ var MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000
1315
1315
  var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
1316
1316
  var morphoIface = new import_ethers2.ethers.Interface(MORPHO_BLUE_ABI);
1317
1317
  var erc20Iface2 = new import_ethers2.ethers.Interface(ERC20_ABI);
1318
- var MorphoClient = class {
1318
+ var _MorphoClient = class _MorphoClient {
1319
1319
  constructor(config) {
1320
1320
  /** Market params cache: keyed by market uniqueKey (bytes32 hash) */
1321
1321
  this._marketCache = /* @__PURE__ */ new Map();
@@ -2691,9 +2691,17 @@ var MorphoClient = class {
2691
2691
  const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
2692
2692
  approveAmount = estimated > 0n ? estimated : import_ethers2.ethers.parseUnits("1", loanDecimals);
2693
2693
  } else {
2694
- repayAssets = import_ethers2.ethers.parseUnits("999999", loanDecimals);
2695
- repayShares = 0n;
2696
- approveAmount = repayAssets;
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);
2697
2705
  }
2698
2706
  } else {
2699
2707
  repayAssets = import_ethers2.ethers.parseUnits(amount, loanDecimals);
@@ -2702,20 +2710,32 @@ var MorphoClient = class {
2702
2710
  }
2703
2711
  const loanContract = new import_ethers2.Contract(loanTokenAddr, ERC20_ABI, this._signer);
2704
2712
  const acctBalance = await loanContract.balanceOf(acctAddr);
2705
- if (acctBalance < approveAmount) {
2706
- const shortfall = approveAmount - acctBalance;
2713
+ const checkAmount = repayShares > 0n && repayAssets === 0n ? approveAmount * 1005n / 1000n : approveAmount;
2714
+ if (acctBalance < checkAmount) {
2715
+ const shortfall = checkAmount - acctBalance;
2707
2716
  const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
2708
2717
  if (eoaBalance < shortfall) {
2709
- const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
2710
- const loanSymbol = loanInfo?.symbol ?? "loan token";
2711
- throw new AgetherError(
2712
- `Insufficient ${loanSymbol} for repay. Need ${import_ethers2.ethers.formatUnits(approveAmount, loanDecimals)}, AgentAccount has ${import_ethers2.ethers.formatUnits(acctBalance, loanDecimals)}, EOA has ${import_ethers2.ethers.formatUnits(eoaBalance, loanDecimals)}.`,
2713
- "INSUFFICIENT_BALANCE"
2714
- );
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();
2715
2738
  }
2716
- const transferTx = await loanContract.transfer(acctAddr, shortfall);
2717
- await transferTx.wait();
2718
- this._refreshSigner();
2719
2739
  }
2720
2740
  const targets = [loanTokenAddr, morphoAddr];
2721
2741
  const values = [0n, 0n];
@@ -2732,20 +2752,138 @@ var MorphoClient = class {
2732
2752
  const receipt = await this.batch(targets, values, datas);
2733
2753
  let remainingDebt = "0";
2734
2754
  try {
2735
- const status = await this.getStatus();
2736
- remainingDebt = status.totalDebt;
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
+ }
2737
2770
  } catch (e) {
2738
2771
  console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
2739
2772
  }
2740
2773
  return { tx: receipt.hash, amount, remainingDebt };
2741
2774
  }
2775
+ // 30 min
2742
2776
  /**
2743
- * Withdraw collateral from Morpho Blue.
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.
2744
2779
  *
2745
- * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
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).
2746
2830
  *
2747
- * @param receiver - defaults to EOA wallet
2831
+ * @returns Array of markets with yield analysis, sorted by net spread descending.
2748
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
+ }
2880
+ /**
2881
+ * Withdraw collateral from Morpho Blue.
2882
+ *
2883
+ * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
2884
+ *
2885
+ * @param receiver - defaults to EOA wallet
2886
+ */
2749
2887
  async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
2750
2888
  const acctAddr = await this.getAccountAddress();
2751
2889
  const colInfo = await this._resolveToken(tokenSymbol);
@@ -3267,6 +3405,31 @@ var MorphoClient = class {
3267
3405
  return result;
3268
3406
  }
3269
3407
  };
3408
+ // ════════════════════════════════════════════════════════
3409
+ // Yield Spread Analysis & Leverage
3410
+ // ════════════════════════════════════════════════════════
3411
+ /**
3412
+ * Mapping from Morpho collateral token symbols to DeFi Llama project IDs.
3413
+ * Used to look up native staking/restaking yields from the DeFi Llama Yields API.
3414
+ */
3415
+ _MorphoClient.LST_PROJECT_MAP = {
3416
+ "wstETH": { project: "lido", symbol: "STETH" },
3417
+ "cbETH": { project: "coinbase-wrapped-staked-eth", symbol: "CBETH" },
3418
+ "rETH": { project: "rocket-pool", symbol: "RETH" },
3419
+ "weETH": { project: "ether.fi-stake", symbol: "WEETH" },
3420
+ "ezETH": { project: "renzo", symbol: "EZETH" },
3421
+ "rsETH": { project: "kelp", symbol: "RSETH" },
3422
+ "swETH": { project: "swell-liquid-staking", symbol: "SWETH" },
3423
+ "mETH": { project: "meth-protocol", symbol: "METH" },
3424
+ "sfrxETH": { project: "frax-ether", symbol: "SFRXETH" },
3425
+ "oETH": { project: "origin-ether", symbol: "OETH" },
3426
+ "ETHx": { project: "stader", symbol: "ETHX" },
3427
+ "wBETH": { project: "binance-staked-eth", symbol: "WBETH" }
3428
+ };
3429
+ /** Cache for LST yields fetched from DeFi Llama. TTL: 30 minutes. */
3430
+ _MorphoClient._lstYieldCache = null;
3431
+ _MorphoClient.LST_CACHE_TTL = 30 * 60 * 1e3;
3432
+ var MorphoClient = _MorphoClient;
3270
3433
 
3271
3434
  // src/clients/ScoringClient.ts
3272
3435
  var import_axios3 = __toESM(require("axios"));
package/dist/index.mjs CHANGED
@@ -1239,7 +1239,7 @@ var MODE_SINGLE2 = "0x0000000000000000000000000000000000000000000000000000000000
1239
1239
  var MODE_BATCH = "0x0100000000000000000000000000000000000000000000000000000000000000";
1240
1240
  var morphoIface = new ethers2.Interface(MORPHO_BLUE_ABI);
1241
1241
  var erc20Iface2 = new ethers2.Interface(ERC20_ABI);
1242
- var MorphoClient = class {
1242
+ var _MorphoClient = class _MorphoClient {
1243
1243
  constructor(config) {
1244
1244
  /** Market params cache: keyed by market uniqueKey (bytes32 hash) */
1245
1245
  this._marketCache = /* @__PURE__ */ new Map();
@@ -2615,9 +2615,17 @@ var MorphoClient = class {
2615
2615
  const estimated = totalBorrowShares > 0n ? repayShares * totalBorrowAssets / totalBorrowShares + 10n : 0n;
2616
2616
  approveAmount = estimated > 0n ? estimated : ethers2.parseUnits("1", loanDecimals);
2617
2617
  } else {
2618
- repayAssets = ethers2.parseUnits("999999", loanDecimals);
2619
- repayShares = 0n;
2620
- approveAmount = repayAssets;
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);
2621
2629
  }
2622
2630
  } else {
2623
2631
  repayAssets = ethers2.parseUnits(amount, loanDecimals);
@@ -2626,20 +2634,32 @@ var MorphoClient = class {
2626
2634
  }
2627
2635
  const loanContract = new Contract2(loanTokenAddr, ERC20_ABI, this._signer);
2628
2636
  const acctBalance = await loanContract.balanceOf(acctAddr);
2629
- if (acctBalance < approveAmount) {
2630
- const shortfall = approveAmount - acctBalance;
2637
+ const checkAmount = repayShares > 0n && repayAssets === 0n ? approveAmount * 1005n / 1000n : approveAmount;
2638
+ if (acctBalance < checkAmount) {
2639
+ const shortfall = checkAmount - acctBalance;
2631
2640
  const eoaBalance = await loanContract.balanceOf(await this.getSignerAddress());
2632
2641
  if (eoaBalance < shortfall) {
2633
- const loanInfo = this._tokenCache.get(loanTokenAddr.toLowerCase());
2634
- const loanSymbol = loanInfo?.symbol ?? "loan token";
2635
- throw new AgetherError(
2636
- `Insufficient ${loanSymbol} for repay. Need ${ethers2.formatUnits(approveAmount, loanDecimals)}, AgentAccount has ${ethers2.formatUnits(acctBalance, loanDecimals)}, EOA has ${ethers2.formatUnits(eoaBalance, loanDecimals)}.`,
2637
- "INSUFFICIENT_BALANCE"
2638
- );
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();
2639
2662
  }
2640
- const transferTx = await loanContract.transfer(acctAddr, shortfall);
2641
- await transferTx.wait();
2642
- this._refreshSigner();
2643
2663
  }
2644
2664
  const targets = [loanTokenAddr, morphoAddr];
2645
2665
  const values = [0n, 0n];
@@ -2656,20 +2676,138 @@ var MorphoClient = class {
2656
2676
  const receipt = await this.batch(targets, values, datas);
2657
2677
  let remainingDebt = "0";
2658
2678
  try {
2659
- const status = await this.getStatus();
2660
- remainingDebt = status.totalDebt;
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
+ }
2661
2694
  } catch (e) {
2662
2695
  console.warn("[agether] failed to read remaining debt after repay:", e instanceof Error ? e.message : e);
2663
2696
  }
2664
2697
  return { tx: receipt.hash, amount, remainingDebt };
2665
2698
  }
2699
+ // 30 min
2666
2700
  /**
2667
- * Withdraw collateral from Morpho Blue.
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.
2668
2703
  *
2669
- * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
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).
2670
2754
  *
2671
- * @param receiver - defaults to EOA wallet
2755
+ * @returns Array of markets with yield analysis, sorted by net spread descending.
2672
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
+ }
2804
+ /**
2805
+ * Withdraw collateral from Morpho Blue.
2806
+ *
2807
+ * AgentAccount.execute: Morpho.withdrawCollateral(params, amount, account, receiver)
2808
+ *
2809
+ * @param receiver - defaults to EOA wallet
2810
+ */
2673
2811
  async withdrawCollateral(tokenSymbol, amount, marketParams, receiver) {
2674
2812
  const acctAddr = await this.getAccountAddress();
2675
2813
  const colInfo = await this._resolveToken(tokenSymbol);
@@ -3191,6 +3329,31 @@ var MorphoClient = class {
3191
3329
  return result;
3192
3330
  }
3193
3331
  };
3332
+ // ════════════════════════════════════════════════════════
3333
+ // Yield Spread Analysis & Leverage
3334
+ // ════════════════════════════════════════════════════════
3335
+ /**
3336
+ * Mapping from Morpho collateral token symbols to DeFi Llama project IDs.
3337
+ * Used to look up native staking/restaking yields from the DeFi Llama Yields API.
3338
+ */
3339
+ _MorphoClient.LST_PROJECT_MAP = {
3340
+ "wstETH": { project: "lido", symbol: "STETH" },
3341
+ "cbETH": { project: "coinbase-wrapped-staked-eth", symbol: "CBETH" },
3342
+ "rETH": { project: "rocket-pool", symbol: "RETH" },
3343
+ "weETH": { project: "ether.fi-stake", symbol: "WEETH" },
3344
+ "ezETH": { project: "renzo", symbol: "EZETH" },
3345
+ "rsETH": { project: "kelp", symbol: "RSETH" },
3346
+ "swETH": { project: "swell-liquid-staking", symbol: "SWETH" },
3347
+ "mETH": { project: "meth-protocol", symbol: "METH" },
3348
+ "sfrxETH": { project: "frax-ether", symbol: "SFRXETH" },
3349
+ "oETH": { project: "origin-ether", symbol: "OETH" },
3350
+ "ETHx": { project: "stader", symbol: "ETHX" },
3351
+ "wBETH": { project: "binance-staked-eth", symbol: "WBETH" }
3352
+ };
3353
+ /** Cache for LST yields fetched from DeFi Llama. TTL: 30 minutes. */
3354
+ _MorphoClient._lstYieldCache = null;
3355
+ _MorphoClient.LST_CACHE_TTL = 30 * 60 * 1e3;
3356
+ var MorphoClient = _MorphoClient;
3194
3357
 
3195
3358
  // src/clients/ScoringClient.ts
3196
3359
  import axios3 from "axios";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agether/sdk",
3
- "version": "2.16.1",
3
+ "version": "2.17.2",
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",