@1delta/margin-fetcher 5.0.108 → 5.0.110

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/index.js CHANGED
@@ -49133,6 +49133,33 @@ var sthusdFetcher = {
49133
49133
  };
49134
49134
  }
49135
49135
  };
49136
+
49137
+ // src/yields/intrinsic/fetchers/usual.ts
49138
+ var USDZEROPLUS_URL = "https://app.usual.money/api/rewards/rates/USD0++";
49139
+ var ETHZERO_URL = "https://app.usual.money/api/rewards/rates/ETH0";
49140
+ var BUSD0_KEY = "Staked USD0::USD0++";
49141
+ var usdZeroPlus = BUSD0_KEY;
49142
+ var ethZero = "ETH0::ETH0";
49143
+ var usdZeroPlusFetcher = {
49144
+ label: "USDZEROPLUS",
49145
+ fetch: async () => {
49146
+ const res = await fetch(USDZEROPLUS_URL, {
49147
+ method: "GET",
49148
+ headers: { Accept: "application/json" }
49149
+ }).then((r) => r.json());
49150
+ return { [usdZeroPlus]: Number(res?.rewards[0]?.apr ?? 0) * 100 };
49151
+ }
49152
+ };
49153
+ var ethZeroFetcher = {
49154
+ label: "ETHZERO",
49155
+ fetch: async () => {
49156
+ const res = await fetch(ETHZERO_URL, {
49157
+ method: "GET",
49158
+ headers: { Accept: "application/json" }
49159
+ }).then((r) => r.json());
49160
+ return { [ethZero]: Number(res?.rewards[0]?.apr ?? 0) * 100 };
49161
+ }
49162
+ };
49136
49163
  var CHAIN_ID14 = Chain.ETHEREUM_MAINNET;
49137
49164
  var HUMA_PST_FEED = "0x4be50be32db1510240d542f77c5b36ca0d0965e6";
49138
49165
  var HUMA_PST_KEY = "HUMA::PST";
@@ -52479,6 +52506,50 @@ var SINGLE_CHAIN_ENTRIES = {
52479
52506
  yieldFetcher: saturnFetcher,
52480
52507
  yieldKey: SUSDAT_KEY
52481
52508
  },
52509
+ // ---- Usual (usual.money) — bUSD0, Bond USD0 (ex-USD0++) ------------
52510
+ //
52511
+ // A fixed-maturity BOND over USD0, not a staked stablecoin: one bUSD0 is
52512
+ // one USD0 at `getEndTime()` = 1844335800 (2028-06-11 11:30 UTC; the bond
52513
+ // started 2024-06-11 and runs `totalBondTimes()` = 4 years). Not ERC-4626
52514
+ // in any part, hence the `usual-bond` reader. Fully backed 1:1 — the
52515
+ // contract held exactly its supply in USD0 (518,288,230.80 each) on
52516
+ // 2026-10-07 — but the backing is admin-sweepable (`emergencyWithdraw`),
52517
+ // so `solvency` stays unasserted and the reader clamps the rate to it.
52518
+ //
52519
+ // The rate is par and NEVER drifts: the yield is a USUAL reward stream
52520
+ // (Usual's `rewards/rates/USD0++` API, 5.69 % at integration). The
52521
+ // interesting numbers are the early exits — `unlockUsd0ppFloorPrice` at
52522
+ // `getFloorPrice()` (0.92 → `withdrawFeeBps` 800), `unlockUSD0ppWithUsual`
52523
+ // at par for `calculateRequiredUsual` (2.147 USUAL per bUSD0 ≈ 2.6 % at
52524
+ // $0.0122), and `reconstruct` at par for an equal rt-bUSD0 burn, which only
52525
+ // V3 mints receive (~337.9k rt against ~518M bUSD0). The market sat at
52526
+ // ~0.971, so the floor is NOT the cheapest exit — the market and the USUAL
52527
+ // unlock both beat it. Every user path is `whenNotPaused`, and transfers
52528
+ // revert for USD0-blacklisted addresses.
52529
+ {
52530
+ reader: "usual-bond",
52531
+ address: "0x35d8949372d46b7a3d5a56006ae77b215fc69bc0",
52532
+ underlying: "0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5",
52533
+ // USD0
52534
+ symbol: "bUSD0",
52535
+ brand: "Usual",
52536
+ description: "Usual's four-year bond on its USD0 stablecoin: each bUSD0 is backed by one USD0 held in the contract and redeems for it at par on 11 June 2028. The yield is paid separately in USUAL tokens, not added to the bond. Leaving before maturity costs something \u2014 the guaranteed floor price (currently 0.92), a fee paid in USUAL for par, or a sale on the market below par \u2014 unless you still hold the redemption token from minting.",
52537
+ decimals: 18,
52538
+ isRebasing: false,
52539
+ isMintable: true,
52540
+ withdrawalMode: "fee-or-maturity",
52541
+ expiry: 1844335800,
52542
+ // getEndTime() — 2028-06-11 11:30 UTC
52543
+ usualBond: {
52544
+ redemptionToken: "0x82dca22b48b14de38ccf83b03330120c4b8acfe9",
52545
+ // rt-bUSD0
52546
+ parUnlockToken: "0xc4441c2be5d8fa8126822b9929ca0b81ea0de38e",
52547
+ // USUAL
52548
+ parUnlockTokenSymbol: "USUAL"
52549
+ },
52550
+ yieldFetcher: usdZeroPlusFetcher,
52551
+ yieldKey: BUSD0_KEY
52552
+ },
52482
52553
  // ---- Strata (docs.strata.markets) — senior/junior risk tranches ----
52483
52554
  //
52484
52555
  // Six CDO markets, twelve ERC-4626 tranche tokens, all Ethereum.
@@ -55558,32 +55629,6 @@ var csusdlFetcher = {
55558
55629
  }
55559
55630
  };
55560
55631
 
