@1delta/margin-fetcher 5.0.109 → 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.d.ts CHANGED
@@ -8316,7 +8316,9 @@ type SupplyExitMode = Open<'instant' | 'instant-capped' | 'instant-or-queued' |
8316
8316
  /** Sell the instrument on a book (Term, TermMax, Midnight). */
8317
8317
  | 'market-sale'
8318
8318
  /** No early exit at all. */
8319
- | 'at-maturity' | 'off-chain' | 'dex-only'>;
8319
+ | 'at-maturity'
8320
+ /** A dated bond: par at maturity, or earlier at a price (Usual bUSD0). */
8321
+ | 'fee-or-maturity' | 'off-chain' | 'dex-only'>;
8320
8322
  /**
8321
8323
  * One concrete way out, as published by the vault row. Re-exported from the
8322
8324
  * vault layer so a term sheet and the `/v1/data/vaults` row cannot disagree
@@ -9108,7 +9110,14 @@ type DeepPartial<T> = {
9108
9110
  * kept separate so the two providers can evolve independently without
9109
9111
  * one provider's withdrawal taxonomy creep affecting the other.
9110
9112
  */
9111
- type SavingsWithdrawalMode = 'instant' | 'instant-capped' | 'fixed-cooldown' | 'queued' | 'request-based' | 'fee-or-queued';
9113
+ type SavingsWithdrawalMode = 'instant' | 'instant-capped' | 'fixed-cooldown' | 'queued' | 'request-based' | 'fee-or-queued'
9114
+ /**
9115
+ * A fixed-maturity BOND: redeemable at par on a date, or earlier for a
9116
+ * price — Usual's bUSD0 (floor-price unlock, a par unlock paid in USUAL, or
9117
+ * par with the redemption token). There is no request and no queue; the
9118
+ * "wait" is the bond's own end, in `expiry`.
9119
+ */
9120
+ | 'fee-or-maturity';
9112
9121
  /**
9113
9122
  * Parsed savings-vault entry.
9114
9123
  *
@@ -9354,6 +9363,44 @@ interface SavingsVault extends VaultClassificationFields {
9354
9363
  * only per-share number there is.
9355
9364
  */
9356
9365
  redemptionDiscountBps?: number;