55561
- // src/yields/intrinsic/fetchers/usual.ts
55562
- var USDZEROPLUS_URL = "https://app.usual.money/api/rewards/rates/USD0++";
55563
- var ETHZERO_URL = "https://app.usual.money/api/rewards/rates/ETH0";
55564
- var usdZeroPlus = "Staked USD0::USD0++";
55565
- var ethZero = "ETH0::ETH0";
55566
- var usdZeroPlusFetcher = {
55567
- label: "USDZEROPLUS",
55568
- fetch: async () => {
55569
- const res = await fetch(USDZEROPLUS_URL, {
55570
- method: "GET",
55571
- headers: { Accept: "application/json" }
55572
- }).then((r) => r.json());
55573
- return { [usdZeroPlus]: Number(res?.rewards[0]?.apr ?? 0) * 100 };
55574
- }
55575
- };
55576
- var ethZeroFetcher = {
55577
- label: "ETHZERO",
55578
- fetch: async () => {
55579
- const res = await fetch(ETHZERO_URL, {
55580
- method: "GET",
55581
- headers: { Accept: "application/json" }
55582
- }).then((r) => r.json());
55583
- return { [ethZero]: Number(res?.rewards[0]?.apr ?? 0) * 100 };
55584
- }
55585
- };
55586
-
55587
55632
  // src/yields/intrinsic/fetchers/jito.ts
55588
55633
  var JITOSOL_URL = "https://www.jito.network/api/getJitoPoolStats/";
55589
55634
  var jitoSol = "Jito Staked SOL::JitoSOL";
@@ -55662,6 +55707,108 @@ var onycFetcher = {
55662
55707
  }
55663
55708
  };
55664
55709
 
55710
+ // src/yields/intrinsic/fetchers/sanctum.ts
55711
+ var KAMINO_STAKING_YIELDS = "https://api.kamino.finance/staking-yields/tokens";
55712
+ var INF_MINT = "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm";
55713
+ var INF_LLAMA_POOL = "3075a746-bdd1-4aac-bcd5-b035abee2622";
55714
+ var INF_KEY = "Infinity::INF::solana";
55715
+ var INF_EPOCHS = 10;
55716
+ var MAX_PLAUSIBLE_APY = 0.3;
55717
+ function meanEpochApy(rows, n = INF_EPOCHS) {
55718
+ if (!Array.isArray(rows)) return void 0;
55719
+ const ok2 = rows.map((r) => ({ epoch: Number(r?.epoch), apy: Number(r?.apy) })).filter(
55720
+ (r) => Number.isFinite(r.epoch) && Number.isFinite(r.apy) && r.apy > 0 && r.apy < MAX_PLAUSIBLE_APY
55721
+ ).sort((a, b) => b.epoch - a.epoch).slice(0, n);
55722
+ if (ok2.length === 0) return void 0;
55723
+ return ok2.reduce((s, r) => s + r.apy, 0) / ok2.length;
55724
+ }
55725
+ async function kaminoInfApy() {
55726
+ const end = /* @__PURE__ */ new Date();
55727
+ const start = new Date(end.getTime() - 30 * 864e5);
55728
+ const d = (x) => x.toISOString().slice(0, 10);
55729
+ const res = await fetch(
55730
+ `${KAMINO_STAKING_YIELDS}/${INF_MINT}/history?start=${d(start)}&end=${d(end)}`,
55731
+ {
55732
+ headers: { accept: "application/json" },
55733
+ signal: AbortSignal.timeout(8e3)
55734
+ }
55735
+ );
55736
+ if (!res.ok) throw new Error(`Kamino staking-yields HTTP ${res.status}`);
55737
+ return meanEpochApy(await res.json());
55738
+ }
55739
+ var infFetcher = {
55740
+ label: "SANCTUM_INF",
55741
+ fetch: async () => {
55742
+ const apy = await kaminoInfApy().catch((e) => {
55743
+ console.warn(`[sanctum] Kamino INF history unavailable: ${e.message}`);
55744
+ return void 0;
55745
+ });
55746
+ if (apy !== void 0) return { [INF_KEY]: apyToAprPercent(apy * 100) };
55747
+ const llama = await fetchDefiLlamaApy(INF_LLAMA_POOL);
55748
+ if (!(llama > 0) || llama >= MAX_PLAUSIBLE_APY * 100) return {};
55749
+ return { [INF_KEY]: apyToAprPercent(llama) };
55750
+ }
55751
+ };
55752
+
55753
+ // src/yields/intrinsic/fetchers/exponentTranching.ts
55754
+ var EXPONENT_APP_API = "https://app.exponent.finance/api/tranching-markets";
55755
+ var EXPONENT_SENIOR_GROUPS = {
55756
+ FvQP1fjox2GPSwkEhENuZisz8UeRURLWf7GYF9n2mURD: "Exponent Senior eHYUSD::srEHYUSD::solana",
55757
+ F17tzaQaFf1x3tC5gQFVvXAF2hcgq1qX6Mc9595zo3FD: "Exponent Senior AUTO::srAUTO::solana",
55758
+ "9J8VvigcjFTkN3jhZH2ieTi2hdGVBVpEXbcA1JDo7QpA": "Exponent Senior ONyc::srONyc::solana",
55759
+ "4tnzVYkaXKwMt7p86wpDzeTyzHSZhj2BvdGwBem7peH5": "Exponent Senior nOPAL::srnOPAL::solana"
55760
+ };
55761
+ var EXPONENT_BASE_GROUPS = {
55762
+ HnnGv3HrSqjRpgdFmx7vQGjntNEoex1SU4e9Lxcxuihz: "Earn Hylo USD::eHYUSD::solana"
55763
+ };
55764
+ var MAX_PLAUSIBLE_APY2 = 0.5;
55765
+ function seniorApy(stats) {
55766
+ const s = stats;
55767
+ for (const k of ["seniorApy30d", "seniorApy7d", "seniorApy1d"]) {
55768
+ const v = Number(s?.[k] ?? NaN);
55769
+ if (s?.[k] != null && Number.isFinite(v) && v > -MAX_PLAUSIBLE_APY2 && v < MAX_PLAUSIBLE_APY2)
55770
+ return v;
55771
+ }
55772
+ return void 0;
55773
+ }
55774
+ var getJson3 = async (url) => {
55775
+ const res = await fetch(url, {
55776
+ headers: { accept: "application/json" },
55777
+ signal: AbortSignal.timeout(8e3)
55778
+ });
55779
+ if (!res.ok) throw new Error(`Exponent HTTP ${res.status} for ${url}`);
55780
+ return res.json();
55781
+ };
55782
+ var exponentSeniorFetcher = {
55783
+ label: "EXPONENT_SENIOR",
55784
+ fetch: async () => {
55785
+ const markets = await getJson3(EXPONENT_APP_API);
55786
+ const wanted = (Array.isArray(markets) ? markets : []).filter(
55787
+ (m) => m.address && m.mintLpSenior && EXPONENT_SENIOR_GROUPS[m.mintLpSenior]
55788
+ );
55789
+ const out = {};
55790
+ for (const m of Array.isArray(markets) ? markets : []) {
55791
+ const group = m.mintBase ? EXPONENT_BASE_GROUPS[m.mintBase] : void 0;
55792
+ const apy = Number(m.underlyingApy7d);
55793
+ if (group && m.underlyingApy7d != null && apy > 0 && apy < MAX_PLAUSIBLE_APY2)
55794
+ out[group] = apyToAprPercent(apy * 100);
55795
+ }
55796
+ await Promise.all(
55797
+ wanted.map(async (m) => {
55798
+ const apy = await getJson3(`${EXPONENT_APP_API}/${m.address}/stats`).then(seniorApy).catch((e) => {
55799
+ console.warn(`[exponent] ${m.address} stats: ${e.message}`);
55800
+ return void 0;
55801
+ });
55802
+ if (apy !== void 0)
55803
+ out[EXPONENT_SENIOR_GROUPS[m.mintLpSenior]] = apyToAprPercent(
55804
+ apy * 100
55805
+ );
55806
+ })
55807
+ );
55808
+ return out;
55809
+ }
55810
+ };
55811
+
55665
55812
  // src/yields/intrinsic/fetchers/superreturn.ts