9366
+ /**
9367
+ * Unix seconds at which the position MATURES and redeems at par. The term
9368
+ * sheet reads it as a `fixed-date` maturity (`termSheet.supply.maturity`),
9369
+ * which also closes entry once it passes. Same field name and units as
9370
+ * `PendlePt.expiry`.
9371
+ */
9372
+ expiry?: number;
9373
+ /**
9374
+ * 1e18-scaled underlying paid per share by the guaranteed EARLY exit
9375
+ * (bUSD0's `getFloorPrice()`, 0.92 at integration). `withdrawFeeBps` carries
9376
+ * the same fact as a haircut on that leg. Absent ⇒ not read; `'0'` ⇒ the
9377
+ * floor exit is switched off.
9378
+ */
9379
+ floorPrice?: string;
9380
+ /**
9381
+ * Signed basis-point gap between the share's MARKET price and what it
9382
+ * redeems for (`exchangeRate` × underlying price): negative ⇒ it trades
9383
+ * below par. Only computed for `fee-or-maturity` rows, and only when the
9384
+ * caller's price map carries the share token itself — bUSD0 traded at ~0.971
9385
+ * against USD0 at integration, i.e. ≈ −290 bps, which is the yield a buyer
9386
+ * locks in by holding to `expiry`.
9387
+ */
9388
+ marketDiscountBps?: number;
9389
+ /** `true` when the protocol's user paths are paused. Absent ⇒ not read. */
9390
+ isPaused?: boolean;
9391
+ /** The rest of a bond's terms — see `SavingsOnChainState.bond`. */
9392
+ bond?: {
9393
+ /** Unix seconds the bond period began. */
9394
+ startTime?: number;
9395
+ /** Token a par exit before maturity burns one-for-one (rt-bUSD0). */
9396
+ redemptionToken?: string;
9397
+ /** Its supply, raw — the most that can EVER exit early at par. */
9398
+ redemptionTokenSupply?: string;
9399
+ /** Token the par early unlock is paid in (USUAL). */
9400
+ parUnlockToken?: string;
9401
+ /** Raw `parUnlockToken` pulled per ONE share by that unlock. */
9402
+ parUnlockCostPerShare?: string;
9403
+ };
9357
9404
  /**
9358
9405
  * Curated loss-waterfall / backing classification, straight from the
9359
9406
  * registry entry. Absent ⇒ not assessed (NOT "safe").
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";
@@ -67581,6 +67626,87 @@ var marketRoute = () => ({
67581
67626
  settlement: "market",
67582
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."
67583
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
+ };
67584
67710
  var deriveExitRoutes = (i) => {
67585
67711
  const routes2 = deriveProtocolExitRoutes(i);
67586
67712
  return i.secondaryMarketOnly ? [...routes2, marketRoute()] : routes2;
@@ -67607,6 +67733,8 @@ var deriveProtocolExitRoutes = (i) => {
67607
67733
  // inventory instead.
67608
67734
  case "instant-or-queued":
67609
67735
  return [instantRoute(i, i.withdrawFeeBps ?? 0), queuedRoute(i)];
67736
+ case "fee-or-maturity":
67737
+ return bondExitRoutes(i);
67610
67738
  case "instant":
67611
67739
  return [
67612
67740
  {
@@ -72236,6 +72364,30 @@ var NEST_FEE_INSTANT_REDEMPTION = 0;
72236
72364
  var NEST_FEE_DEPOSIT = 1;
72237
72365
  var NEST_FEE_REDEMPTION = 2;
72238
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
+
72239
72391
  // src/vaults/savings/readers/erc4626Idle.ts
72240
72392
  var readerErc4626Idle = (entry) => {
72241
72393
  const shareUnit = 10n ** BigInt(entry.decimals);
@@ -72801,6 +72953,88 @@ var readerSaturnVault = (entry) => {
72801
72953
  };
72802
72954
  };
72803
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
+
72804
73038
  // src/vaults/savings/readers/venusHub.ts
72805
73039
  var NEUTRAL_PROBE2 = "0x000000000000000000000000000000000000dead";
72806
73040
  var BPS_TO_PCT = 100;
@@ -73092,6 +73326,8 @@ var buildReader2 = (entry) => {
73092
73326
  return readerSaturnVault(entry);
73093
73327
  case "nest-vault":
73094
73328
  return readerNestVault(entry);
73329
+ case "usual-bond":
73330
+ return readerUsualBond(entry);
73095
73331
  case "erc4626-cooldown":
73096
73332
  return readerErc4626Cooldown(entry);
73097
73333
  case "erc4626-idle":
@@ -73203,6 +73439,18 @@ var fetchSavingsVaults = async (chainId, multicallRetry, prices = {}, tokenList
73203
73439
  const redemptionDiscountBps = state.fundamentalExchangeRate !== void 0 && state.fundamentalExchangeRate > 0n ? Number(
73204
73440
  state.exchangeRate * 100000000n / state.fundamentalExchangeRate - 100000000n
73205
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;
73206
73454
  const productName = entry.label ?? entry.symbol;
73207
73455
  const displayName = entry.label ? `${entry.brand} ${entry.label}` : composeVaultDisplayName(
73208
73456
  entry.brand,
@@ -73264,6 +73512,18 @@ var fetchSavingsVaults = async (chainId, multicallRetry, prices = {}, tokenList
73264
73512
  withdrawalCooldownSeconds: state.withdrawalCooldownSeconds ?? entry.withdrawalCooldownSeconds,
73265
73513
  withdrawFeeBps: state.withdrawFeeBps,
73266
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,
73267
73527
  // Term-sheet inputs that no reader can derive — see the registry's
73268
73528
  // docstrings. Passed through verbatim so the sheet can stop hardcoding
73269
73529
  // "compounds per second" and "needs an approval" for every vault.
@@ -73298,7 +73558,14 @@ var fetchSavingsVaults = async (chainId, multicallRetry, prices = {}, tokenList
73298
73558
  exitMinAmountFormatted: state.exitMinAmount !== void 0 ? Number(state.exitMinAmount) / 10 ** underlyingDec : void 0,
73299
73559
  // Adds the `market` leg — the only route a holder without the
73300
73560
  // protocol's mint/redeem permission actually has.
73301
- 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
73302
73569
  }),
73303
73570
  resolveSelfOnly(chainId, entry.address)
73304
73571
  ),
@@ -74333,6 +74600,7 @@ var classifyRedemption = (provider, v) => {
74333
74600
  if (SYNC_REDEMPTION_PROVIDERS.has(provider)) return "sync";
74334
74601
  if (provider === "lst") return "async";
74335
74602
  if (provider === "savings") {
74603
+ if (v.withdrawalMode === "fee-or-maturity") return void 0;
74336
74604
  return v.withdrawalMode === "instant" && !v.withdrawalCooldownSeconds ? "sync" : "async";
74337
74605
  }
74338
74606
  return "sync";
@@ -76676,6 +76944,7 @@ var exitPhrase = {
76676
76944
  "request-based": "withdrawals need a request",
76677
76945
  "market-sale": "exit by selling on the market",
76678
76946
  "at-maturity": "locked until maturity",
76947
+ "fee-or-maturity": "exit early at a discount, or at par at maturity",
76679
76948
  "off-chain": "exit off-chain",
76680
76949
  "dex-only": "exit only via a DEX"
76681
76950
  };
@@ -76715,6 +76984,7 @@ function headlineExitFromRoutes(routes2, symbol) {
76715
76984
  const fastPart = fast.feeBps == null ? "instant exit" : fast.feeBps === 0 ? "free instant exit" : `instant exit for ${(fast.feeBps / 100).toFixed(2)} %`;
76716
76985
  const slowCost = slow.feeBps === 0 ? "free" : slow.feeBps != null ? `${(slow.feeBps / 100).toFixed(2)} %` : void 0;
76717
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`;
76718
76988
  const slowPart = slowCost ? `queue${floor} for ${slowCost}` : `queue${floor}`;
76719
76989
  return `${fastPart}, or ${slowPart}`;
76720
76990
  }
@@ -79067,6 +79337,7 @@ var EXIT_MODE_LABELS = {
79067
79337
  "request-based": "Request",
79068
79338
  "market-sale": "Market sale",
79069
79339
  "at-maturity": "At maturity",
79340
+ "fee-or-maturity": "Fee or maturity",
79070
79341
  "off-chain": "Off-chain",
79071
79342
  "dex-only": "DEX only"
79072
79343
  };
@@ -79080,6 +79351,7 @@ var EXIT_MODE_DESCRIPTIONS = {
79080
79351
  "request-based": "Withdrawals need a request and a later claim.",
79081
79352
  "market-sale": "Exit by selling the position on a market.",
79082
79353
  "at-maturity": "Locked until maturity.",
79354
+ "fee-or-maturity": "Exit early at a discount, or at par at maturity.",
79083
79355
  "off-chain": "Exit is settled off-chain.",
79084
79356
  "dex-only": "Exit only by selling on a DEX."
79085
79357
  };
@@ -79386,7 +79658,10 @@ var UNCAPPED_EXIT_MODES = /* @__PURE__ */ new Set([
79386
79658
  "instant-or-queued",
79387
79659
  "request-based",
79388
79660
  "off-chain",
79389
- "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"
79390
79665
  ]);