55666
55813
  var SSUPERUSD_URL = "https://www.superreturn.ai/api/trpc/cmc.getUSDCPrice,rewardBackend.getYield,rewardBackend.getTvlChange?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%7D%7D%2C%221%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%7D%7D%2C%222%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%7D%7D%7D";
55667
55814
  var ssuperusd = "SuperReturn sSuperUSD::SSUPERUSD";
@@ -55777,7 +55924,7 @@ var getGmxApiHost = (chainId) => GMX_API_HOSTS[chainId];
55777
55924
 
55778
55925
  // src/vaults/gmx/api.ts
55779
55926
  var GMX_DEFAULT_APY_PERIOD = "1d";
55780
- var getJson3 = async (url) => {
55927
+ var getJson4 = async (url) => {
55781
55928
  const res = await fetch(url, {
55782
55929
  method: "GET",
55783
55930
  headers: { Accept: "application/json" }
@@ -55789,12 +55936,12 @@ var fetchGmxRawData = async (chainId, period, apiUrlOverride) => {
55789
55936
  const host = apiUrlOverride ?? getGmxApiHost(chainId);
55790
55937
  if (!host) throw new Error(`GMX not supported on chain ${chainId}`);
55791
55938
  const [apy, marketsRes, glvsRes, tokensRes] = await Promise.all([
55792
- getJson3(`${host}/apy?period=${encodeURIComponent(period)}`),
55793
- getJson3(`${host}/markets`).catch(() => ({
55939
+ getJson4(`${host}/apy?period=${encodeURIComponent(period)}`),
55940
+ getJson4(`${host}/markets`).catch(() => ({
55794
55941
  markets: []
55795
55942
  })),
55796
- getJson3(`${host}/glvs`).catch(() => ({ glvs: [] })),
55797
- getJson3(`${host}/tokens`).catch(() => ({
55943
+ getJson4(`${host}/glvs`).catch(() => ({ glvs: [] })),
55944
+ getJson4(`${host}/tokens`).catch(() => ({
55798
55945
  tokens: []
55799
55946
  }))
55800
55947
  ]);
@@ -56416,6 +56563,13 @@ var LENDING_ONLY_FETCHERS = [
56416
56563
  // ONyc (OnRe reinsurance, Solana): NAV-accruing collateral on Loopscale /
56417
56564
  // Kamino / Jupiter Lend, no vault row.
56418
56565
  onycFetcher,
56566
+ // INF (Sanctum Infinity, Solana): the SOL-value accrual of the multi-LST
56567
+ // pool, collateral on Kamino / Jupiter Lend / Project 0 / Save. Its earn
56568
+ // row lives in margin-fetcher-sol, so it is lending-only here.
56569
+ infFetcher,
56570
+ // Exponent senior tranches (srEHYUSD / srAUTO / srONyc / srnOPAL, Solana):
56571
+ // Loopscale loop collateral, no earn row.
56572
+ exponentSeniorFetcher,
56419
56573
  thbillFetcher,
56420
56574
  // sthusdFetcher / scrvusdFetcher are NOT here (and deliberately not
56421
56575
  // imported): both are vault-wired by their savings rows, so they reach
@@ -65071,7 +65225,7 @@ function rescaleExchangeRate(exchangeRate, shareDecimals) {
65071
65225
  const dec2 = Math.max(0, Math.round(Number(shareDecimals) || 0));
65072
65226
  return (r * 10n ** BigInt(dec2) / 10n ** 18n).toString();
65073
65227
  }
65074
- async function getJson4(url) {
65228
+ async function getJson5(url) {
65075
65229
  const controller = new AbortController();
65076
65230
  const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS2);
65077
65231
  try {
@@ -65163,7 +65317,7 @@ async function mapWithConcurrency(items, limit, fn) {
65163
65317
  return out;
65164
65318
  }
65165
65319
  async function fetchEulerEarnVaultsFromApi(chainId, prices = {}, tokenList = {}, multicallRetry) {
65166
- const list = await getJson4(
65320
+ const list = await getJson5(
65167
65321
  `${EULER_DATA_API_BASE2}/earn/vaults?chainId=${chainId}&limit=200&visibility=${VISIBILITY_ALL}`
65168
65322
  );
65169
65323
  if (!list) return void 0;
@@ -65180,7 +65334,7 @@ async function fetchEulerEarnVaultsFromApi(chainId, prices = {}, tokenList = {},
65180
65334
  mapWithConcurrency(
65181
65335
  rows,
65182
65336
  DETAIL_CONCURRENCY,
65183
- (row) => getJson4(
65337
+ (row) => getJson5(
65184
65338
  `${EULER_DATA_API_BASE2}/earn/vaults/${chainId}/${row.address}?include=strategies`
65185
65339
  ).then((d) => d?.data)
65186
65340
  ),
@@ -67472,6 +67626,87 @@ var marketRoute = () => ({
67472
67626
  settlement: "market",
67473
67627
  description: "The protocol's own mint and redeem are permissioned, so for a holder without that permission this is the route: sell the share token at whatever the book bids, with price impact that grows with size. It fills at the MARKET price, which is not the row's `exchangeRate` \u2014 that stays the NAV \u2014 so the leg must be built with a slippage bound."
67474
67628
  });
67629
+ var capped = (a, b) => {
67630
+ if (a === void 0) return b;
67631
+ if (b === void 0) return a;
67632
+ return BigInt(a) < BigInt(b) ? a : b;
67633
+ };
67634
+ var bondExitRoutes = (i) => {
67635
+ const routes2 = [];
67636
+ const now = i.now ?? Math.floor(Date.now() / 1e3);
67637
+ const matured = i.expiry !== void 0 && now >= i.expiry;
67638
+ const capacity = {
67639
+ capacity: i.liquidity,
67640
+ capacityFormatted: i.liquidityFormatted,
67641
+ capacityUsd: i.liquidityUsd
67642
+ };
67643
+ if (i.withdrawFeeBps != null)
67644
+ routes2.push({
67645
+ id: "floor",
67646
+ kind: "instant",
67647
+ label: "Early exit at the floor price",
67648
+ settlement: "sync",
67649
+ feeBps: i.withdrawFeeBps,
67650
+ waitSeconds: 0,
67651
+ ...capacity,
67652
+ selfOnly: true,
67653
+ description: "Burns the bond now for the protocol's floor price; the difference to par goes to the protocol's treasury, not to you. Pays the sender only."
67654
+ });
67655
+ if (i.parUnlockAvailable) {
67656
+ const token = i.parUnlockTokenSymbol ?? "the protocol token";
67657
+ routes2.push({
67658
+ id: "usual-unlock",
67659
+ kind: "instant",
67660
+ label: `Early exit at par (pays ${token})`,
67661
+ settlement: "sync",
67662
+ feeBps: i.parUnlockFeeBps,
67663
+ ...i.parUnlockFeeBps == null ? { feeUnknown: true } : {},
67664
+ waitSeconds: 0,
67665
+ ...capacity,
67666
+ selfOnly: true,
67667
+ description: `Pays par in the underlying, but you must also pay ${token} \u2014 a fixed amount per bond, so its cost in dollars moves with ${token}'s price. Pays the sender only.`
67668
+ });
67669
+ }
67670
+ if (i.redemptionTokenCapacity !== void 0)
67671
+ routes2.push({
67672
+ id: "reconstruct",
67673
+ kind: "instant",
67674
+ label: "Par with the redemption token",
67675
+ settlement: "sync",
67676
+ feeBps: 0,
67677
+ waitSeconds: 0,
67678
+ capacity: capped(i.liquidity, i.redemptionTokenCapacity),
67679
+ capacityFormatted: i.liquidityFormatted !== void 0 && i.redemptionTokenCapacityFormatted !== void 0 ? Math.min(i.liquidityFormatted, i.redemptionTokenCapacityFormatted) : i.redemptionTokenCapacityFormatted ?? i.liquidityFormatted,
67680
+ description: "Free and at par any time, but it burns an equal amount of the redemption token, which only fresh mints receive \u2014 a holder who bought the bond, or minted it before the token existed, cannot take this route. Capacity is the token supply across everyone."
67681
+ });
67682
+ routes2.push(
67683
+ matured ? {
67684
+ id: "maturity",
67685
+ kind: "instant",
67686
+ label: "Par at maturity",
67687
+ settlement: "sync",
67688
+ feeBps: 0,
67689
+ waitSeconds: 0,
67690
+ description: "The bond has matured: redeem at par. Burns your WHOLE balance in one call."
67691
+ } : {
67692
+ id: "maturity",
67693
+ kind: "queued",
67694
+ label: i.expiry !== void 0 ? `Par at maturity (${new Date(i.expiry * 1e3).toISOString().slice(0, 10)})` : "Par at maturity",
67695
+ settlement: "async",
67696
+ feeBps: 0,
67697
+ waitSeconds: i.expiry !== void 0 ? Math.max(0, i.expiry - now) : void 0,
67698
+ description: "Hold to the maturity date and redeem at par \u2014 nothing to request in the meantime. The redemption burns your WHOLE balance in one call."
67699
+ }
67700
+ );
67701
+ routes2.push({
67702
+ id: "market",
67703
+ kind: "market",
67704
+ label: "Sell on the market",
67705
+ settlement: "market",
67706
+ description: "Sell the bond to somebody else. It fills at the market price \u2014 usually a discount to par that closes toward maturity \u2014 not at the row's `exchangeRate`, with price impact that grows with size, so the leg must be built with a slippage bound."
67707
+ });
67708
+ return routes2;
67709
+ };
67475
67710
  var deriveExitRoutes = (i) => {
67476
67711
  const routes2 = deriveProtocolExitRoutes(i);
67477
67712
  return i.secondaryMarketOnly ? [...routes2, marketRoute()] : routes2;
@@ -67498,6 +67733,8 @@ var deriveProtocolExitRoutes = (i) => {
67498
67733
  // inventory instead.
67499
67734
  case "instant-or-queued":
67500
67735
  return [instantRoute(i, i.withdrawFeeBps ?? 0), queuedRoute(i)];
67736
+ case "fee-or-maturity":
67737
+ return bondExitRoutes(i);
67501
67738
  case "instant":
67502
67739
  return [
67503
67740
  {
@@ -72127,6 +72364,30 @@ var NEST_FEE_INSTANT_REDEMPTION = 0;
72127
72364
  var NEST_FEE_DEPOSIT = 1;
72128
72365
  var NEST_FEE_REDEMPTION = 2;
72129
72366
 
72367
+ // src/vaults/savings/abis/usual.ts
72368
+ var view = (name, inputs = []) => ({
72369
+ name,
72370
+ type: "function",
72371
+ stateMutability: "view",
72372
+ inputs,
72373
+ outputs: [{ name: "", type: "uint256" }]
72374
+ });
72375
+ var UsualBondFloorPriceAbi = [view("getFloorPrice")];
72376
+ var UsualBondStartTimeAbi = [view("getStartTime")];
72377
+ var UsualBondEndTimeAbi = [view("getEndTime")];
72378
+ var UsualBondRequiredUsualAbi = [
72379
+ view("calculateRequiredUsual", [{ name: "usd0ppAmount", type: "uint256" }])
72380
+ ];
72381
+ var UsualBondPausedAbi = [
72382
+ {
72383
+ name: "paused",
72384
+ type: "function",
72385
+ stateMutability: "view",
72386
+ inputs: [],
72387
+ outputs: [{ name: "", type: "bool" }]
72388
+ }
72389
+ ];
72390
+
72130
72391
  // src/vaults/savings/readers/erc4626Idle.ts
72131
72392
  var readerErc4626Idle = (entry) => {
72132
72393
  const shareUnit = 10n ** BigInt(entry.decimals);
@@ -72692,6 +72953,88 @@ var readerSaturnVault = (entry) => {
72692
72953
  };
72693
72954
  };
72694
72955
 
72956
+ // src/vaults/savings/readers/usualBond.ts
72957
+ var readerUsualBond = (entry) => {
72958
+ const cfg = entry.usualBond;
72959
+ const shareUnit = 10n ** BigInt(entry.decimals);
72960
+ return {
72961
+ calls: [
72962
+ { address: entry.address, name: "totalSupply", params: [] },
72963
+ { address: entry.underlying, name: "balanceOf", params: [entry.address] },
72964
+ { address: entry.address, name: "getFloorPrice", params: [] },
72965
+ { address: entry.address, name: "getStartTime", params: [] },
72966
+ { address: entry.address, name: "getEndTime", params: [] },
72967
+ { address: entry.address, name: "paused", params: [] },
72968
+ // No pinned rt token ⇒ read the bond's own supply in its place and
72969
+ // discard it, keeping the slice width fixed.
72970
+ {
72971
+ address: cfg?.redemptionToken ?? entry.address,
72972
+ name: "totalSupply",
72973
+ params: []
72974
+ },
72975
+ {
72976
+ address: entry.address,
72977
+ name: "calculateRequiredUsual",
72978
+ params: [shareUnit]
72979
+ }
72980
+ ],
72981
+ abis: [
72982
+ TotalSupplyAbi2,
72983
+ BalanceOfAbi,
72984
+ UsualBondFloorPriceAbi,
72985
+ UsualBondStartTimeAbi,
72986
+ UsualBondEndTimeAbi,
72987
+ UsualBondPausedAbi,
72988
+ TotalSupplyAbi2,
72989
+ UsualBondRequiredUsualAbi
72990
+ ],
72991
+ parse: ([
72992
+ supply,
72993
+ backingRaw,
72994
+ floor,
72995
+ start,
72996
+ end,
72997
+ paused,
72998
+ rtSupply,
72999
+ usual
73000
+ ]) => {
73001
+ const totalSupply = toBigInt16(supply);
73002
+ const backing = toBigInt16(backingRaw);
73003
+ const endTime = toBigInt16(end);
73004
+ if (totalSupply === void 0 || backing === void 0 || endTime === void 0 || endTime <= 0n)
73005
+ return void 0;
73006
+ const exchangeRate = totalSupply > 0n && backing < totalSupply ? backing * ONE_E1815 / totalSupply : ONE_E1815;
73007
+ const floorPrice = toBigInt16(floor);
73008
+ const startTime = toBigInt16(start);
73009
+ const isPaused = typeof paused === "boolean" ? paused : void 0;
73010
+ const parUnlockCostPerShare = toBigInt16(usual);
73011
+ return {
73012
+ totalAssets: backing,
73013
+ totalSupply,
73014
+ exchangeRate,
73015
+ expiry: Number(endTime),
73016
+ paused: isPaused,
73017
+ // The floor exit's haircut, in the field every reader uses for "what
73018
+ // the immediate exit costs". Rounded UP: 0.92 → 800 bps exactly, and a
73019
+ // floor that is not a whole bps never reads cheaper than it is.
73020
+ withdrawFeeBps: floorPrice !== void 0 && floorPrice > 0n && floorPrice <= ONE_E1815 ? Number((ONE_E1815 - floorPrice + 10n ** 14n - 1n) / 10n ** 14n) : void 0,
73021
+ // Unread ⇒ assume open (the immediate exits are the norm); a READ pause
73022
+ // closes every leg, maturity included.
73023
+ instantRedeemEnabled: isPaused !== true,
73024
+ // Every immediate exit pays out of the bond's own USD0 — at most the
73025
+ // whole backing, never more.
73026
+ instantRedeemCapacity: isPaused === true ? 0n : backing,
73027
+ bond: {
73028
+ floorPrice,
73029
+ startTime: startTime !== void 0 ? Number(startTime) : void 0,
73030
+ redemptionTokenSupply: cfg?.redemptionToken ? toBigInt16(rtSupply) : void 0,
73031
+ parUnlockCostPerShare
73032
+ }
73033
+ };
73034
+ }
73035
+ };
73036
+ };
73037
+
72695
73038
  // src/vaults/savings/readers/venusHub.ts
72696
73039
  var NEUTRAL_PROBE2 = "0x000000000000000000000000000000000000dead";
72697
73040
  var BPS_TO_PCT = 100;
@@ -72983,6 +73326,8 @@ var buildReader2 = (entry) => {
72983
73326
  return readerSaturnVault(entry);
72984
73327
  case "nest-vault":
72985
73328
  return readerNestVault(entry);
73329
+ case "usual-bond":
73330
+ return readerUsualBond(entry);
72986
73331
  case "erc4626-cooldown":
72987
73332
  return readerErc4626Cooldown(entry);
72988
73333
  case "erc4626-idle":
@@ -73094,6 +73439,18 @@ var fetchSavingsVaults = async (chainId, multicallRetry, prices = {}, tokenList
73094
73439
  const redemptionDiscountBps = state.fundamentalExchangeRate !== void 0 && state.fundamentalExchangeRate > 0n ? Number(
73095
73440
  state.exchangeRate * 100000000n / state.fundamentalExchangeRate - 100000000n
73096
73441
  ) / 1e4 : void 0;
73442
+ const bond = state.bond;
73443
+ const isBond = entry.withdrawalMode === "fee-or-maturity";
73444
+ const sharePriceUsd = prices[addressLc];
73445
+ const redeemableUsd = priceUsd !== void 0 ? Number(state.exchangeRate) / 1e18 * priceUsd : 0;
73446
+ const marketDiscountBps = isBond && sharePriceUsd !== void 0 && redeemableUsd > 0 ? Math.round((sharePriceUsd / redeemableUsd - 1) * 1e5) / 10 : void 0;
73447
+ const parToken = entry.usualBond?.parUnlockToken.toLowerCase();
73448
+ const parTokenPrice = parToken !== void 0 ? prices[parToken] : void 0;
73449
+ const parTokenDec = parToken !== void 0 ? tokenList[parToken]?.decimals ?? 18 : 18;
73450
+ const parUnlockFeeBps = bond?.parUnlockCostPerShare !== void 0 && parTokenPrice !== void 0 && redeemableUsd > 0 ? Math.round(
73451
+ Number(bond.parUnlockCostPerShare) / 10 ** parTokenDec * parTokenPrice * 1e4 / redeemableUsd
73452
+ ) : void 0;
73453
+ const rtSupply = bond?.redemptionTokenSupply;
73097
73454
  const productName = entry.label ?? entry.symbol;
73098
73455
  const displayName = entry.label ? `${entry.brand} ${entry.label}` : composeVaultDisplayName(
73099
73456
  entry.brand,
@@ -73155,6 +73512,18 @@ var fetchSavingsVaults = async (chainId, multicallRetry, prices = {}, tokenList
73155
73512
  withdrawalCooldownSeconds: state.withdrawalCooldownSeconds ?? entry.withdrawalCooldownSeconds,
73156
73513
  withdrawFeeBps: state.withdrawFeeBps,
73157
73514
  depositFeeBps: state.depositFeeBps,
73515
+ // Bond terms — absent on every perpetual vault. See the type.
73516
+ expiry: state.expiry ?? entry.expiry,
73517
+ isPaused: state.paused,
73518
+ floorPrice: bond?.floorPrice?.toString(),
73519
+ marketDiscountBps,
73520
+ bond: bond ? {
73521
+ startTime: bond.startTime,
73522
+ redemptionToken: entry.usualBond?.redemptionToken.toLowerCase(),
73523
+ redemptionTokenSupply: rtSupply?.toString(),
73524
+ parUnlockToken: parToken,
73525
+ parUnlockCostPerShare: bond.parUnlockCostPerShare?.toString()
73526
+ } : void 0,
73158
73527
  // Term-sheet inputs that no reader can derive — see the registry's
73159
73528
  // docstrings. Passed through verbatim so the sheet can stop hardcoding
73160
73529
  // "compounds per second" and "needs an approval" for every vault.
@@ -73189,7 +73558,14 @@ var fetchSavingsVaults = async (chainId, multicallRetry, prices = {}, tokenList
73189
73558
  exitMinAmountFormatted: state.exitMinAmount !== void 0 ? Number(state.exitMinAmount) / 10 ** underlyingDec : void 0,
73190
73559
  // Adds the `market` leg — the only route a holder without the
73191
73560
  // protocol's mint/redeem permission actually has.
73192
- secondaryMarketOnly: entry.secondaryMarketOnly
73561
+ secondaryMarketOnly: entry.secondaryMarketOnly,
73562
+ // Bond legs (`fee-or-maturity` only; ignored by every other mode).
73563
+ expiry: state.expiry ?? entry.expiry,
73564
+ redemptionTokenCapacity: rtSupply?.toString(),
73565
+ redemptionTokenCapacityFormatted: rtSupply !== void 0 ? Number(rtSupply) / 10 ** underlyingDec : void 0,
73566
+ parUnlockAvailable: bond?.parUnlockCostPerShare !== void 0 && bond.parUnlockCostPerShare > 0n,
73567
+ parUnlockFeeBps,
73568
+ parUnlockTokenSymbol: entry.usualBond?.parUnlockTokenSymbol
73193
73569
  }),
73194
73570
  resolveSelfOnly(chainId, entry.address)
73195
73571
  ),
@@ -74224,6 +74600,7 @@ var classifyRedemption = (provider, v) => {
74224
74600
  if (SYNC_REDEMPTION_PROVIDERS.has(provider)) return "sync";
74225
74601
  if (provider === "lst") return "async";
74226
74602
  if (provider === "savings") {
74603
+ if (v.withdrawalMode === "fee-or-maturity") return void 0;
74227
74604
  return v.withdrawalMode === "instant" && !v.withdrawalCooldownSeconds ? "sync" : "async";
74228
74605
  }
74229
74606
  return "sync";
@@ -75610,7 +75987,7 @@ function clearPendleMarketsCache() {
75610
75987
  marketsCache = void 0;
75611
75988
  assetsCache = void 0;
75612
75989
  }
75613
- async function getJson5(url, label) {
75990
+ async function getJson6(url, label) {
75614
75991
  const response = await fetch(url, { headers: { Accept: "application/json" } });
75615
75992
  if (!response.ok) {
75616
75993
  throw new Error(
@@ -75631,7 +76008,7 @@ function cache5(slot, build6, store) {
75631
76008
  function fetchPendleApiMarkets() {
75632
76009
  return cache5(
75633
76010
  marketsCache,
75634
- async () => (await getJson5(PENDLE_MARKETS_URL, "markets"))?.markets ?? [],
76011
+ async () => (await getJson6(PENDLE_MARKETS_URL, "markets"))?.markets ?? [],
75635
76012
  (c) => {
75636
76013
  marketsCache = c;
75637
76014
  }
@@ -75642,7 +76019,7 @@ function fetchPendleApiAssets() {
75642
76019
  return cache5(
75643
76020
  assetsCache,
75644
76021
  async () => {
75645
- const json = await getJson5(PENDLE_ASSETS_URL, "assets");
76022
+ const json = await getJson6(PENDLE_ASSETS_URL, "assets");
75646
76023
  const map = /* @__PURE__ */ new Map();
75647
76024
  for (const a of json?.assets ?? []) {
75648
76025
  if (a?.chainId == null || !a?.address) continue;
@@ -76567,6 +76944,7 @@ var exitPhrase = {
76567
76944
  "request-based": "withdrawals need a request",
76568
76945
  "market-sale": "exit by selling on the market",
76569
76946
  "at-maturity": "locked until maturity",
76947
+ "fee-or-maturity": "exit early at a discount, or at par at maturity",
76570
76948
  "off-chain": "exit off-chain",
76571
76949
  "dex-only": "exit only via a DEX"
76572
76950
  };
@@ -76606,6 +76984,7 @@ function headlineExitFromRoutes(routes2, symbol) {
76606
76984
  const fastPart = fast.feeBps == null ? "instant exit" : fast.feeBps === 0 ? "free instant exit" : `instant exit for ${(fast.feeBps / 100).toFixed(2)} %`;
76607
76985
  const slowCost = slow.feeBps === 0 ? "free" : slow.feeBps != null ? `${(slow.feeBps / 100).toFixed(2)} %` : void 0;
76608
76986
  const floor = slow.minAmountFormatted != null && slow.minAmountFormatted > 0 ? ` above ${amount(slow.minAmountFormatted)}${symbol ? ` ${symbol}` : ""}` : "";
76987
+ if (slow.id === "maturity") return `${fastPart}, or par at maturity`;
76609
76988
  const slowPart = slowCost ? `queue${floor} for ${slowCost}` : `queue${floor}`;
76610
76989
  return `${fastPart}, or ${slowPart}`;
76611
76990
  }
@@ -78958,6 +79337,7 @@ var EXIT_MODE_LABELS = {
78958
79337
  "request-based": "Request",
78959
79338
  "market-sale": "Market sale",
78960
79339
  "at-maturity": "At maturity",
79340
+ "fee-or-maturity": "Fee or maturity",
78961
79341
  "off-chain": "Off-chain",
78962
79342
  "dex-only": "DEX only"
78963
79343
  };
@@ -78971,6 +79351,7 @@ var EXIT_MODE_DESCRIPTIONS = {
78971
79351
  "request-based": "Withdrawals need a request and a later claim.",
78972
79352
  "market-sale": "Exit by selling the position on a market.",
78973
79353
  "at-maturity": "Locked until maturity.",
79354
+ "fee-or-maturity": "Exit early at a discount, or at par at maturity.",
78974
79355
  "off-chain": "Exit is settled off-chain.",
78975
79356
  "dex-only": "Exit only by selling on a DEX."
78976
79357
  };
@@ -79277,7 +79658,10 @@ var UNCAPPED_EXIT_MODES = /* @__PURE__ */ new Set([
79277
79658
  "instant-or-queued",
79278
79659
  "request-based",
79279
79660
  "off-chain",
79280
- "at-maturity"
79661
+ "at-maturity",
79662
+ // A bond's par leg is the maturity, uncapped like `at-maturity`; the early
79663
+ // legs are bounded by the backing, which `liquidity` already reports.
79664
+ "fee-or-maturity"
79281
79665
  ]);
79282
79666
  function meetsLiquidityFloor(input, minUsd) {
79283
79667
  if (input.canWithdraw === false) return false;
@@ -79838,7 +80222,10 @@ function savingsCuration(provider, chainId, address) {
79838
80222
  solvency: e.solvency,
79839
80223
  accrual: e.accrual,
79840
80224
  yieldWarmupSeconds: e.yieldWarmupSeconds,
79841
- needsDepositApproval: e.needsDepositApproval
80225
+ needsDepositApproval: e.needsDepositApproval,
80226
+ // A dated bond's maturity (Usual bUSD0) — static, and the one fact that
80227
+ // keeps a proxied bond row from rendering as perpetual.
80228
+ expiry: e.expiry
79842
80229
  };
79843
80230
  }
79844
80231
  function vaultTermInputFromEarnMarket(m) {
@@ -79898,7 +80285,7 @@ function vaultTermInputFromEarnMarket(m) {
79898
80285
  withdrawalsPaused: m.availability.canWithdraw ? void 0 : true,
79899
80286
  isClosed: bool2(meta.isClosed),
79900
80287
  whitelisted: bool2(meta.whitelisted),
79901
- expiry: m.maturity?.maturity,
80288
+ expiry: m.maturity?.maturity ?? curated.expiry,
79902
80289
  timelock: num14(meta.timelock),
79903
80290
  allocators: strArray(meta.allocators),
79904
80291
  owner: str7(meta.owner),
@@ -79980,7 +80367,7 @@ function vaultTermInputFromSourceRow(row, chainId) {
79980
80367
  withdrawalsPaused: bool2(meta.isWithdrawalPaused),
79981
80368
  isClosed: bool2(meta.isClosed),
79982
80369
  whitelisted: bool2(meta.whitelisted),
79983
- expiry: num14(meta.expiry) ?? num14(meta.maturity),
80370
+ expiry: num14(meta.expiry) ?? num14(meta.maturity) ?? curated.expiry,
79984
80371
  timelock: num14(meta.timelock),
79985
80372
  allocators: strArray(meta.allocators),
79986
80373
  owner: str7(meta.owner),
@@ -82784,6 +83171,11 @@ var isNum4 = (v) => typeof v === "number" && Number.isFinite(v);
82784
83171
  function build2(input) {
82785
83172
  const vault = jupiterLendVaultFromKey(input.lender);
82786
83173
  const address = input.asset?.address ?? input.underlying;
83174
+ const smart = input.market?.fluid ?? {};
83175
+ const isSmartCol = !!smart.isSmartCol;
83176
+ const isSmartDebt = !!smart.isSmartDebt;
83177
+ const isSmart = isSmartCol || isSmartDebt;
83178
+ const pair = (p) => Array.isArray(p) ? p.join(" + ") : void 0;
82787
83179
  const pending = {
82788
83180
  "borrow.liquidation.liquidationMaxLimit": "the max liquidation limit (the LTV past which a position is liquidated ENTIRELY) is not served",
82789
83181
  "borrow.availability.cap": "the layer's EXPANDING borrow limit (current, hard max, expand percent and duration) is not served \u2014 it is not a cap, so the row carries none",
@@ -82844,12 +83236,22 @@ function build2(input) {
82844
83236
  constraints: {
82845
83237
  crossMargin: false,
82846
83238
  positionModel: "nft",
82847
- positionIdMeaning: "The vault position NFT id (per vault; one collateral / debt pair each). positionId 0 MINTS a new position \u2014 only an opening deposit may pass 0; any other action sent with 0 acts on a fresh empty position and reports success."
83239
+ positionIdMeaning: "The vault position NFT id (per vault; one collateral / debt pair each). positionId 0 MINTS a new position \u2014 only an opening deposit may pass 0; any other action sent with 0 acts on a fresh empty position and reports success.",
83240
+ ...isSmart ? {
83241
+ // the fluid adapter's notes: a smart side is a DEX share, not a token
83242
+ notes: [
83243
+ isSmartCol ? `Collateral is a Jupiter Lend DEX position (shares) over ${pair(smart.collateralPair) ?? "two tokens"}; this row is one leg of it.` : void 0,
83244
+ isSmartDebt ? `Debt is a Jupiter Lend DEX position (shares) over ${pair(smart.debtPair) ?? "two tokens"}; this row is one leg of it.` : void 0,
83245
+ isSmartDebt ? "The borrow rate is NET of the trading yield the debt LP earns, so it can be negative." : void 0,
83246
+ isSmartCol ? "The supply rate INCLUDES the DEX trading yield (trailing 7 days, after the protocol revenue cut) on top of the liquidity-layer rate." : void 0,
83247
+ "Both legs of a side move together \u2014 a leg cannot be supplied, borrowed or withdrawn on its own; the pool rebalances the split."
83248
+ ].filter(Boolean)
83249
+ } : {}
82848
83250
  },
82849
83251
  ext: {
82850
83252
  jupiterLend: {
82851
83253
  ...vault ? { market: vault.market, vaultId: vault.vaultId } : {},
82852
- fluidVaultType: FLUID_VAULT_TYPE_T1,
83254
+ fluidVaultType: isNum4(smart.vaultType) ? smart.vaultType : FLUID_VAULT_TYPE_T1,
82853
83255
  ...vault?.market === "main" ? { programs: JL_MAIN_PROGRAMS } : {},
82854
83256
  /** The marketUid ref is the leg's MINT. */
82855
83257
  marketUidRef: "mint"
@@ -84198,7 +84600,13 @@ var SVM_EARN_VAULT_BUILDERS = {
84198
84600
  "savings:Nest": { verbs: ["deposit", "request-withdraw"] },
84199
84601
  // PRIME / AUTO: the vault-stake pool's own stake / redeem, same
84200
84602
  // transaction; wYLDS in (or USDC, minted 1:1 first), wYLDS out
84201
- "savings:Hastra": { verbs: ["deposit", "withdraw"], payAsset: true }
84603
+ "savings:Hastra": { verbs: ["deposit", "withdraw"], payAsset: true },
84604
+ // INF: Sanctum Infinity's own add / remove liquidity against wSOL
84605
+ "savings:Sanctum": {
84606
+ verbs: ["deposit", "withdraw"],
84607
+ nativeDeposit: true,
84608
+ nativeWithdraw: true
84609
+ }
84202
84610
  };
84203
84611
  var UNBUILT_REASONS = {
84204
84612
  exponent: "Exponent PTs are not tradable through this API yet",