79391
79666
  function meetsLiquidityFloor(input, minUsd) {
79392
79667
  if (input.canWithdraw === false) return false;
@@ -79947,7 +80222,10 @@ function savingsCuration(provider, chainId, address) {
79947
80222
  solvency: e.solvency,
79948
80223
  accrual: e.accrual,
79949
80224
  yieldWarmupSeconds: e.yieldWarmupSeconds,
79950
- 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
79951
80229
  };
79952
80230
  }
79953
80231
  function vaultTermInputFromEarnMarket(m) {
@@ -80007,7 +80285,7 @@ function vaultTermInputFromEarnMarket(m) {
80007
80285
  withdrawalsPaused: m.availability.canWithdraw ? void 0 : true,
80008
80286
  isClosed: bool2(meta.isClosed),
80009
80287
  whitelisted: bool2(meta.whitelisted),
80010
- expiry: m.maturity?.maturity,
80288
+ expiry: m.maturity?.maturity ?? curated.expiry,
80011
80289
  timelock: num14(meta.timelock),
80012
80290
  allocators: strArray(meta.allocators),
80013
80291
  owner: str7(meta.owner),
@@ -80089,7 +80367,7 @@ function vaultTermInputFromSourceRow(row, chainId) {
80089
80367
  withdrawalsPaused: bool2(meta.isWithdrawalPaused),
80090
80368
  isClosed: bool2(meta.isClosed),
80091
80369
  whitelisted: bool2(meta.whitelisted),
80092
- expiry: num14(meta.expiry) ?? num14(meta.maturity),
80370
+ expiry: num14(meta.expiry) ?? num14(meta.maturity) ?? curated.expiry,
80093
80371
  timelock: num14(meta.timelock),
80094
80372
  allocators: strArray(meta.allocators),
80095
80373
  owner: str7(meta.owner),
@@ -82893,6 +83171,11 @@ var isNum4 = (v) => typeof v === "number" && Number.isFinite(v);
82893
83171
  function build2(input) {
82894
83172
  const vault = jupiterLendVaultFromKey(input.lender);
82895
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;
82896
83179
  const pending = {
82897
83180
  "borrow.liquidation.liquidationMaxLimit": "the max liquidation limit (the LTV past which a position is liquidated ENTIRELY) is not served",
82898
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",
@@ -82953,12 +83236,22 @@ function build2(input) {
82953
83236
  constraints: {
82954
83237
  crossMargin: false,
82955
83238
  positionModel: "nft",
82956
- 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
+ } : {}
82957
83250
  },
82958
83251
  ext: {
82959
83252
  jupiterLend: {
82960
83253
  ...vault ? { market: vault.market, vaultId: vault.vaultId } : {},
82961
- fluidVaultType: FLUID_VAULT_TYPE_T1,
83254
+ fluidVaultType: isNum4(smart.vaultType) ? smart.vaultType : FLUID_VAULT_TYPE_T1,
82962
83255
  ...vault?.market === "main" ? { programs: JL_MAIN_PROGRAMS } : {},
82963
83256
  /** The marketUid ref is the leg's MINT. */
82964
83257
  marketUidRef: "mint"