@1delta/margin-fetcher 0.0.3423 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { PublicClient, Address, Hex } from 'viem';
2
2
  import { Lender } from '@1delta/lender-registry';
3
- export { isAaveType, isAaveV2Type, isAaveV32Type, isAaveV3Type, isCompoundV3, isCompoundV3Type, isInit, isMorphoType, isMultiMarket, isYLDR } from '@1delta/lender-registry';
3
+ export { hasCrossMarginRisk, isAaveType, isAaveV2Type, isAaveV32Type, isAaveV3Type, isCompoundV3, isCompoundV3Type, isInit, isMorphoType, isMultiMarket, isYLDR } from '@1delta/lender-registry';
4
4
  import { DebitData, LenderDebitData, LstAcceptedInput } from '@1delta/calldata-sdk';
5
5
  import { RelayProxyConfig } from '@1delta/proxy-fetch';
6
- import { TermMarketConfig, LiquityBranchConfig, LiquityConfigChain, RiverMarketConfig, RiverConfigChain, RiverChainData, TellerPoolConfig, MorphoTypeVaultEntry } from '@1delta/data-sdk';
6
+ import { TermMarketConfig, LiquityBranchConfig, LiquityConfigChain, RiverMarketConfig, RiverConfigChain, RiverChainData, InverseMarketConfig, InverseConfigChain, InverseChainData, ResupplyConfigChain, LlamaLendMarketConfig, LlamaLendConfigChain, LlamaLendChainData, UsddMarketConfig, UsddConfigChain, UsddChainData, FrankencoinMarketConfig, FrankencoinConfigChain, FrankencoinChainData, TellerPoolConfig, MorphoTypeVaultEntry } from '@1delta/data-sdk';
7
7
  export { MorphoLensAbi } from '@1delta/abis';
8
8
 
9
9
  interface GenericCurrency {
@@ -329,6 +329,76 @@ interface FixedTermProvider {
329
329
  /** The single counterparty/venue contract, when there is one (Lista broker, Term servicer). */
330
330
  address?: string;
331
331
  }
332
+ /**
333
+ * Origination window for a fixed-term market whose terms are only obtainable
334
+ * during a bounded round rather than continuously (`provider.kind: 'auction'`
335
+ * — Term Finance).
336
+ *
337
+ * This is the difference between "the rate card is empty right now" and "this
338
+ * market is dead": between rounds a Term repo still has a maturity, collateral
339
+ * params and a last-cleared rate, but nothing can be borrowed until the next
340
+ * round is listed. Without it every closed repo renders as an ordinary
341
+ * borrowable market whose action silently cannot be built.
342
+ *
343
+ * `status` is a snapshot at fetch time; the timestamps are raw so a consumer
344
+ * can re-derive it live (and drive a countdown) against a cached response.
345
+ */
346
+ interface FixedTermAuction {
347
+ /**
348
+ * Round lifecycle at fetch time:
349
+ * - `upcoming` — listed but not yet accepting submissions (`now < startTime`)
350
+ * - `open` — accepting sealed bids/offers (`startTime ≤ now < revealTime`)
351
+ * - `revealing` — submissions closed, prices revealing / clearing pending
352
+ * (`revealTime ≤ now < endTime`)
353
+ * - `closed` — no round is currently listed for this market. Borrowing is
354
+ * unavailable until the next one; lending may still be
355
+ * possible on the secondary repo-token book.
356
+ */
357
+ status: 'upcoming' | 'open' | 'revealing' | 'closed';
358
+ /**
359
+ * Can a NEW borrow be opened right now? True only inside an open round —
360
+ * Term borrow origination is a sealed bid, so there is no other entry point.
361
+ *
362
+ * Consume this rather than re-deriving from `status`: it is the single flag
363
+ * a borrow CTA should gate on, and it stays correct if more statuses appear.
364
+ * It is NOT the same as `canLend` — see below.
365
+ */
366
+ canBorrow: boolean;
367
+ /**
368
+ * Can a NEW lend position be opened right now? Deliberately decoupled from
369
+ * `canBorrow`: the primary auction is only one of two lend surfaces, and
370
+ * buying repo tokens on the secondary book works between rounds. So a closed
371
+ * round leaves the market lend-only rather than fully inert, and a UI that
372
+ * greys out the whole market would be wrong.
373
+ */
374
+ canLend: boolean;
375
+ /**
376
+ * Seconds until submissions close (`revealTime − now`), or undefined when no
377
+ * round is open. A snapshot — for a live countdown, derive from `revealTime`.
378
+ */
379
+ secondsUntilClose?: number;
380
+ /**
381
+ * Ready-to-display consequences of this market's origination model, most
382
+ * important first. Mirrors `params.market.teller.implications`: auction
383
+ * mechanics are unusual enough that a UI showing only a rate misleads.
384
+ */
385
+ implications?: string[];
386
+ /** Round id. Absent when `status: 'closed'`. */
387
+ id?: string;
388
+ /** Submissions open (unix seconds). Absent when `status: 'closed'`. */
389
+ startTime?: number;
390
+ /** Submissions CLOSE / reveal begins (unix seconds). Absent when closed. */
391
+ revealTime?: number;
392
+ /** Round clears (unix seconds). Absent when closed. */
393
+ endTime?: number;
394
+ /**
395
+ * Minimum submission size in loan-token base units (raw). Term rounds carry a
396
+ * real floor (e.g. 1000 USDC) — an amount below it cannot be submitted at all,
397
+ * so it belongs next to the terms rather than surfacing as a failed action.
398
+ */
399
+ minBorrowAmount?: string;
400
+ minLendAmount?: string;
401
+ }
332
402
  /**
333
403
  * Canonical fixed-term market descriptor, emitted on `params.market.fixedTerm`
334
404
  * for EVERY fixed-rate / fixed-maturity market (Lista brokered + Morpho
@@ -338,7 +408,7 @@ interface FixedTermProvider {
338
408
  */
339
409
  interface FixedTermInfo {
340
410
  /** Underlying fixed-term protocol shape. */
341
- model: 'lista' | 'midnight' | 'term' | 'exactly' | 'teller';
411
+ model: 'lista' | 'midnight' | 'term' | 'exactly' | 'teller' | 'termmax';
342
412
  /**
343
413
  * Single fixed calendar maturity (unix secs). Undefined for rolling-duration
344
414
  * menus (Lista) and multi-maturity markets (Exactly — the maturity menu lives
@@ -367,17 +437,40 @@ interface FixedTermInfo {
367
437
  earlyRepay: FixedTermEarlyRepay;
368
438
  /** Who offers the term (Lista broker vs Midnight order book). */
369
439
  provider?: FixedTermProvider;
440
+ /**
441
+ * Origination window, for `provider.kind: 'auction'` markets only (Term
442
+ * Finance). Absent for lenders whose terms are continuously available — a
443
+ * missing `auction` means "no window applies", NOT "closed".
444
+ */
445
+ auction?: FixedTermAuction;
370
446
  }
371
- /** A Lista loan, attached to its own entry in the positions array. */
447
+ /**
448
+ * A single fixed-term loan, attached to its own entry in the positions array.
449
+ *
450
+ * Named for Lista (the first producer) but SHARED by every fixed-term lender
451
+ * that emits per-loan rows — Lista, Exactly, TermMax, Teller. Fields are
452
+ * therefore mostly optional and several are lender-specific; see
453
+ * [FIXED_TERM_REPAY_TERMS.md](../../../../FIXED_TERM_REPAY_TERMS.md) for which
454
+ * lender populates what and for the exact repay economics behind each number.
455
+ *
456
+ * `loanId` is the WRITE TARGET and its meaning differs per lender (Lista posId /
457
+ * Exactly maturity-as-string / TermMax gtId / Teller bidId) — check the lender
458
+ * before using it. `termId` is the RATE-MENU id and is NOT interchangeable with
459
+ * it (Exactly is the only lender where the two coincide, both = maturity).
460
+ */
372
461
  interface ListaTermLoan {
373
462
  /** loanId — the repay target for the LISTA_BROKER_REPAY composer op. For fixed loans this is the
374
- * posId; for the dynamic (flexible) loan it is the dynamic sentinel (type(uint128).max). */
463
+ * posId; for the dynamic (flexible) loan it is the dynamic sentinel (type(uint128).max).
464
+ * Other lenders reuse the slot: Exactly = String(maturity), TermMax = gtId, Teller = bidId. */
375
465
  loanId: string;
376
466
  /** true for the flexible (dynamic / variable-rate) loan; fixed loans omit it */
377
467
  isDynamic?: boolean;
378
468
  /** best-effort term product id (matched from duration vs the current menu); may be undefined */
379
469
  termId?: number;
380
- /** outstanding debt in loan-token units (principal + accrued interest) */
470
+ /** outstanding debt in loan-token units. Lista: principal + accrued interest.
471
+ * Static-face-value lenders (Exactly / TermMax / Term): the EXIT-NOW cost —
472
+ * for Exactly that is discounted early and penalty-inflated when overdue, so
473
+ * compare against `faceValue` rather than assuming it is the face. */
381
474
  debt: string;
382
475
  /** locked annualised borrow rate in PERCENT (e.g. 3.857 = 3.857% APR) */
383
476
  apr?: number;
@@ -386,9 +479,27 @@ interface ListaTermLoan {
386
479
  termDays?: number;
387
480
  /** outstanding accrued interest in loan-token units */
388
481
  accruedInterest?: string;
389
- /** early-repayment penalty (loan-token units) to close the loan now; 0 once matured */
482
+ /** early-repayment penalty (loan-token units) to close the loan now; 0 once matured.
483
+ * Lista only — the OPPOSITE sign to Exactly's `earlyRepayDiscount` below. */
390
484
  earlyRepayPenalty?: string;
391
485
  isMatured?: boolean;
486
+ /** amount owed AT maturity (principal + fee). Static — no accrual index; it
487
+ * grows only via a late penalty where the protocol has one. */
488
+ faceValue?: string;
489
+ /** Exactly: rebate for repaying BEFORE maturity (`faceValue − debt`). Exactly
490
+ * never charges an early-repay fee, but this is 0 when the fixed pool has no
491
+ * unassigned earnings left, so it is not a guaranteed saving. */
492
+ earlyRepayDiscount?: string;
493
+ /** Exactly: penalty accrued so far past maturity (`debt − faceValue`). */
494
+ latePenalty?: string;
495
+ /** Exactly: further penalty per additional day overdue — LINEAR on face, not
496
+ * compounding. */
497
+ latePenaltyPerDay?: string;
498
+ /** annualized late-penalty rate in PERCENT (Exactly `penaltyRate`; ~164 %/yr).
499
+ * A mutable market parameter, snapshotted per fetch. */
500
+ latePenaltyApr?: number;
501
+ /** seconds past maturity; 0 until overdue */
502
+ secondsLate?: number;
392
503
  }
393
504
  interface MorphoLendingPositions extends BaseLendingPositions {
394
505
  isWhitelisted?: boolean;
@@ -592,6 +703,18 @@ declare const getLenderPublicDataViaApi: (chainId: string, lenders: string[], pr
592
703
  [lender: string]: any;
593
704
  }>;
594
705
 
706
+ /**
707
+ * Returns true when the lender should ONLY use the API path (no on-chain
708
+ * fallback). Currently Morpho-type only — the Morpho GraphQL indexer has
709
+ * been reliable on every chain it supports, so there's no benefit to
710
+ * double-fetching.
711
+ *
712
+ * Exported so the config-consistency test can assert the invariant that makes
713
+ * the Morpho list safe: a chain routed to the on-chain path MUST have a
714
+ * `MORPHO_LENS` entry, otherwise `buildMorphoCall` produces a call with an
715
+ * undefined address and the chain silently yields no markets at all.
716
+ */
717
+ declare function lenderApiOnly(lender: string, chainId: string): boolean;
595
718
  declare const getLenderPublicDataAll: (chainId: string, lenders: string[], prices: {
596
719
  [asset: string]: number;
597
720
  }, additionalYields: AdditionalYields, multicallRetry: MulticallRetryFunction, tokenList?: () => Promise<GenericTokenList>, includeUnlistedMorphoMarkets?: boolean) => Promise<{
@@ -664,6 +787,38 @@ declare function createRawRpcCalls(preparedCalls: PreparedCall[], batchSize?: nu
664
787
  declare function createMulticallRpcCall(preparedCalls: PreparedCall[], multicallAddress: string, batchSize?: number, blockTag?: string, allowFailure?: boolean): MulticallRpcBatch[];
665
788
 
666
789
  type Call = GeneralCall;
790
+ /**
791
+ * Sentinel written into the result array for a call that returned NO data —
792
+ * a revert, an RPC error, or a whole `aggregate3` chunk that was rejected
793
+ * (rate limit, subrequest cap, dropped connection: viem marks every call in a
794
+ * rejected chunk as `status: 'failure'`).
795
+ *
796
+ * It is NOT a zero value. Parsers must skip these slots — coercing one to `0`
797
+ * turns a failed read into a phantom "no balance" position and, worse, makes a
798
+ * real deposit or debt silently disappear from a user's portfolio.
799
+ */
800
+ declare const MULTICALL_FAILURE = "0x";
801
+ /** True when a multicall slot holds no usable data (see {@link MULTICALL_FAILURE}). */
802
+ declare const isFailedCall: (value: unknown) => boolean;
803
+ /** Reported for each endpoint that failed to serve a batch. */
804
+ interface EndpointFailure {
805
+ chainId: string;
806
+ /** Endpoint URL, or `rpc#<id>` when the transport does not expose one. */
807
+ url: string;
808
+ rpcId: number;
809
+ /** `transport` — the request itself died. `slots` — it answered with nothing but failures. */
810
+ kind: 'transport' | 'slots';
811
+ }
812
+ interface MulticallEndpointOptions {
813
+ /**
814
+ * Endpoint URLs already attempted for this call set. Failover consults it so
815
+ * a retry lands on an endpoint that has NOT already failed — see
816
+ * {@link resolveEndpoint}.
817
+ */
818
+ tried?: Set<string>;
819
+ /** Invoked for every endpoint that fails, so callers can demote it. */
820
+ onEndpointFailure?: (info: EndpointFailure) => void;
821
+ }
667
822
  declare function prepareMulticallInputs(abi: any[], calls: Call[]): PreparedCall[];
668
823
 
669
824
  interface PreparedUserDataRpcCalls {
@@ -926,33 +1081,106 @@ type UserData = {
926
1081
  * not as the user's full position.
927
1082
  */
928
1083
  incomplete?: boolean;
1084
+ /**
1085
+ * Set when this entry did NOT come from the current read: the live read failed
1086
+ * and a previously COMPLETE snapshot was served in its place. The position was
1087
+ * accurate as of `staleAgeMs` ago; it is not a partial read (those are
1088
+ * `incomplete`) and it is never served for a lender that read successfully.
1089
+ */
1090
+ stale?: boolean;
1091
+ /** Age of the served snapshot in ms. Only set alongside `stale`. */
1092
+ staleAgeMs?: number;
929
1093
  };
930
1094
 
931
- /** Reported per lender whose multicall slice contained failed reads. */
1095
+ /**
1096
+ * How a lender's slice reacts to a failed read.
1097
+ *
1098
+ * Note what this is NOT keyed on: "is the position cross-margin". EVERY position
1099
+ * with debt is corrupted by a lost read — an isolated Morpho market computes its
1100
+ * health from a collateral read and a debt read, so losing either fabricates the
1101
+ * same nonsense a lost Aave reserve does. The question here is narrower and
1102
+ * purely mechanical: **what is the smallest thing we can void?** A failed slot
1103
+ * carries no market label, so the only unit we can void is the lender key.
1104
+ *
1105
+ * - `strict` — void the whole slice on any lost read. Correct when the slice
1106
+ * resolves to ONE risk computation, which is true both for a single-market
1107
+ * lender and for a multi-market lender that is nevertheless cross-margin
1108
+ * (Exactly scores every market under one per-chain Auditor). Nothing smaller
1109
+ * can be voided, and publishing the remainder would publish a fiction.
1110
+ * - `lenient` — publish, flag `incomplete`, and let per-record invariant
1111
+ * validation catch the corrupted one. Correct ONLY when the slice fans out to
1112
+ * many INDEPENDENT isolated positions, where voiding the key would discard
1113
+ * hundreds of intact markets to hide one — a cure worse than the disease.
1114
+ *
1115
+ * So leniency requires BOTH properties: many independent positions AND no shared
1116
+ * risk computation across them.
1117
+ */
1118
+ type ReadFailurePolicy = 'strict' | 'lenient';
1119
+ declare const getReadFailurePolicy: (lender: string) => ReadFailurePolicy;
1120
+ /** Why a lender's slice did not convert cleanly. */
1121
+ type IncompleteReason =
1122
+ /** Every read in the slice failed. */
1123
+ 'all-reads-failed'
1124
+ /** Reads failed on a strict lender — the whole risk set was voided. */
1125
+ | 'partial-read-cross-margin'
1126
+ /** Reads failed on a lenient lender — surviving markets were published. */
1127
+ | 'partial-read'
1128
+ /** The converter threw. */
1129
+ | 'converter-error'
1130
+ /** The converted entry asserted something that cannot be true. */
1131
+ | 'invariant-violation';
1132
+ /** Reported per lender whose multicall slice did not convert cleanly. */
932
1133
  interface IncompleteLenderRead {
933
1134
  lender: string;
934
1135
  /** Number of slots in the lender's slice that returned no data. */
935
1136
  failedCalls: number;
1137
+ /**
1138
+ * Subset of `failedCalls` that could plausibly succeed on a re-read — i.e.
1139
+ * excluding calls known to have reverted. Zero means re-fetching this lender
1140
+ * is pointless: the markets in question always revert for this account.
1141
+ * Equals `failedCalls` when no `permanentFailures` set was supplied.
1142
+ */
1143
+ retryableFailedCalls: number;
936
1144
  /** Size of the lender's slice. */
937
1145
  totalCalls: number;
938
- /** True when EVERY read failed and the lender was skipped entirely. */
1146
+ /** True when nothing was published for this lender. */
939
1147
  dropped: boolean;
1148
+ /** What went wrong. */
1149
+ reason: IncompleteReason;
1150
+ /** Extra context for logs (converter message, violation details). */
1151
+ detail?: string;
940
1152
  }
941
1153
  interface ConvertLenderUserDataOptions {
942
- /** Invoked once per lender with failed reads — for logging / surfacing. */
1154
+ /** Invoked once per lender that did not convert cleanly — for logging / surfacing. */
943
1155
  onIncomplete?: (info: IncompleteLenderRead) => void;
1156
+ /**
1157
+ * Indices (into `rawResults`) of calls that failed deterministically, as
1158
+ * collected by `getLenderUserDataResult`. Used only to compute
1159
+ * `retryableFailedCalls`.
1160
+ */
1161
+ permanentFailures?: Set<number>;
944
1162
  }
945
1163
  /**
946
1164
  * Converts the raw results into the desired format
947
1165
  *
948
1166
  * Slots that hold the multicall failure sentinel are NOT data: a failed read
949
- * says nothing about the user's position. When every read for a lender failed
950
- * the lender is skipped outright converting would emit an all-zero entry that
951
- * is indistinguishable from "user has no position here", which is how a
952
- * rate-limited RPC ends up rendering phantom $0 rows (and, when only some calls
953
- * fail, understated balances). Partial failures still convert — the readable
954
- * positions are real but are flagged `incomplete` and reported via
955
- * `options.onIncomplete`.
1167
+ * says nothing about the user's position. Coercing one to zero is how a
1168
+ * rate-limited RPC ends up rendering phantom $0 rows and understated balances,
1169
+ * so failures are handled explicitly, in three gates:
1170
+ *
1171
+ * 1. **Failure policy** (see {@link ReadFailurePolicy}). Every read failing
1172
+ * drops the lender under either policy. Beyond that, a `strict`
1173
+ * (cross-margin) lender drops on ANY failure because its aggregates are only
1174
+ * meaningful over the complete set, while a `lenient` (multi-market) lender
1175
+ * publishes the markets that did read and is flagged `incomplete`.
1176
+ * 2. **Converter errors** are reported rather than swallowed — a throwing
1177
+ * converter used to leave a lender silently absent, indistinguishable from a
1178
+ * user with no position there.
1179
+ * 3. **Invariant validation** (see `validate.ts`) rejects sub-accounts that
1180
+ * cannot be true regardless of how the reads went — a `NaN` anywhere in the
1181
+ * aggregates, or (alongside failed reads) debt with no collateral behind it.
1182
+ *
1183
+ * Anything published after that is either complete or explicitly marked as not.
956
1184
  *
957
1185
  * @param chainId - The chain ID
958
1186
  * @param queriesRaw - The queries to fetch data for
@@ -965,6 +1193,53 @@ declare const convertLenderUserDataResult: (chainId: string, queriesRaw: LenderU
965
1193
  [lender: string]: UserData;
966
1194
  };
967
1195
 
1196
+ /**
1197
+ * Why this exists SEPARATELY from the failure sentinels
1198
+ * -----------------------------------------------------
1199
+ * The sentinel path (`isFailedCall`) catches reads that announced themselves as
1200
+ * failures. This catches the ones that did not: a decodable-but-wrong response,
1201
+ * a market whose metadata went missing so its price resolved to `undefined`, a
1202
+ * converter that divided by a zero it should never have seen. Those produce the
1203
+ * SAME user-visible artefact as a dropped read — a debt with no collateral, a
1204
+ * `NaN` health factor — while every slot reports success.
1205
+ *
1206
+ * So this is the last gate before a position is published: it asserts what must
1207
+ * be true of any real lending position, independent of how the data was
1208
+ * obtained.
1209
+ */
1210
+ /** One failed assertion about a sub-account's published shape. */
1211
+ interface InvariantViolation {
1212
+ /** Sub-account this fired on (`accountId`). */
1213
+ accountId: string;
1214
+ /** Machine-readable check name. */
1215
+ code: 'non-finite' | 'debt-without-collateral' | 'invalid-mode';
1216
+ /** Human-readable detail for logs. */
1217
+ detail: string;
1218
+ /**
1219
+ * `true` when the violation is only conclusive because the read was also
1220
+ * known-incomplete (see {@link validateUserData}).
1221
+ */
1222
+ requiresFailedReads: boolean;
1223
+ }
1224
+ interface ValidationResult {
1225
+ /** Sub-accounts that passed. Empty means the whole entry must be dropped. */
1226
+ kept: UserDataForSubAccount[];
1227
+ /** Every violation found, including ones on kept sub-accounts. */
1228
+ violations: InvariantViolation[];
1229
+ /** Sub-account ids dropped as corrupt. */
1230
+ dropped: string[];
1231
+ }
1232
+ /**
1233
+ * Validates a converted entry and drops the sub-accounts that cannot be true.
1234
+ *
1235
+ * `hadFailedReads` gates the checks whose violation is ambiguous on its own:
1236
+ * with a known-incomplete read, `debt-without-collateral` is the signature of a
1237
+ * dropped collateral slot and the sub-account is corrupt; with a clean read it
1238
+ * is a genuine (if grim) position and is kept. Unconditional checks — anything
1239
+ * non-finite — fire either way, because no read produces those legitimately.
1240
+ */
1241
+ declare function validateUserData(userData: UserData, hadFailedReads: boolean): ValidationResult;
1242
+
968
1243
  interface ExposureInfo {
969
1244
  asset: GenericCurrency;
970
1245
  collateralFactor: number;
@@ -1004,9 +1279,17 @@ declare function unflattenLenderData(pools: PoolWithMeta[]): LenderData;
1004
1279
  * @param logs - show multicall error logs, default is false
1005
1280
  * @param concurrency - number of distinct RPC endpoints to shard batches
1006
1281
  * across in parallel; <= 1 keeps the legacy single-endpoint path
1282
+ * @param permanentFailures - optional collector filled with the indices of
1283
+ * calls that failed DETERMINISTICALLY (revert / no code / unknown selector)
1284
+ * rather than because of the RPC. Pass it to
1285
+ * {@link convertLenderUserDataResult} so a caller can tell "this market
1286
+ * always reverts" from "this read was lost" and only re-fetch the latter.
1287
+ * @param onEndpointFailure - optional hook invoked for every RPC endpoint that
1288
+ * fails to serve a batch. Only the caller knows where to persist that (KV,
1289
+ * metrics), and without it every request rediscovers the same bad endpoint.
1007
1290
  * @returns The raw results from the multicall, "0x" for failures
1008
1291
  */
1009
- declare const getLenderUserDataResult: (chainId: string, queriesRaw: LenderUserQuery[], getEvmClient: GetEvmClientFunction, allowFailure?: boolean, batchSize?: number, retries?: number, logs?: boolean, concurrency?: number) => Promise<any[]>;
1292
+ declare const getLenderUserDataResult: (chainId: string, queriesRaw: LenderUserQuery[], getEvmClient: GetEvmClientFunction, allowFailure?: boolean, batchSize?: number, retries?: number, logs?: boolean, concurrency?: number, permanentFailures?: Set<number>, onEndpointFailure?: (info: EndpointFailure) => void) => Promise<any[]>;
1010
1293
  /**
1011
1294
  * Prepares the RPC calls for fetching user data without executing them
1012
1295
  * Uses multicall3 aggregate3 to batch all calls into a single RPC call
@@ -1361,6 +1644,21 @@ interface LenderDataEntry extends Omit<LenderSummary, 'subAccounts'> {
1361
1644
  account: string;
1362
1645
  lenderInfo?: LenderInfo;
1363
1646
  data: UserDataForSubAccount[];
1647
+ /**
1648
+ * Set when some of this lender's on-chain reads could not be completed. The
1649
+ * positions listed are real but the set is a LOWER BOUND — anything derived
1650
+ * from the whole picture (NAV, net APR, health factor) is unreliable and must
1651
+ * not be rendered as fact.
1652
+ */
1653
+ incomplete?: boolean;
1654
+ /**
1655
+ * Set when this entry was served from the last COMPLETE snapshot because the
1656
+ * live read failed. Internally consistent — unlike `incomplete` — but as of
1657
+ * `staleAgeMs` ago rather than now.
1658
+ */
1659
+ stale?: boolean;
1660
+ /** Age of the served snapshot in ms. Only set alongside `stale`. */
1661
+ staleAgeMs?: number;
1364
1662
  }
1365
1663
  /**
1366
1664
  * Input type for buildSummaries - user data result from convertLenderUserDataResult
@@ -1472,6 +1770,26 @@ interface MarketBook {
1472
1770
  bids: PublicBookLevel[];
1473
1771
  asks: PublicBookLevel[];
1474
1772
  }
1773
+ /**
1774
+ * One entry in a fixed-term rate menu. Lives on `params.market.terms` for
1775
+ * single-borrowable-asset markets, and on `data[*].terms` for cross-margin
1776
+ * multi-asset lenders (Exactly), where each asset has its own fixed pools.
1777
+ *
1778
+ * `termId` semantics are LENDER-SPECIFIC — Exactly/TermMax = the unix maturity,
1779
+ * Teller = duration in seconds, Lista = the broker product id, Midnight/Term =
1780
+ * `0` placeholder. See FIXED_TERM_REPAY_TERMS.md.
1781
+ */
1782
+ interface MarketTermEntry {
1783
+ termId: number;
1784
+ durationSecs: number;
1785
+ durationDays: number;
1786
+ /** annualised borrow rate in PERCENT (e.g. 3.857 = 3.857% APR) */
1787
+ apr: number;
1788
+ /** annualised fixed LEND rate in PERCENT at this maturity (Exactly only) */
1789
+ depositApr?: number;
1790
+ /** borrowable liquidity at this maturity, loan-token human units (Exactly only) */
1791
+ available?: number;
1792
+ }
1475
1793
  interface MorphoMarket {
1476
1794
  /** the 1delta lender enum */
1477
1795
  lender: string;
@@ -1502,19 +1820,13 @@ interface MorphoMarket {
1502
1820
  /** IRM rate floor */
1503
1821
  rateFloor?: string;
1504
1822
  /** Fixed-term rate menu — available term products (Lista brokered markets,
1505
- * Term/Midnight single-maturity markets, Exactly multi-maturity markets —
1506
- * for Exactly `termId` = the pool's unix maturity timestamp). */
1507
- terms?: {
1508
- termId: number;
1509
- durationSecs: number;
1510
- durationDays: number;
1511
- /** annualised borrow rate in PERCENT (e.g. 3.857 = 3.857% APR) */
1512
- apr: number;
1513
- /** annualised fixed LEND rate in PERCENT at this maturity (Exactly only) */
1514
- depositApr?: number;
1515
- /** borrowable liquidity at this maturity, loan-token human units (Exactly only) */
1516
- available?: number;
1517
- }[];
1823
+ * Term/Midnight single-maturity markets).
1824
+ *
1825
+ * MARKET-LEVEL menu, valid only when the lender key has ONE borrowable asset
1826
+ * (every isolated-market fixed-term lender). CROSS-MARGIN multi-asset
1827
+ * lenders — Exactly — carry a menu PER ASSET on `data[*].terms` instead,
1828
+ * since each asset has its own fixed pools. */
1829
+ terms?: MarketTermEntry[];
1518
1830
  /**
1519
1831
  * Canonical cross-protocol fixed-term descriptor (Lista brokered + Morpho
1520
1832
  * Midnight). Present on fixed-rate/fixed-maturity markets only. See
@@ -1565,7 +1877,7 @@ interface MorphoGeneralPublicResponse {
1565
1877
  * - `'zeroInterest'` — NO ongoing rate at all (River/Satoshi). The borrow
1566
1878
  * cost is the one-off `originationFee`, not an APR.
1567
1879
  */
1568
- rateModel?: 'variable' | 'userSet' | 'fixedTerm' | 'zeroInterest';
1880
+ rateModel?: 'variable' | 'userSet' | 'fixedTerm' | 'zeroInterest' | 'dbr' | 'protocolSet';
1569
1881
  /**
1570
1882
  * One-off fee charged ONCE at borrow time, as a PERCENT of the amount
1571
1883
  * borrowed (e.g. `0.5` = 0.5%). Front-loaded cost that is NOT an APR and
@@ -1575,6 +1887,20 @@ interface MorphoGeneralPublicResponse {
1575
1887
  * only for an effective-cost-since-open view.
1576
1888
  */
1577
1889
  originationFee?: number;
1890
+ /**
1891
+ * PER-ASSET fixed-term rate menu, for CROSS-MARGIN multi-asset fixed-term
1892
+ * lenders (Exactly): one lender key covers every asset, and each asset has
1893
+ * its own fixed pools, so the menu cannot live on `params.market`.
1894
+ * Isolated-market fixed-term lenders (Midnight, Term, Lista broker,
1895
+ * TermMax, Teller) keep using `params.market.terms` — read that as the
1896
+ * fallback when this is absent.
1897
+ */
1898
+ terms?: MarketTermEntry[];
1899
+ /**
1900
+ * PER-ASSET fixed-term descriptor, same rationale as `terms` above
1901
+ * (Exactly). Falls back to `params.market.fixedTerm` when absent.
1902
+ */
1903
+ fixedTerm?: FixedTermInfo;
1578
1904
  rewards?: RewardsList;
1579
1905
  decimals: number;
1580
1906
  config: {
@@ -2065,7 +2391,7 @@ interface MidnightBookSource {
2065
2391
 
2066
2392
  /** Default hosted Midnight API (see @morpho-org/midnight-sdk MidnightApi). */
2067
2393
  declare const DEFAULT_MIDNIGHT_API = "https://api.morpho.org/v0/midnight";
2068
- type FetchLike$1 = typeof fetch;
2394
+ type FetchLike$2 = typeof fetch;
2069
2395
  /**
2070
2396
  * Hosted-API book source. Reads `GET {base}/books/{marketId}` and reduces the
2071
2397
  * `asks`/`bids` price levels to a {@link MidnightBookTop}. This is the swappable
@@ -2074,7 +2400,7 @@ type FetchLike$1 = typeof fetch;
2074
2400
  declare class ApiBookSource implements MidnightBookSource {
2075
2401
  private readonly baseUrl;
2076
2402
  private readonly fetchImpl;
2077
- constructor(baseUrl: string, fetchImpl?: FetchLike$1);
2403
+ constructor(baseUrl: string, fetchImpl?: FetchLike$2);
2078
2404
  getBookTop(marketId: string): Promise<MidnightBookTop | null>;
2079
2405
  /**
2080
2406
  * Full ladder for a market — every level per side, best-first (unlike
@@ -2105,7 +2431,7 @@ declare class ApiBookSource implements MidnightBookSource {
2105
2431
  getOfferMakers(marketId: string, side: 'bids' | 'asks', assets: bigint): Promise<Record<string, string[]>>;
2106
2432
  }
2107
2433
  /** Build the default (hosted-API) book source for a chain. */
2108
- declare function createMidnightBookSource(chainId: string, fetchImpl?: FetchLike$1): MidnightBookSource;
2434
+ declare function createMidnightBookSource(chainId: string, fetchImpl?: FetchLike$2): MidnightBookSource;
2109
2435
 
2110
2436
  /**
2111
2437
  * Top-of-book snapshot for a single Term repo, already reduced to best
@@ -2222,14 +2548,48 @@ interface TermBookSource {
2222
2548
  getTopAndBook?(config: TermMarketConfig, maxLevels?: number): Promise<{
2223
2549
  top: TermBookTop;
2224
2550
  book: TermBook;
2551
+ /** Live/upcoming auction round; null when none is listed. */
2552
+ auction: TermAuctionWindow | null;
2225
2553
  } | null>;
2226
2554
  }
2555
+ /**
2556
+ * The repo's CURRENT primary auction round, when one is listed.
2557
+ *
2558
+ * Term borrow origination is a periodic sealed-bid auction, not a continuous
2559
+ * book: outside the submission window there is nothing to bid on, so a repo
2560
+ * whose auction has cleared is lend-only (buy repo tokens on the secondary
2561
+ * book) until the next round is listed. Timestamps are raw so consumers can
2562
+ * derive a live countdown; `status` is a snapshot at fetch time.
2563
+ */
2564
+ interface TermAuctionWindow {
2565
+ /** Auction round id (the TermAuction entity id). */
2566
+ id: string;
2567
+ /** Submissions open (unix seconds). */
2568
+ startTime: number;
2569
+ /** Submissions CLOSE and the sealed prices start revealing (unix seconds). */
2570
+ revealTime: number;
2571
+ /** Auction clears (unix seconds). Equal to `revealTime` on current deployments. */
2572
+ endTime: number;
2573
+ /** Minimum bid (borrow) size, loan-token base units (raw string; '0' when unset). */
2574
+ minBidAmount: string;
2575
+ /** Minimum offer (lend) size, loan-token base units (raw string; '0' when unset). */
2576
+ minOfferAmount: string;
2577
+ /** Highest accepted bid rate, WAD (raw string; '0' when unset). */
2578
+ maxBidPriceWad: string;
2579
+ /** Highest accepted offer rate, WAD (raw string; '0' when unset). */
2580
+ maxOfferPriceWad: string;
2581
+ }
2227
2582
  /** A Term repo paired with its current top-of-book (null when the fetch failed). */
2228
2583
  interface TermMarketRaw {
2229
2584
  config: TermMarketConfig;
2230
2585
  top: TermBookTop | null;
2231
2586
  /** Bounded book slice (top-N levels/side); null/absent when unavailable. */
2232
2587
  book?: TermBook | null;
2588
+ /**
2589
+ * The live/upcoming auction round, or null when no round is currently listed
2590
+ * (the common case between auctions — the repo is then lend-only).
2591
+ */
2592
+ auction?: TermAuctionWindow | null;
2233
2593
  }
2234
2594
 
2235
2595
  /**
@@ -2261,7 +2621,7 @@ declare function convertTermMarketsToResponse(raw: TermMarketRaw[], chainId: str
2261
2621
  [m: string]: MorphoGeneralPublicResponse;
2262
2622
  };
2263
2623
 
2264
- type FetchLike = typeof fetch;
2624
+ type FetchLike$1 = typeof fetch;
2265
2625
  /**
2266
2626
  * GraphQL subgraph source. `getBookTop` derives the fixed APR from the repo's
2267
2627
  * latest completed auction clearing price and open-order depth; `getListings`
@@ -2270,20 +2630,29 @@ type FetchLike = typeof fetch;
2270
2630
  declare class TermSubgraphSource implements TermBookSource {
2271
2631
  private readonly url;
2272
2632
  private readonly fetchImpl;
2273
- constructor(url: string, fetchImpl?: FetchLike);
2633
+ constructor(url: string, fetchImpl?: FetchLike$1);
2274
2634
  private gql;
2275
2635
  getBookTop(config: TermMarketConfig): Promise<TermBookTop | null>;
2276
2636
  /**
2277
2637
  * ONE query → the aggregate top (best APR + FULL depth) PLUS a bounded book
2278
- * slice (top `maxLevels` open orders per side). `asks` = orders selling repo
2279
- * tokens (the secondary LEND book); `bids` = the rest (borrow side, usually
2280
- * empty Term borrow is sealed-bid auction, not a continuous book). Term
2281
- * secondary orders carry no per-order rate, so every level shares the market's
2282
- * clearing APR; the levels expose per-order SIZE for filtering.
2638
+ * slice (top `maxLevels` open orders per side) PLUS the repo's current
2639
+ * auction round. `asks` = orders selling repo tokens (the secondary LEND
2640
+ * book); `bids` = the rest (borrow side, usually empty Term borrow is
2641
+ * sealed-bid auction, not a continuous book). Term secondary orders carry no
2642
+ * per-order rate, so every level shares the market's clearing APR; the levels
2643
+ * expose per-order SIZE for filtering.
2644
+ *
2645
+ * Two auction reads, deliberately distinct:
2646
+ * - `cleared` — the latest COMPLETE round, whose clearing price IS the
2647
+ * market's fixed APR (and stays the reference rate between auctions).
2648
+ * - `pending` — rounds not yet complete/cancelled. Only one of these is a
2649
+ * real, actionable round; the rest are abandoned listings the subgraph
2650
+ * never marked complete, filtered out below.
2283
2651
  */
2284
2652
  getTopAndBook(config: TermMarketConfig, maxLevels?: number): Promise<{
2285
2653
  top: TermBookTop;
2286
2654
  book: TermBook;
2655
+ auction: TermAuctionWindow | null;
2287
2656
  } | null>;
2288
2657
  getListings(config: TermMarketConfig): Promise<TermListing[] | null>;
2289
2658
  /**
@@ -2296,7 +2665,7 @@ declare class TermSubgraphSource implements TermBookSource {
2296
2665
  getAuctionOrders(config: TermMarketConfig, account: string): Promise<TermAuctionOrders | null>;
2297
2666
  }
2298
2667
  /** Default Term public-data source for a chain (subgraph via resolved URL). */
2299
- declare function createTermBookSource(chainId: string, fetchImpl?: FetchLike): TermBookSource;
2668
+ declare function createTermBookSource(chainId: string, fetchImpl?: FetchLike$1): TermBookSource;
2300
2669
 
2301
2670
  /**
2302
2671
  * Decoded shapes of the Exactly `Previewer.exactly(account)` aggregate view.
@@ -2390,26 +2759,46 @@ interface ExactlyMarketsRaw {
2390
2759
  */
2391
2760
  declare function fetchExactlyMarkets(chainId: string): Promise<ExactlyMarketsRaw>;
2392
2761
 
2393
- /** Synthesized per-market lender key, e.g. `EXACTLY_<MARKET_ADDRESS_HEX_UPPER>`. */
2394
- declare function exactlyLenderKey(market: string): string;
2395
- /** Recover the Market address from an `EXACTLY_<HEX>` lender key (or undefined). */
2396
- declare function exactlyMarketFromLenderKey(lender: string): string | undefined;
2397
2762
  /**
2398
- * Map the on-chain Previewer batch into the shared `MorphoGeneralPublicResponse`
2399
- * shape (identical to Midnight/Term), keyed by `EXACTLY_<MARKET_ADDRESS>` — one
2400
- * key per asset Market (NOT per maturity; the maturity menu is the market's
2401
- * `params.market.terms[]`, `termId` = maturity timestamp).
2763
+ * The ONE Exactly lender key per chain.
2402
2764
  *
2403
- * Per market:
2404
- * - the LOAN entry carries the FLOATING rates (`depositRate` /
2405
- * `variableBorrowRate`) plus the best live fixed borrow APR on
2406
- * `stableBorrowRate`; its own asset is also collateral (self-pair
2407
- * adjustFactor²) since Exactly is cross-margin;
2408
- * - one COLLATERAL entry per SIBLING market (cross-margin: any entered market
2409
- * collateralizes any borrow) with pairwise LTV = adjF_coll × adjF_borrow
2410
- * (multiplicative, Dolomite-style) and the Auditor liquidation bonus;
2411
- * - `params.market.fixedTerm` = `{ model: 'exactly', earlyRepay: 'discount',
2412
- * fees.latePenaltyApr }` see the wrapper README for the repay mechanics.
2765
+ * Exactly is a CROSS-MARGIN protocol: a single per-chain `Auditor` (a
2766
+ * Compound-V2-shaped comptroller, NOT a Euler controller) holds one
2767
+ * `enterMarket` bitmap per account, every entered deposit backs debt in ANY
2768
+ * market simultaneously, and health is one global check. The per-asset `Market`
2769
+ * contracts exist because each is the ERC-4626 share token for its asset and
2770
+ * carries that asset's rates / fixed pools exactly like cUSDC and cETH under
2771
+ * one Comptroller. They are NOT isolated markets.
2772
+ *
2773
+ * So Exactly is modeled like Compound V2: ONE lender key, one entry per asset.
2774
+ * (It was previously split into synthesized `EXACTLY_<MARKET_ADDR>` keys that
2775
+ * only ever existed because `terms[]` / `fixedTerm` lived on `params.market`,
2776
+ * which assumes one borrowable asset per key. Both now also exist per asset on
2777
+ * `data[*]`, so the split is gone along with the cross-margin collateral
2778
+ * mirroring, the double-count hazard and the optimistic per-key health it
2779
+ * forced. Resolve a Market contract from the ASSET via
2780
+ * `exactlyMarketByAsset(chainId, asset)` — or from the entry's `poolId`.)
2781
+ */
2782
+ declare const EXACTLY_LENDER_KEY = "EXACTLY";
2783
+ /**
2784
+ * Map the on-chain Previewer batch into the shared `MorphoGeneralPublicResponse`
2785
+ * shape, under the SINGLE cross-margin {@link EXACTLY_LENDER_KEY} — one entry
2786
+ * per ASSET (the Compound V2 shape), never one key per Market.
2787
+ *
2788
+ * Per asset entry:
2789
+ * - FLOATING rates (`depositRate` / `variableBorrowRate`) plus the best live
2790
+ * fixed borrow APR on `stableBorrowRate`;
2791
+ * - its OWN `terms[]` maturity menu (`termId` = the pool's maturity) and its
2792
+ * OWN `fixedTerm` descriptor pointing at that asset's Market — per-asset
2793
+ * because each asset has its own fixed pools;
2794
+ * - risk as `collateralFactor = adjustFactor` + `borrowFactor = 1/adjustFactor`,
2795
+ * which is the Auditor's own formula (their product = the pairwise LTV);
2796
+ * - `poolId` / `exactly.market` = the Market contract (the write target).
2797
+ *
2798
+ * Every asset is simultaneously borrowable AND collateral for every other, so
2799
+ * there are no sibling-collateral rows. `params.market` carries only the
2800
+ * pool-wide descriptor (Auditor as `id`, a market-level `fixedTerm` without a
2801
+ * provider address). See the wrapper README for the repay mechanics.
2413
2802
  */
2414
2803
  declare function convertExactlyMarketsToResponse(raw: ExactlyMarketsRaw, chainId: string, prices?: {
2415
2804
  [asset: string]: number;
@@ -2435,7 +2824,15 @@ declare function exactlyPenaltyRateToAprPercent(penaltyRatePerSecond: bigint | u
2435
2824
  */
2436
2825
  declare function exactlyPairLtv(collateralAdjustFactor: bigint | undefined, borrowAdjustFactor: bigint | undefined): number;
2437
2826
 
2438
- /** Per-position fixed-term detail attached to the position row (strings raw). */
2827
+ /**
2828
+ * Per-position fixed-term detail attached to the position row (raw strings).
2829
+ *
2830
+ * Carries the FULL exit economics so a repay/withdraw UI needs no second read:
2831
+ * `faceValue` is what is owed/paid at maturity, `previewValue` is what the exit
2832
+ * actually costs/pays RIGHT NOW, and exactly one of `earlyRepayDiscount` /
2833
+ * `earlyExitCost` / `latePenalty` explains the gap. See the "repay terms"
2834
+ * section of the Exactly README for the source-verified formulas.
2835
+ */
2439
2836
  interface ExactlyUserFixedPosition {
2440
2837
  /** unix maturity */
2441
2838
  maturity: number;
@@ -2445,12 +2842,32 @@ interface ExactlyUserFixedPosition {
2445
2842
  principal: string;
2446
2843
  /** face fee locked at trade time (raw asset units) */
2447
2844
  fee: string;
2845
+ /** face value at maturity = principal + fee. Static — Exactly fixed debt does
2846
+ * NOT accrue an index; it only grows via the late penalty below. */
2847
+ faceValue: string;
2448
2848
  /** live exit value now: withdraw-now / repay-now incl. discount or overdue
2449
2849
  * penalty (raw asset units) — from the Previewer */
2450
2850
  previewValue: string;
2451
2851
  /** true once maturity passed and the position is still open (borrows accrue
2452
2852
  * the per-second late penalty until repaid) */
2453
2853
  overdue: boolean;
2854
+ /** seconds past maturity (0 until overdue) */
2855
+ secondsLate: number;
2856
+ /** BORROW before maturity: face − repay-now, the REBATE for repaying early
2857
+ * (Exactly never charges an early-repay fee). Absent otherwise. */
2858
+ earlyRepayDiscount?: string;
2859
+ /** DEPOSIT before maturity: face − payout-now, the HAIRCUT for exiting a
2860
+ * fixed deposit early (sold back at the current curve rate). Absent
2861
+ * otherwise. */
2862
+ earlyExitCost?: string;
2863
+ /** BORROW past maturity: repay-now − face, penalty accrued SO FAR. Absent
2864
+ * otherwise. */
2865
+ latePenalty?: string;
2866
+ /** BORROW: penalty this position accrues per further day overdue (raw units,
2867
+ * linear on face — not compounding). Present for borrows only. */
2868
+ latePenaltyPerDay: string;
2869
+ /** market's linear late-penalty rate as an annualized percent (e.g. 164.24) */
2870
+ latePenaltyApr: number;
2454
2871
  }
2455
2872
 
2456
2873
  /**
@@ -2695,6 +3112,682 @@ interface RiverPositionInfo {
2695
3112
  collateralSurplus: string;
2696
3113
  }
2697
3114
 
3115
+ /**
3116
+ * Per-market snapshot of a FiRM market. Numbers are HUMAN units (the
3117
+ * Inverse API serves human numbers; the on-chain fallback normalizes to
3118
+ * match). `null` marks a value the active source could not provide —
3119
+ * the converter degrades gracefully per field.
3120
+ */
3121
+ interface InverseMarketRaw {
3122
+ market: InverseMarketConfig;
3123
+ /** Market.totalDebt — DOLA units (human). */
3124
+ totalDebt: number | null;
3125
+ /** DOLA sitting in the Market = instant borrowable ceiling (human). */
3126
+ dolaLiquidity: number | null;
3127
+ /** min(dolaLiquidity, dailyLimit − dailyBorrows) — API only. */
3128
+ leftToBorrow: number | null;
3129
+ /** Collateral price in USD (pessimistic-oracle based). */
3130
+ price: number | null;
3131
+ /** Live borrowPaused (falls back to the metadata snapshot). */
3132
+ borrowPaused: boolean | null;
3133
+ /** Borrows already taken today against `dailyLimit` — API only. */
3134
+ dailyBorrows: number | null;
3135
+ /**
3136
+ * `Market.replenishmentIncentiveBps` (1000 = 10%) — the replenisher
3137
+ * bot's cut of a force-replenish, per market. It is carved OUT of the
3138
+ * `replenishmentPriceBps` cost and paid in DOLA from the market's own
3139
+ * liquidity; the borrower's debt grows by the FULL cost either way, so
3140
+ * this is a protocol/bot split, not an extra borrower charge.
3141
+ */
3142
+ replenishmentIncentiveBps: number | null;
3143
+ }
3144
+ /** Raw public-data batch for the FiRM deployment (one chain). */
3145
+ interface InverseMarketsRaw {
3146
+ /** The bare lender key, `INVERSE`. */
3147
+ lender: string;
3148
+ config: InverseConfigChain | undefined;
3149
+ chainData: InverseChainData | undefined;
3150
+ /**
3151
+ * DBR price in DOLA — THE fixed borrow APR as a decimal (0.041 =
3152
+ * 4.1%). API-first, metadata snapshot as fallback, `null` if neither
3153
+ * resolves.
3154
+ */
3155
+ dbrPriceDola: number | null;
3156
+ /** Force-replenish penalty APR in bps (54.75% = 5475) — static read. */
3157
+ replenishmentPriceBps: number | null;
3158
+ markets: InverseMarketRaw[];
3159
+ /** Which source filled the market rows. */
3160
+ source: 'api' | 'chain' | 'none';
3161
+ }
3162
+
3163
+ declare function fetchInverseMarkets(lender: string, chainId: string): Promise<InverseMarketsRaw>;
3164
+
3165
+ /**
3166
+ * Synthesized per-market lender key, e.g.
3167
+ * `INVERSE_63DF5E23DB45A2066508318F172BA45B9CD37035` (= the WETH
3168
+ * market). Address-suffixed (Teller/Exactly convention) — FiRM is
3169
+ * Ethereum-only so the chain id is not part of the key.
3170
+ */
3171
+ declare function inverseLenderKey(lender: string, market: string): string;
3172
+ /** Recover `{ lender, market }` from a per-market key (or undefined). */
3173
+ declare function inverseKeyParts(key: string): {
3174
+ lender: string;
3175
+ market: string;
3176
+ } | undefined;
3177
+ /**
3178
+ * Map the FiRM batch into the shared `MorphoGeneralPublicResponse`
3179
+ * shape, keyed by `INVERSE_<MARKET_ADDR>` — one key per Market
3180
+ * (collateral).
3181
+ *
3182
+ * Per market:
3183
+ * - COLLATERAL entry: deposit-only; LTV = `collateralFactorBps`;
3184
+ * liquidation penalty = `liquidationIncentiveBps`; the close factor
3185
+ * is `liquidationFactorBps` (a liquidation may only close that
3186
+ * share of the position).
3187
+ * - LOAN entry (DOLA): `totalDebt` = market debt; the borrow rate is
3188
+ * the DBR price (FIXED APR — interest is prepaid in DBR, not
3189
+ * accrued on principal, `rateModel: 'dbr'`); `borrowLiquidity` =
3190
+ * `leftToBorrow` (API: min(dailyLimit headroom, DOLA in market)) or
3191
+ * `dolaLiquidity` in the on-chain fallback. There is NO supply side
3192
+ * — `totalDeposits` on the loan row is always 0 (DOLA is Fed-minted
3193
+ * into markets, not user-deposited).
3194
+ *
3195
+ * The full FiRM descriptor (minDebt, dailyLimit, escrow implementation,
3196
+ * DBR addresses, replenishment penalty) rides in
3197
+ * `params.market.inverse` for the calldata builders + worker-api
3198
+ * resolvers.
3199
+ */
3200
+ declare function convertInverseMarketsToResponse(raw: InverseMarketsRaw, chainId: string, prices?: {
3201
+ [asset: string]: number;
3202
+ }, _additionalYields?: AdditionalYields, tokens?: GenericTokenList): {
3203
+ [m: string]: MorphoGeneralPublicResponse;
3204
+ };
3205
+
3206
+ /**
3207
+ * The immutable half of a Resupply pair. Cached across refreshes because none
3208
+ * of it can change: `collateral` and `underlying` are set in the pair's
3209
+ * constructor and there is no setter (`setConvexPool` only moves where the
3210
+ * SHARES are staked, never what the collateral token is).
3211
+ */
3212
+ interface ResupplyPairIdentity {
3213
+ pair: string;
3214
+ /** e.g. `Resupply Pair (CurveLend: crvUSD/sfrxUSD) - 1`. */
3215
+ name: string;
3216
+ /** The ERC-4626 share of the WRAPPED market — the accounting unit. */
3217
+ collateral: string;
3218
+ /** What the user actually deposits and withdraws: crvUSD or frxUSD. */
3219
+ underlying: string;
3220
+ collateralDecimals: number;
3221
+ underlyingDecimals: number;
3222
+ }
3223
+ /**
3224
+ * One Resupply pair after the state batch. Raw bigints; `null` = failed
3225
+ * allowFailure read.
3226
+ */
3227
+ interface ResupplyPairRaw {
3228
+ identity: ResupplyPairIdentity;
3229
+ /** 1e5-scaled (95000 = 95%). */
3230
+ maxLTV: bigint | null;
3231
+ /** Debt ceiling. ZERO ⇒ paused/retired — this is the liveness signal. */
3232
+ borrowLimit: bigint | null;
3233
+ /** 1e5-scaled penalty on top of the debt at liquidation. */
3234
+ liquidationFee: bigint | null;
3235
+ /** 1e5-scaled fee added to minted debt (0 on every live pair). */
3236
+ mintFee: bigint | null;
3237
+ /** Hard per-position floor (1,000 reUSD). */
3238
+ minimumBorrowAmount: bigint | null;
3239
+ /** Face reUSD debt, interest previewed. */
3240
+ totalBorrowAmount: bigint | null;
3241
+ totalBorrowShares: bigint | null;
3242
+ /** Collateral SHARES held by the pair. */
3243
+ totalCollateral: bigint | null;
3244
+ /** 1e18-scaled per SECOND, from `currentRateInfo` (last checkpoint). */
3245
+ ratePerSec: bigint | null;
3246
+ /** Same, but recomputed live by the Utilities lens — preferred. */
3247
+ liveRatePerSec: bigint | null;
3248
+ /** The WRAPPED market's supply rate per second, 1e18-scaled. */
3249
+ underlyingSupplyRatePerSec: bigint | null;
3250
+ /** `convertToAssets(1e18)` on the collateral vault — UNDERLYING per share.
3251
+ * ~1e15 for Curve Lend vaults, ~1e18 for Fraxlend pairs. */
3252
+ collateralPrice: bigint | null;
3253
+ /** Cached `1e36 / collateralPrice` from the pair (stale between writes). */
3254
+ exchangeRate: bigint | null;
3255
+ }
3256
+ interface ResupplyMarketsRaw {
3257
+ lender: string;
3258
+ config?: ResupplyConfigChain;
3259
+ pairs: ResupplyPairRaw[];
3260
+ }
3261
+
3262
+ /**
3263
+ * Fetch every Resupply market on a chain — FULLY ON-CHAIN, no API and no
3264
+ * published market roster.
3265
+ *
3266
+ * Discovery is `ResupplyRegistry.getAllPairAddresses()`. That is deliberate:
3267
+ * governance adds pairs (7 appeared between the docs' published list and
3268
+ * 2026-08) and retires them by zeroing `borrowLimit`, so a static file would
3269
+ * both miss new markets and advertise frozen ones as borrowable. The registry
3270
+ * is permissionless to read and is the same source the protocol's own
3271
+ * periphery uses.
3272
+ *
3273
+ * Three rounds cold, two warm:
3274
+ * 1. registry → pair addresses (intersected with `pairAllowlist` if set);
3275
+ * 2. identity (`name`/`collateral`/`underlying` + both tokens' decimals) for
3276
+ * pairs not already cached — immutable, so this is once per pair ever;
3277
+ * 3. state: terms, accounting, rates and the collateral vault's share price.
3278
+ *
3279
+ * The rate is read from the `Utilities` lens rather than `currentRateInfo`,
3280
+ * which is only a checkpoint from the last write — on a quiet pair that can be
3281
+ * hours stale, and the off-peg amplifier moves with the reUSD price. The
3282
+ * checkpoint is kept as a fallback.
3283
+ */
3284
+ declare function fetchResupplyMarkets(lender: string, chainId: string): Promise<ResupplyMarketsRaw>;
3285
+
3286
+ /**
3287
+ * The external lending market a Resupply pair wraps.
3288
+ *
3289
+ * Every Resupply pair's collateral IS another lender's supply position, so a
3290
+ * position here carries that market's risk on top of Resupply's own. This
3291
+ * resolves the link where we can: the collateral vault is matched against the
3292
+ * LlamaLend roster by ADDRESS (LlamaLend indexes markets by Controller, so the
3293
+ * lookup goes through `llamaLendMarketByVault`).
3294
+ *
3295
+ * Verified 2026-08-04: 16 of the 21 registered pairs match a LlamaLend market
3296
+ * exactly, and the generation agrees independently — Resupply's own
3297
+ * `CurveLend:` names map to LlamaLend `version: 1` and `CurveLendV2:` to
3298
+ * `version: 2`. The other 5 are Fraxlend pairs, which we do not integrate as a
3299
+ * lender, so they resolve to `provider: 'fraxlend'` with no market key.
3300
+ */
3301
+ interface ResupplyWrappedMarket {
3302
+ /** Which protocol the collateral position lives in. */
3303
+ provider: 'llamalend' | 'fraxlend' | 'unknown';
3304
+ /** The ERC-4626 the pair custodies — always known (it IS the collateral). */
3305
+ vault: string;
3306
+ /** `LLAMALEND_<CONTROLLER_ADDR>` when we integrate that market, else absent.
3307
+ * This is the key to look the wrapped market up in our own data. */
3308
+ lender?: string;
3309
+ /** LlamaLend Controller (the borrow surface of the wrapped market). */
3310
+ controller?: string;
3311
+ /** LlamaLend LLAMMA. Carried because a leverage route through Resupply must
3312
+ * never touch it — the wrapped Controller asserts its band state. */
3313
+ amm?: string;
3314
+ /** 1 = `oneway`, 2 = `oneway-v2`. Matches Resupply's CurveLend/CurveLendV2. */
3315
+ version?: 1 | 2;
3316
+ /** What the wrapped market lends against, e.g. `sfrxUSD`. */
3317
+ collateralSymbol?: string;
3318
+ }
3319
+ /**
3320
+ * Synthesized per-pair lender key, e.g. `RESUPPLY_1_C5184CCC…`. The chain id
3321
+ * rides in the key (Fluid/River/Frankencoin convention) even though Resupply
3322
+ * is Ethereum-only today.
3323
+ */
3324
+ declare function resupplyLenderKey(lender: string, chainId: string | number, pair: string): string;
3325
+ /** Recover `{ lender, chainId, pair }` from a per-pair key. */
3326
+ declare function resupplyKeyParts(key: string): {
3327
+ lender: string;
3328
+ chainId: string;
3329
+ pair: string;
3330
+ } | undefined;
3331
+ /**
3332
+ * Map one Resupply deployment's on-chain batch into the shared
3333
+ * `MorphoGeneralPublicResponse` shape, keyed `RESUPPLY_<chainId>_<PAIR>`.
3334
+ *
3335
+ * Four modelling decisions worth knowing (all from RESUPPLY_PLAN.md):
3336
+ *
3337
+ * - **The collateral we publish is the UNDERLYING (crvUSD / frxUSD), not the
3338
+ * ERC-4626 share.** The share is an internal accounting unit that no token
3339
+ * list carries and no price feed covers, and both user-facing entry points
3340
+ * (`addCollateral` / `removeCollateral`) are denominated in the underlying.
3341
+ * Share amounts are converted with the vault's own
3342
+ * `convertToAssets(1e18)` — the exact number Resupply's oracle uses. NB
3343
+ * that price is ~**1e15** for Curve Lend vaults, so the share count is
3344
+ * ~1000x the underlying; a bare 1e18 divide is wrong by three orders of
3345
+ * magnitude.
3346
+ * - **The collateral carries the wrapped market's yield.** A Resupply deposit
3347
+ * is a Curve Lend / Fraxlend supply position, so `getUnderlyingSupplyRate`
3348
+ * is published as the collateral row's `intrinsicYield`. Without it the
3349
+ * position looks like it pays nothing, when in fact the whole product is
3350
+ * the spread between that and the ~half-of-it borrow rate.
3351
+ * - **`borrowLimit == 0` means PAUSED.** `pause()` zeroes it and there is no
3352
+ * `isPaused`; 9 of 21 pairs sat at zero at integration. Such a pair is
3353
+ * reported frozen and non-borrowable, but deposits/withdrawals stay open so
3354
+ * users can exit.
3355
+ * - **There is no supply side** — reUSD is minted — so `totalDeposits` on the
3356
+ * loan row is 0 and `depositRate` is 0. The earn leg is sreUSD, which
3357
+ * belongs to the savings provider.
3358
+ */
3359
+ declare function convertResupplyMarketsToResponse(raw: ResupplyMarketsRaw, chainId: string, prices?: {
3360
+ [asset: string]: number;
3361
+ }, _additionalYields?: AdditionalYields, tokens?: GenericTokenList): {
3362
+ [m: string]: MorphoGeneralPublicResponse;
3363
+ };
3364
+
3365
+ /** Per-position Resupply detail attached to the debt row (raw strings). */
3366
+ interface ResupplyPositionInfo {
3367
+ /** Internal borrow shares. NOT an amount — see `debt` for reUSD units. */
3368
+ borrowShares: string;
3369
+ /** Collateral in VAULT SHARES (the pair's own accounting unit). */
3370
+ collateralShares: string;
3371
+ /** `convertToAssets(1e18)` on the collateral vault at read time — the
3372
+ * factor that turned those shares into the reported underlying. ~1e15 for
3373
+ * Curve Lend vaults. */
3374
+ collateralSharePrice: string;
3375
+ /** The pair contract — the market id and every write target. */
3376
+ pair: string;
3377
+ /** The collateral vault (Curve Lend / Fraxlend 4626 share token). */
3378
+ collateralVault: string;
3379
+ }
3380
+
3381
+ /** Test seam — drops the roster + discovery caches. */
3382
+ declare function __resetResupplyUserCaches(): void;
3383
+
3384
+ /** Off-chain `Market.predictEscrow(user)`. */
3385
+ declare function predictInverseEscrow(market: Address, escrowImplementation: Address, user: Address): Address;
3386
+
3387
+ /** Per-position FiRM detail attached to the debt row (raw strings). */
3388
+ interface InversePositionInfo {
3389
+ /** Pessimistic-oracle borrow ceiling for the CURRENT collateral (DOLA raw). */
3390
+ creditLimit: string;
3391
+ /** Max collateral withdrawable right now (collateral raw). */
3392
+ withdrawalLimit: string;
3393
+ /** DBR wallet balance (raw) — the prepaid-interest runway. */
3394
+ dbrBalance: string;
3395
+ /** DBR deficit (raw). > 0 ⇒ force-replenishable at 54.75% APR AND
3396
+ * withdrawals are FROZEN until the user buys DBR. */
3397
+ dbrDeficit: string;
3398
+ /** DBR signed balance (raw, may be negative). */
3399
+ dbrSignedBalance: string;
3400
+ /** `DBR.debts(user)` (raw DOLA) — debt across ALL FiRM markets, which is
3401
+ * what burns DBR at 1 per DOLA-year. Chain-wide, repeated on every row. */
3402
+ dbrTotalDebt: string;
3403
+ /** Seconds until `dbrBalance` is exhausted at that burn ('0' if no debt). */
3404
+ dbrRunwaySeconds: string;
3405
+ /** Unix seconds of the projected depletion — past that point anyone can
3406
+ * force-replenish the account, charging the replenished DBR to its DOLA
3407
+ * debt at `replenishmentPriceBps`, and withdrawals freeze. Absent when
3408
+ * there is no debt. */
3409
+ dbrDepletionTimestamp?: string;
3410
+ }
3411
+
3412
+ /**
3413
+ * Per-market snapshot of a LlamaLend market. Amounts are HUMAN units (the
3414
+ * Curve API serves human numbers; the on-chain fallback normalizes to match).
3415
+ * Rates are DECIMALS (0.0391 = 3.91% APR), nominal — never the compounded
3416
+ * `borrowApy` the API also carries.
3417
+ *
3418
+ * `null` marks a value the active source could not provide; the converter
3419
+ * degrades per field rather than dropping the market.
3420
+ */
3421
+ interface LlamaLendMarketRaw {
3422
+ market: LlamaLendMarketConfig;
3423
+ /** Controller.total_debt — borrowed-token units (human). */
3424
+ totalDebt: number | null;
3425
+ /** Vault.totalAssets — borrowed-token units (human). */
3426
+ totalSupplied: number | null;
3427
+ /**
3428
+ * Borrowable right now. v2: `Controller.available_balance()`. v1:
3429
+ * `borrowedToken.balanceOf(controller)`. This is also the withdrawal
3430
+ * ceiling for lenders — the vault cannot pay out what is lent.
3431
+ */
3432
+ availableToBorrow: number | null;
3433
+ /** Nominal borrow APR as a decimal. */
3434
+ borrowApr: number | null;
3435
+ /** Nominal lend APR as a decimal. */
3436
+ lendApr: number | null;
3437
+ /** Collateral price in borrowed-token terms, from the AMM's EMA oracle. */
3438
+ collateralPrice: number | null;
3439
+ /** USD price of the collateral, API only (the chain read has no USD leg). */
3440
+ collateralPriceUsd: number | null;
3441
+ /** USD price of the borrowed token, API only. */
3442
+ borrowedPriceUsd: number | null;
3443
+ /**
3444
+ * Effective collateral factor at the market's `defaultBands`, derived from
3445
+ * `max_borrowable(1 unit, N)` divided by the oracle price.
3446
+ *
3447
+ * There is NO market-constant LTV in LlamaLend — the number moves with the
3448
+ * band count. Measured on sreUSD/crvUSD: 0.991 at N=4 down to 0.886 at
3449
+ * N=50. Reporting the N=4 maximum would flatter every risk comparison
3450
+ * against Aave/Morpho, so the default N is what gets reported and the rest
3451
+ * of the curve travels alongside in `bandLtv`.
3452
+ */
3453
+ collateralFactor: number | null;
3454
+ /** `{ [N]: collateralFactor }` — the trade-off curve for the UI and sizer. */
3455
+ bandLtv: {
3456
+ [bands: string]: number;
3457
+ } | null;
3458
+ /**
3459
+ * v2 borrow cap in borrowed-token units (human). `0` DISABLES borrowing —
3460
+ * a fresh v2 market looks live but is not. `null` on v1 (uncapped).
3461
+ */
3462
+ borrowCap: number | null;
3463
+ /** Whether new borrows are possible at all right now. */
3464
+ borrowingEnabled: boolean;
3465
+ /** Vault.maxDeposit — `0` disables deposits (v2 `max_supply`). */
3466
+ maxDeposit: number | null;
3467
+ /** Number of open loans, for the liquidations surface. */
3468
+ nLoans: number | null;
3469
+ /**
3470
+ * Soft-liquidation state of the market as a whole: the AMM's active band.
3471
+ * Not a per-user value, but it tells the UI whether the market is currently
3472
+ * converting anyone's collateral.
3473
+ */
3474
+ activeBand: number | null;
3475
+ }
3476
+ /** Raw public-data batch for one LlamaLend chain (both generations together). */
3477
+ interface LlamaLendMarketsRaw {
3478
+ /** The bare lender key, `LLAMALEND`. */
3479
+ lender: string;
3480
+ config: LlamaLendConfigChain | undefined;
3481
+ chainData: LlamaLendChainData | undefined;
3482
+ markets: LlamaLendMarketRaw[];
3483
+ /** Which source filled the market rows. */
3484
+ source: 'api' | 'chain' | 'none';
3485
+ }
3486
+
3487
+ declare function fetchLlamaLendMarkets(lender: string, chainId: string): Promise<LlamaLendMarketsRaw>;
3488
+
3489
+ /**
3490
+ * Synthesized per-market lender key, e.g.
3491
+ * `LLAMALEND_4F79FE450A2BAF833E8F50340BD230F5A3ECAFE9` (= the sreUSD/crvUSD
3492
+ * market). Keyed by the CONTROLLER, which is what every write and every user
3493
+ * read targets — the vault is a lookup off it. Address-suffixed
3494
+ * (Teller/Exactly/Inverse convention); the chain id is not part of the key
3495
+ * because chain scoping happens at the marketUid level.
3496
+ *
3497
+ * Both generations share this key space on purpose: to a user they are one
3498
+ * protocol, and the Curve API returns them in one list. The `version` field on
3499
+ * the market row is what encoders branch on.
3500
+ */
3501
+ declare function llamaLendLenderKey(lender: string, controller: string): string;
3502
+ /** Recover `{ lender, controller }` from a per-market key (or undefined). */
3503
+ declare function llamaLendKeyParts(key: string): {
3504
+ lender: string;
3505
+ controller: string;
3506
+ } | undefined;
3507
+ /**
3508
+ * Map the LlamaLend batch into the shared `MorphoGeneralPublicResponse` shape,
3509
+ * keyed by `LLAMALEND_<CONTROLLER_ADDR>` — one key per market.
3510
+ *
3511
+ * Per market, two entries in the isolated-pair layout:
3512
+ *
3513
+ * - COLLATERAL entry — deposit-only. `collateralFactor` is the LTV AT THE
3514
+ * MARKET'S DEFAULT BAND COUNT, because LlamaLend has no market-constant
3515
+ * LTV: it is a function of `N` and moves 0.886..0.991 on a single market.
3516
+ * The whole curve rides along in `params.market.llamalend.bandLtv` so the
3517
+ * UI can show the trade-off and the leverage sizer can use the real number
3518
+ * for the `N` the user actually picks.
3519
+ * - LOAN entry — the borrowed token. Supply side is the ERC-4626 vault, so
3520
+ * unlike Inverse this one HAS `totalDeposits`.
3521
+ *
3522
+ * SOFT LIQUIDATION is the thing this shape cannot express natively, so it is
3523
+ * carried explicitly in the descriptor. `liquidationPenalty` here is the HARD
3524
+ * liquidation bonus only — it applies below the entire band range. Inside the
3525
+ * range a position is converted gradually through the market's own AMM with no
3526
+ * penalty at all, and a consumer that renders `liquidationPenalty` as "what
3527
+ * you lose when the price hits X" is describing the wrong event.
3528
+ */
3529
+ declare function convertLlamaLendMarketsToResponse(raw: LlamaLendMarketsRaw, chainId: string, prices?: {
3530
+ [asset: string]: number;
3531
+ }, additionalYields?: AdditionalYields, tokens?: GenericTokenList): {
3532
+ [m: string]: MorphoGeneralPublicResponse;
3533
+ };
3534
+
3535
+ /**
3536
+ * Per-position LlamaLend detail attached to the debt row (raw strings unless
3537
+ * noted).
3538
+ *
3539
+ * The two fields a consumer must not ignore:
3540
+ *
3541
+ * - `softLiquidating` / `priceUpper` / `priceLower` — a LlamaLend position
3542
+ * does not have a liquidation price. It has a band RANGE, and it starts
3543
+ * converting collateral into the borrowed token as soon as the oracle
3544
+ * enters that range. Rendering a single number here misrepresents the
3545
+ * protocol.
3546
+ * - `bandCollateralInBorrowed` — the borrowed-token leg the LLAMMA has
3547
+ * already produced from the user's collateral. It sits inside the position,
3548
+ * offsets debt, and is NOT a wallet balance.
3549
+ */
3550
+ interface LlamaLendPositionInfo {
3551
+ /** Signed health, WAD. `< 0` ⇒ hard-liquidatable. */
3552
+ health: string;
3553
+ /** Upper bound of the soft-liquidation band range, WAD. */
3554
+ priceUpper: string;
3555
+ /** Lower bound of the soft-liquidation band range, WAD. */
3556
+ priceLower: string;
3557
+ /** Band indices `[n1, n2]` the collateral currently occupies. */
3558
+ bands: [number, number] | undefined;
3559
+ /** Band count `N` chosen at loan creation — IMMUTABLE for the loan's life. */
3560
+ bandCount: number;
3561
+ /**
3562
+ * Borrowed-token amount held inside the user's bands (raw). Non-zero means
3563
+ * the position IS or HAS BEEN in soft liquidation.
3564
+ */
3565
+ bandCollateralInBorrowed: string;
3566
+ /** True when the LLAMMA currently holds a borrowed-token leg for this user. */
3567
+ softLiquidating: boolean;
3568
+ /**
3569
+ * Whether the probed spender holds the Controller's boolean grant for this
3570
+ * market. FAILS CLOSED — older-blueprint controllers have no `approval`
3571
+ * method, the call fails, and this reads `false`, which is correct.
3572
+ */
3573
+ delegated: boolean;
3574
+ /** Whether the market's Controller supports delegation at all. */
3575
+ supportsDelegation: boolean;
3576
+ /** Market generation — the leverage/encoder ABIs differ. */
3577
+ version: 1 | 2;
3578
+ }
3579
+
3580
+ /**
3581
+ * One USDD market (= one collateral ilk) after the on-chain batch.
3582
+ * Raw bigints; `null` = failed allowFailure read. Maker fixed-point:
3583
+ * wad 1e18 / ray 1e27 / rad 1e45.
3584
+ */
3585
+ interface UsddMarketRaw {
3586
+ market: UsddMarketConfig;
3587
+ /** Vat.ilks — total normalised debt (wad). */
3588
+ Art: bigint | null;
3589
+ /** Vat.ilks — debt accumulator (ray); debt = Art × rate (rad). */
3590
+ rate: bigint | null;
3591
+ /** Vat.ilks — liquidation-adjusted price (ray): price / (par × mat). */
3592
+ spot: bigint | null;
3593
+ /** Vat.ilks — ilk debt ceiling (rad). */
3594
+ line: bigint | null;
3595
+ /** Vat.ilks — per-urn debt floor (rad). */
3596
+ dust: bigint | null;
3597
+ /** Jug.ilks — per-second stability fee (ray). */
3598
+ duty: bigint | null;
3599
+ /** Spot.ilks — liquidation ratio (ray). */
3600
+ mat: bigint | null;
3601
+ /** gem.balanceOf(gemJoin) — total collateral custodied by the adapter
3602
+ * (locked ink + unswept gem), gem-native decimals. */
3603
+ joinBalance: bigint | null;
3604
+ }
3605
+ interface UsddMarketsRaw {
3606
+ lender: string;
3607
+ config?: UsddConfigChain;
3608
+ chainData?: UsddChainData;
3609
+ markets: UsddMarketRaw[];
3610
+ }
3611
+
3612
+ /** Ilk string → bytes32 (`'WBTC-A'` → right-padded hex). */
3613
+ declare const usddIlkBytes32: (ilk: string) => `0x${string}`;
3614
+ /**
3615
+ * Fetch all market data of ONE USDD (Maker-fork) deployment — FULLY ON-CHAIN
3616
+ * via one retrying multicall. The ilk roster comes from lender-metadata
3617
+ * (`usddConfig`/`usddMarkets`, discovered + verified by its `update:usdd`
3618
+ * generator); this fetch reads the LIVE Vat/Jug/Spot params per ilk plus the
3619
+ * gem-join balance (total custodied collateral — the Vat keeps no per-ilk
3620
+ * ink total).
3621
+ *
3622
+ * The roster is EMPTY on both EVM chains today (`cdpi() = 0`, no ilk filed —
3623
+ * see USDD_PLAN.md), so this returns zero markets without issuing a
3624
+ * multicall. The code path stays live so the day metadata fills, data flows
3625
+ * with no code change.
3626
+ */
3627
+ declare function fetchUsddMarkets(lender: string, chainId: string): Promise<UsddMarketsRaw>;
3628
+
3629
+ /**
3630
+ * Synthesized per-ilk lender key, e.g. `USDD_1_WBTC-A`. The CHAIN ID is part
3631
+ * of the key (Fluid/River convention) because Ethereum and BNB run
3632
+ * INDEPENDENT Maker stacks that could file the same ilk string.
3633
+ */
3634
+ declare function usddLenderKey(lender: string, chainId: string | number, ilk: string): string;
3635
+ /**
3636
+ * Recover `{ lender, chainId, ilk }` from a per-market key (or undefined).
3637
+ * Ilk strings are Maker `<GEM>-<CLASS>` tokens (`WBTC-A`, `PSM-USDT-A`) —
3638
+ * uppercase alphanumerics + dashes; the leading `\d+_` disambiguates from
3639
+ * the bare `USDD` key.
3640
+ */
3641
+ declare function usddKeyParts(key: string): {
3642
+ lender: string;
3643
+ chainId: string;
3644
+ ilk: string;
3645
+ } | undefined;
3646
+ /**
3647
+ * Map one USDD deployment's on-chain batch into the shared
3648
+ * `MorphoGeneralPublicResponse` shape, keyed `USDD_<chainId>_<ILK>` — one key
3649
+ * per collateral ilk.
3650
+ *
3651
+ * Per market:
3652
+ * - COLLATERAL entry: totals = the gem-join balance (the Vat keeps no
3653
+ * per-ilk ink total; the adapter custodies locked + unswept gems);
3654
+ * LTV = 1/mat; liquidation penalty = chop − 1 (Dog.chop, wad).
3655
+ * - LOAN entry (USDD): `totalDebt` = Art × rate (rad → human);
3656
+ * `variableBorrowRate` = the stability fee as a nominal APR percent —
3657
+ * `(duty − RAY)/RAY × YEAR_SECONDS × 100`, the same annualisation as the
3658
+ * Pot's dsr in the savings fetcher (never `^ seconds − 1`, which is the
3659
+ * APY); `borrowLiquidity` = ceiling headroom `(line − Art × rate)/1e45`.
3660
+ * There is NO protocol supply side (USDD is Vat-minted) — the earn side
3661
+ * is sUSDD, carried by the savings provider, so `totalDeposits` on the
3662
+ * loan row is 0 and `depositRate` 0 here.
3663
+ * - Collateral price: Vat.spot × mat (both ray) recovers the par-adjusted
3664
+ * OSM price without reading the pip (whitelisted `peek` would revert);
3665
+ * shared price map as fallback.
3666
+ */
3667
+ declare function convertUsddMarketsToResponse(raw: UsddMarketsRaw, chainId: string, prices?: {
3668
+ [asset: string]: number;
3669
+ }, _additionalYields?: AdditionalYields, tokens?: GenericTokenList): {
3670
+ [m: string]: MorphoGeneralPublicResponse;
3671
+ };
3672
+
3673
+ /** Per-CDP position detail attached to the debt row (raw strings). */
3674
+ interface UsddPositionInfo {
3675
+ /** DssCdpManager id — the sub-account id and every write op's target. */
3676
+ cdpId: string;
3677
+ /** Urn handle in the Vat. */
3678
+ urn: string;
3679
+ ilk: string;
3680
+ }
3681
+
3682
+ /**
3683
+ * One Frankencoin market (= one ORIGINAL position) after the on-chain batch.
3684
+ * Raw bigints; `null` = failed allowFailure read.
3685
+ */
3686
+ interface FrankencoinMarketRaw {
3687
+ market: FrankencoinMarketConfig;
3688
+ /** Owner-declared liquidation price, 36-dec scaled vs collateral decimals. */
3689
+ price: bigint | null;
3690
+ /** FACE debt on the original itself (clones carry their own). */
3691
+ minted: bigint | null;
3692
+ /** ZCHF the original + its clones may still draw — market borrow capacity. */
3693
+ availableForClones: bigint | null;
3694
+ /** Collateral custodied by the ORIGINAL position contract. */
3695
+ collateralBalance: bigint | null;
3696
+ /** Hub lead rate + risk premium, ppm. */
3697
+ annualInterestPPM: bigint | null;
3698
+ /** Upfront fee for minting now (pro-rata to expiry), ppm. */
3699
+ currentFeePPM: bigint | null;
3700
+ /** Withheld into the equity reserve at mint, ppm. */
3701
+ reserveContribution: bigint | null;
3702
+ /** Non-zero while a Dutch-auction challenge is running. */
3703
+ challengedAmount: bigint | null;
3704
+ expiration: bigint | null;
3705
+ isClosed: boolean | null;
3706
+ }
3707
+ interface FrankencoinMarketsRaw {
3708
+ lender: string;
3709
+ config?: FrankencoinConfigChain;
3710
+ chainData?: FrankencoinChainData;
3711
+ markets: FrankencoinMarketRaw[];
3712
+ }
3713
+
3714
+ /**
3715
+ * Fetch all market data of ONE Frankencoin deployment — FULLY ON-CHAIN via
3716
+ * one retrying multicall over the curated ORIGINAL-position roster from
3717
+ * lender-metadata (`frankencoinConfig` / `frankencoinMarkets`, generated by
3718
+ * its `update:frankencoin` job, which filters to V2 + open + a priceable
3719
+ * collateral allowlist).
3720
+ *
3721
+ * Only originals are read here: they carry the terms AND the market-level
3722
+ * borrow capacity (`availableForClones`). Clones are USER positions and are
3723
+ * resolved per-account in the user-data path.
3724
+ *
3725
+ * NB `price` is the owner-DECLARED liquidation price, not an oracle quote —
3726
+ * see the converter for how that is surfaced.
3727
+ */
3728
+ declare function fetchFrankencoinMarkets(lender: string, chainId: string): Promise<FrankencoinMarketsRaw>;
3729
+
3730
+ /**
3731
+ * Synthesized per-market lender key, e.g.
3732
+ * `FRANKENCOIN_1_5F2C10F7…` — one per ORIGINAL position. The chain id is part
3733
+ * of the key (Fluid/River convention) even though Frankencoin is
3734
+ * Ethereum-only today, so an L2 hub would not collide.
3735
+ */
3736
+ declare function frankencoinLenderKey(lender: string, chainId: string | number, position: string): string;
3737
+ /** Recover `{ lender, chainId, position }` from a per-market key. */
3738
+ declare function frankencoinKeyParts(key: string): {
3739
+ lender: string;
3740
+ chainId: string;
3741
+ position: string;
3742
+ } | undefined;
3743
+ /**
3744
+ * Map one Frankencoin deployment's on-chain batch into the shared
3745
+ * `MorphoGeneralPublicResponse` shape, keyed `FRANKENCOIN_<chainId>_<ORIGINAL>`.
3746
+ *
3747
+ * Three modelling decisions worth knowing (all from FRANKENCOIN_PLAN.md):
3748
+ *
3749
+ * - **The liquidation price is owner-DECLARED, not an oracle.** `price` is
3750
+ * 36-dec scaled against the collateral's decimals and is policed by a
3751
+ * Dutch-auction challenge game. We surface it as the LIQUIDATION price
3752
+ * (it defines `collateralFactor = 1`, i.e. minting is allowed up to
3753
+ * `coll × price`) but value collateral with OUR price feeds. The
3754
+ * divergence between the two is the risk signal, and it is carried
3755
+ * verbatim in `params.market.frankencoin.declaredPrice` so a consumer can
3756
+ * compute it. Never present the resulting health as a protocol invariant.
3757
+ * - **Debt ≠ proceeds.** `minted` is FACE debt including the withheld
3758
+ * `reserveContribution` (10–40 %) plus the upfront pro-rata fee. Both
3759
+ * ppm figures ride along in the descriptor so a quote layer can convert.
3760
+ * - **Positions expire.** `expiration` is surfaced and an expired market is
3761
+ * reported halted (its collateral is subject to forced sale).
3762
+ *
3763
+ * Per market: a COLLATERAL entry (the original's own collateral) and a LOAN
3764
+ * entry (ZCHF). There is no protocol supply side — ZCHF is minted, and the
3765
+ * earn leg is the separate savings module carried by the savings provider —
3766
+ * so `totalDeposits` on the loan row is 0.
3767
+ */
3768
+ declare function convertFrankencoinMarketsToResponse(raw: FrankencoinMarketsRaw, chainId: string, prices?: {
3769
+ [asset: string]: number;
3770
+ }, _additionalYields?: AdditionalYields, tokens?: GenericTokenList): {
3771
+ [m: string]: MorphoGeneralPublicResponse;
3772
+ };
3773
+
3774
+ /** Per-position detail attached to the debt row (raw strings). */
3775
+ interface FrankencoinPositionInfo {
3776
+ /** The position contract — the sub-account id and every write op's target. */
3777
+ position: string;
3778
+ /** The ORIGINAL this position was cloned from (its market). */
3779
+ original: string;
3780
+ /** Owner-declared liquidation price (raw, 36-dec scaled). */
3781
+ declaredPrice: string;
3782
+ /** Unix seconds; a position past this is subject to forced sale. */
3783
+ expiration: string;
3784
+ /** Non-zero while a Dutch-auction challenge is running against it. */
3785
+ challengedAmount: string;
3786
+ /** ppm withheld into the equity reserve — needed to quote a close, since
3787
+ * repaying burns with reserve credit and costs LESS than `minted`. */
3788
+ reserveContributionPPM: string;
3789
+ }
3790
+
2698
3791
  /**
2699
3792
  * Raw on-chain read for ONE Teller `LenderCommitmentGroup` pool. All amounts are
2700
3793
  * raw token base units; `minRateBps` is the pool's min borrow APR in BASIS
@@ -2827,6 +3920,369 @@ interface TellerDiscovery {
2827
3920
  declare const getCachedTellerBids: (chainId: string, account: string) => TellerDiscovery | undefined;
2828
3921
  declare const buildTellerUserCall: (chainId: string, _lender: string, account: string) => Promise<Call[]>;
2829
3922
 
3923
+ /**
3924
+ * TermMax public-data types.
3925
+ *
3926
+ * ─── SIDE SEMANTICS, READ THIS FIRST ───────────────────────────────────────
3927
+ * TermMax names its two curves from the MAKER's perspective, and they cross
3928
+ * over relative to what a taker (our user) is doing:
3929
+ *
3930
+ * maker's `lendCurveCuts` → consumed by a taker who BORROWS
3931
+ * maker's `borrowCurveCuts` → consumed by a taker who LENDS
3932
+ *
3933
+ * `ITermMaxOrder.apr()` inherits that naming, so its `lendApr` is our user's
3934
+ * BORROW rate and its `borrowApr` is our user's LEND rate — and the API's
3935
+ * `priceInfo.term.lcft` / `bcft` follow the same convention.
3936
+ *
3937
+ * Every field in this file is named for the TAKER action. The crossing happens
3938
+ * exactly once, at the adapter boundary in `apiClient.ts` / `onchain.ts`, and
3939
+ * nowhere else. If a value ever produces lend > borrow on the same order, the
3940
+ * mapping has been applied twice.
3941
+ * ───────────────────────────────────────────────────────────────────────────
3942
+ */
3943
+ /**
3944
+ * One segment of an order's piecewise pricing curve (TermMax `CurveCut`).
3945
+ *
3946
+ * `xtReserve` is the LEFT EDGE of the segment's validity interval, not a
3947
+ * quantity. Within a segment the swap solves a constant product over
3948
+ * `(xtReserve + offset, liqSquare_eff / (xtReserve + offset))` where
3949
+ * `liqSquare_eff = liqSquare · daysToMaturity · nif / (365 · 1e8)`.
3950
+ *
3951
+ * This is the TermMax analogue of a Midnight book LEVEL, but continuous rather
3952
+ * than discrete — which is why it cannot reuse `MidnightBookLevel`.
3953
+ */
3954
+ interface TermMaxCurveSegment {
3955
+ xtReserve: bigint;
3956
+ liqSquare: bigint;
3957
+ /** Signed — shifts the virtual reserve. */
3958
+ offset: bigint;
3959
+ }
3960
+ /** Per-order fee ratios, 1e8-scaled (`0.02e8` = 2%). Fees apply to the INTEREST, not principal. */
3961
+ interface TermMaxFeeConfig {
3962
+ lendTakerFeeRatio: bigint;
3963
+ lendMakerFeeRatio: bigint;
3964
+ borrowTakerFeeRatio: bigint;
3965
+ borrowMakerFeeRatio: bigint;
3966
+ mintGtFeeRatio: bigint;
3967
+ mintGtFeeRef: bigint;
3968
+ }
3969
+ /**
3970
+ * Live state of one maker order, already mapped to taker-side semantics.
3971
+ *
3972
+ * An order can source liquidity beyond its own token balance (from an ERC-4626
3973
+ * `pool`, or by minting fresh FT against the maker's own `gtId`), so
3974
+ * balance-derived depth UNDERSTATES what is actually executable. Prefer the
3975
+ * capacity fields on {@link TermMaxBookTop}, or quote.
3976
+ */
3977
+ interface TermMaxOrderState {
3978
+ orderAddress: string;
3979
+ marketAddress: string;
3980
+ /** The maker; `makerIsVault` when it is a curated ERC-4626 vault. */
3981
+ makerAddress?: string;
3982
+ makerIsVault?: boolean;
3983
+ /** THE pricing state in V2 (V1 used the real XT balance). */
3984
+ virtualXtReserve: bigint;
3985
+ ftReserve: bigint;
3986
+ xtReserve: bigint;
3987
+ maxXtReserve: bigint;
3988
+ /** Maker's own GT, used to mint FT on demand. 0 = none (it is a sentinel). */
3989
+ gtId: bigint;
3990
+ /** Curve a TAKER LENDS against (TermMax's `borrowCurveCuts`). */
3991
+ takerLendCuts: TermMaxCurveSegment[];
3992
+ /** Curve a TAKER BORROWS against (TermMax's `lendCurveCuts`). */
3993
+ takerBorrowCuts: TermMaxCurveSegment[];
3994
+ feeConfig?: TermMaxFeeConfig;
3995
+ /** Optional ERC-4626 base-yield sink; absent/zero when unset. */
3996
+ pool?: string;
3997
+ /** Executable size caps in USD, as reported upstream. */
3998
+ lendCapacityUsd: number;
3999
+ borrowCapacityUsd: number;
4000
+ /** Executable size caps in debt-token units (human, not raw). */
4001
+ lendCapacityAmount: number;
4002
+ borrowCapacityAmount: number;
4003
+ /** Fee-free mid APRs as fractions (0.055 = 5.5%), taker-side. Display only. */
4004
+ takerLendApr?: number;
4005
+ takerBorrowApr?: number;
4006
+ }
4007
+ /**
4008
+ * Best executable rate per side plus aggregate depth for one market — the
4009
+ * direct analogue of `MidnightBookTop`, collapsed across every order.
4010
+ *
4011
+ * "Best" is order-agnostic (we scan all orders): best LEND = highest taker-lend
4012
+ * APR, best BORROW = lowest taker-borrow APR.
4013
+ */
4014
+ interface TermMaxBookTop {
4015
+ /** Highest taker LEND APR available, as a fraction. Undefined when no order quotes the side. */
4016
+ bestLendApr?: number;
4017
+ /** Lowest taker BORROW APR available, as a fraction. */
4018
+ bestBorrowApr?: number;
4019
+ /** Aggregate executable depth, USD. */
4020
+ lendDepthUsd: number;
4021
+ borrowDepthUsd: number;
4022
+ /** Aggregate executable depth in debt-token units (human). */
4023
+ lendDepthAmount: number;
4024
+ borrowDepthAmount: number;
4025
+ }
4026
+ /**
4027
+ * A TermMax market, i.e. one (debtToken, collateral, maturity) tuple.
4028
+ *
4029
+ * Discovered DYNAMICALLY — never read from a static registry. Matured markets
4030
+ * disappear from upstream entirely rather than lingering with a flag, and ~15%
4031
+ * of the book can roll on a single maturity date.
4032
+ */
4033
+ interface TermMaxMarketConfig {
4034
+ /** Market contract — also the per-market lender-key body. */
4035
+ market: string;
4036
+ /** FT: the zero-coupon bond ERC-20. THE LEND POSITION. */
4037
+ ft: string;
4038
+ /** XT: the complement (`FT + XT = 1` debt token). */
4039
+ xt: string;
4040
+ /** GT: the ERC-721 loan. THE BORROW POSITION (sub-accounts). */
4041
+ gt: string;
4042
+ /** Debt / loan token. */
4043
+ debtToken: string;
4044
+ debtDecimals: number;
4045
+ collateral: string;
4046
+ collateralDecimals: number;
4047
+ /**
4048
+ * False when decimals could not be resolved from the payload and the 18-dec
4049
+ * default was used. Exists because reading the wrong `assetConfigs` field
4050
+ * names once made EVERY 6-dec stablecoin market silently 10^12 out; a
4051
+ * consumer that cares about exactness should treat `false` as suspect.
4052
+ */
4053
+ debtDecimalsResolved?: boolean;
4054
+ collateralDecimalsResolved?: boolean;
4055
+ /** Display symbol, e.g. `USDC/PT-sUSDE-13AUG2026@16AUG2026`. */
4056
+ symbol?: string;
4057
+ /** Unix seconds. */
4058
+ maturity: number;
4059
+ /** 1e8-scaled, as strings (as upstream reports them). */
4060
+ maxLtv: string;
4061
+ liquidationLtv: string;
4062
+ /** false ⇒ NO liquidation at all, only post-maturity physical delivery. */
4063
+ liquidatable: boolean;
4064
+ /** Post-maturity liquidation window, seconds (7200 on every live market). */
4065
+ liquidationWindowSeconds?: number;
4066
+ /** Contract `getVersion()`: `v2` = "2.0.0", `v2_01` = "2.0.1". Both are V2. */
4067
+ version?: string;
4068
+ /** Market-level fee config (1e8-scaled). */
4069
+ feeConfig?: TermMaxFeeConfig;
4070
+ /** Oracle the protocol itself prices LTV/liquidation against. */
4071
+ oracle?: string;
4072
+ isMatured?: boolean;
4073
+ isEnabled?: boolean;
4074
+ }
4075
+ /** A market paired with its live book state. `top` is null when the fetch failed. */
4076
+ interface TermMaxMarketRaw {
4077
+ config: TermMaxMarketConfig;
4078
+ top: TermMaxBookTop | null;
4079
+ /** Per-order state, best-first by taker rate. Empty when the market has no live orders. */
4080
+ orders: TermMaxOrderState[];
4081
+ }
4082
+ /**
4083
+ * Pluggable TermMax data source — the hosted API today, a self-hosted indexer
4084
+ * or a pure on-chain reader later. Mirrors `MidnightBookSource`.
4085
+ */
4086
+ interface TermMaxDataSource {
4087
+ /**
4088
+ * Every live market on a chain with its orders, or null when unavailable.
4089
+ * One upstream call per chain on the happy path.
4090
+ */
4091
+ getChainMarkets(chainId: string): Promise<TermMaxMarketRaw[] | null>;
4092
+ }
4093
+
4094
+ /**
4095
+ * Resolve a TermMax market from the discovery cache, or undefined when it was
4096
+ * never fetched / has gone stale. Callers that need a guaranteed answer should
4097
+ * `await fetchTermMaxMarkets(chainId)` first (or read the market on-chain).
4098
+ */
4099
+ declare function getCachedTermMaxMarket(chainId: string | number, market: string): TermMaxMarketConfig | undefined;
4100
+ /** All markets cached for a chain (may be empty before the first fetch). */
4101
+ declare function getCachedTermMaxMarkets(chainId: string | number): TermMaxMarketConfig[];
4102
+ /**
4103
+ * Fetch every live TermMax market on a chain, with its order book collapsed to
4104
+ * a best-rate + depth snapshot.
4105
+ *
4106
+ * Returns `[]` (not an error) when the chain has no TermMax deployment
4107
+ * configured, so callers can fan out across chains unconditionally.
4108
+ *
4109
+ * Markets are DISCOVERED, never read from a static list: matured markets vanish
4110
+ * from upstream entirely rather than lingering with a flag, and ~15% of the
4111
+ * book can roll on a single maturity date.
4112
+ */
4113
+ declare function fetchTermMaxMarkets(chainId: string, source?: TermMaxDataSource): Promise<TermMaxMarketRaw[]>;
4114
+
4115
+ /**
4116
+ * Map fetched TermMax markets into the shared `MorphoGeneralPublicResponse`
4117
+ * shape, keyed by the synthesized `TERMMAX_<MARKET_ADDR>` lender key.
4118
+ *
4119
+ * Each TermMax market is ONE (debt, collateral, maturity) tuple, so it emits
4120
+ * exactly two entries — unlike Midnight, which has several collateral legs per
4121
+ * market:
4122
+ * - LOAN entry: `depositRate` = best taker LEND APR, `variableBorrowRate` =
4123
+ * best taker BORROW APR (already crossed from TermMax's maker-side names in
4124
+ * `apiClient.parseOrder`), plus order-book depth as the liquidity proxy.
4125
+ * - COLLATERAL entry: maxLtv → collateralFactor, liquidationLtv →
4126
+ * borrowCollateralFactor, fixed 10% liquidation penalty.
4127
+ *
4128
+ * Rates are DISPLAY values from the fee-free mid quote. Anything that actually
4129
+ * executes must price off a live quote at build time.
4130
+ */
4131
+ declare function convertTermMaxMarketsToResponse(raw: TermMaxMarketRaw[], chainId: string, prices?: {
4132
+ [asset: string]: number;
4133
+ }, _additionalYields?: AdditionalYields, tokens?: GenericTokenList): {
4134
+ [m: string]: MorphoGeneralPublicResponse;
4135
+ };
4136
+
4137
+ /** Hosted TermMax data API (Swagger at `/api-docs`, spec at `/api-docs-json`). */
4138
+ declare const DEFAULT_TERMMAX_API = "https://api.termmax.ts.finance";
4139
+ type FetchLike = typeof fetch;
4140
+ /** Resolve a chain's TermMax API base (config override → hosted default). */
4141
+ declare function termMaxApiBase(chainId: string): string;
4142
+ /**
4143
+ * Hosted-API data source. ONE call per chain — `GET /market/data?chainId=`
4144
+ * returns global config, asset configs, markets, order configs and live order
4145
+ * state together (~240KB on Ethereum).
4146
+ *
4147
+ * This is the swappable seam: a self-hosted indexer or a pure on-chain reader
4148
+ * can implement {@link TermMaxDataSource} later without touching callers.
4149
+ *
4150
+ * Reliability posture: the endpoint is an undocumented app backend (no
4151
+ * versioning or rate-limit statement, `/admin/*` routes on the same host), so
4152
+ * treat it exactly like the Morpho GraphQL API — primary, but the caller keeps
4153
+ * a last-known-good snapshot (see fetchPublic.ts).
4154
+ */
4155
+ declare class TermMaxApiSource implements TermMaxDataSource {
4156
+ private readonly baseUrl;
4157
+ private readonly fetchImpl;
4158
+ constructor(baseUrl: string, fetchImpl?: FetchLike);
4159
+ getChainMarkets(chainId: string): Promise<TermMaxMarketRaw[] | null>;
4160
+ }
4161
+ /** Default data source for a chain (hosted API, resolved per chain). */
4162
+ declare function createTermMaxDataSource(chainId: string, fetchImpl?: FetchLike): TermMaxDataSource;
4163
+
4164
+ /** TermMax ratio base: `0.01e8` = 1%. */
4165
+ declare const DECIMAL_BASE = 100000000n;
4166
+ /**
4167
+ * Days to maturity, **ceilinged to whole days** — exactly as
4168
+ * `TermMaxOrderV2._daysToMaturity` does it:
4169
+ *
4170
+ * `(maturity - now + SECONDS_IN_DAY - 1) / SECONDS_IN_DAY`
4171
+ *
4172
+ * Reproduce the ceiling or an off-chain quote drifts by up to a full day of
4173
+ * interest against the contract. Returns 0 at/after maturity.
4174
+ */
4175
+ declare function daysToMaturity(maturity: number, nowSec: number): bigint;
4176
+ /**
4177
+ * Marginal (fee-free) APR implied by a curve at a given reserve, 1e8-scaled —
4178
+ * the same formula `TermMaxOrderV2.apr()` uses:
4179
+ *
4180
+ * `apr = vFt · 1e8 · 365 / (vXt · daysToMaturity)`
4181
+ *
4182
+ * Returns 0n for an empty curve or a matured market. This is a MID PRICE at the
4183
+ * current reserve: it ignores both the taker fee and trade size, so it is for
4184
+ * display and sorting only — never quote against it.
4185
+ */
4186
+ declare function curveApr(cuts: TermMaxCurveSegment[], virtualXtReserve: bigint, days: bigint): bigint;
4187
+ /** 1e8-scaled ratio → plain fraction (`5_500_000n` → `0.055`). */
4188
+ declare function ratioToNumber(v: bigint): number;
4189
+ /** 1e8-scaled ratio → percent (`5_500_000n` → `5.5`). */
4190
+ declare function ratioToPercent(v: bigint): number;
4191
+ /**
4192
+ * Marginal APR as a fraction for one side, taker-side by construction.
4193
+ *
4194
+ * Pass `takerLendCuts` for the lend rate and `takerBorrowCuts` for the borrow
4195
+ * rate — the maker/taker crossing has already been applied when those fields
4196
+ * were built (see types.ts).
4197
+ */
4198
+ declare function curveAprNumber(cuts: TermMaxCurveSegment[], virtualXtReserve: bigint, days: bigint): number;
4199
+ /**
4200
+ * Net-interest factor for a taker LEND (`1e8 - lendTakerFeeRatio`).
4201
+ * Guarded so a malformed fee config cannot produce a non-positive factor.
4202
+ */
4203
+ declare function lendNif(fee?: TermMaxFeeConfig): bigint;
4204
+ /** Net-interest factor for a taker BORROW (`1e8 + borrowTakerFeeRatio`). */
4205
+ declare function borrowNif(fee?: TermMaxFeeConfig): bigint;
4206
+ /**
4207
+ * The GT-mint fee ratio at a given time to maturity, 1e8-scaled — mirrors
4208
+ * `TermMaxMarketV2.mintGtFeeRatio()`:
4209
+ *
4210
+ * `days · feeRatio · feeRef / (365·1e8 + feeRef·days)`
4211
+ *
4212
+ * This is a genuine percentage OF PRINCIPAL charged once at borrow time
4213
+ * (`issueFee = debt · ratio / 1e8`), so it maps onto the cross-protocol
4214
+ * `FixedTermInfo.fees.originationFeePercent`. It is NOT the raw
4215
+ * `feeConfig.mintGtFeeRatio` — prefer reading the contract when you can.
4216
+ */
4217
+ declare function mintGtFeeRatio(fee: TermMaxFeeConfig | undefined, days: bigint): bigint;
4218
+ /**
4219
+ * TermMax liquidation penalty as a fraction of the repaid debt.
4220
+ *
4221
+ * Protocol constants, not per-market: the liquidator is paid 5% and the
4222
+ * protocol reserve takes 5%, so the borrower loses 10% of the liquidated debt
4223
+ * value. (Loans over $10k can only be liquidated 50% at a time; that is a size
4224
+ * cap, not a penalty, and is modeled as `closeFactor`.)
4225
+ */
4226
+ declare const TERMMAX_LIQUIDATION_PENALTY = 0.1;
4227
+ declare const TERMMAX_LIQUIDATOR_BONUS = 0.05;
4228
+ /** Partial-liquidation threshold, USD: above it a single call can take at most 50%. */
4229
+ declare const TERMMAX_PARTIAL_LIQUIDATION_THRESHOLD_USD = 10000;
4230
+ declare const TERMMAX_PARTIAL_CLOSE_FACTOR = 0.5;
4231
+ /** Post-maturity liquidation window before physical delivery (`Constants.LIQUIDATION_WINDOW`). */
4232
+ declare const TERMMAX_LIQUIDATION_WINDOW_SECS = 7200;
4233
+ /**
4234
+ * Parse a 1e8-scaled LTV string into a plain fraction (`87500000` → `0.875`).
4235
+ * Returns 0 for missing/garbage input rather than NaN, so a bad config row
4236
+ * degrades to "no collateral value" instead of poisoning the optimizer.
4237
+ */
4238
+ declare function parseTermMaxLtv(v: string | number | bigint | undefined): number;
4239
+
4240
+ /**
4241
+ * TermMax user data is a DISCOVERY-first, async build — the Teller/Liquity
4242
+ * shape, not the Midnight one.
4243
+ *
4244
+ * Midnight can build synchronously because its positions are keyed
4245
+ * `(marketId, user)`: one fixed slot per user per market, with the market list
4246
+ * coming from a static registry. TermMax has neither property:
4247
+ *
4248
+ * 1. the market list is DYNAMIC (markets churn on every maturity roll and
4249
+ * matured ones vanish upstream), so it must be resolved at call time; and
4250
+ * 2. borrow positions are GT ERC-721 sub-accounts — a user can hold N per
4251
+ * market and the ids are not derivable.
4252
+ *
4253
+ * But it is cheaper than Teller: `TermMaxViewer.getPositionDetails(markets[],
4254
+ * owner)` returns FT/XT/collateral balances AND every GT with its debt and
4255
+ * collateral in ONE call, so discovery and data collapse into a single
4256
+ * multicall entry — no two-phase discovery round-trip. The viewer `try`-guards
4257
+ * each position internally, so one bad loan cannot poison the batch.
4258
+ *
4259
+ * Health is NOT read per GT (`getLiquidationInfo`): it is computed downstream
4260
+ * from LTVs + oracle prices in `createMultiAccountTypeUserState`, exactly as
4261
+ * Midnight does. That keeps this to one call.
4262
+ */
4263
+ /** Every TermMax market read consumes exactly one viewer call (batched). */
4264
+ declare const TERMMAX_CALLS_PER_ACCOUNT = 1;
4265
+ interface TermMaxDiscovery {
4266
+ /** Markets passed to the viewer, IN ORDER — the parser slices results by index. */
4267
+ markets: TermMaxMarketConfig[];
4268
+ at: number;
4269
+ }
4270
+ /**
4271
+ * The market layout used for the last build on this (chain, account).
4272
+ *
4273
+ * The parser runs as a separate phase, fed only the multicall results, so it
4274
+ * needs the layout the builder chose — the same trick the Teller / Liquity /
4275
+ * Lista-broker caches use.
4276
+ */
4277
+ declare const getCachedTermMaxDiscovery: (chainId: string, account: string) => TermMaxDiscovery | undefined;
4278
+ /**
4279
+ * Build the user-data call for every live TermMax market on a chain.
4280
+ *
4281
+ * Returns `[]` when the chain has no TermMax deployment, no viewer configured,
4282
+ * or no live markets — callers can fan out unconditionally.
4283
+ */
4284
+ declare const buildTermMaxUserCall: (chainId: string, _lender: string, account: string) => Promise<Call[]>;
4285
+
2830
4286
  type PendleAssetTypes = 'PT' | 'YT' | 'SY' | 'PENDLE_LP';
2831
4287
  /**
2832
4288
  * Main function to fetch Pendle prices
@@ -4015,45 +5471,160 @@ interface EulerEarnVault extends VaultClassificationFields {
4015
5471
  depositRate: number;
4016
5472
  /** Performance fee in percent (e.g. `5.0` = 5 %). */
4017
5473
  fee: number;
4018
- /** Governance timelock in seconds — the delay before owner/curator config
4019
- * changes take effect (Euler Earn `governance.timelock`). Governance-only,
4020
- * NOT a per-deposit withdrawal lock. Mirrors `MorphoVault.timelock`. */
5474
+ /** Governance timelock in seconds — the delay before owner/curator config
5475
+ * changes take effect (Euler Earn `governance.timelock`). Governance-only,
5476
+ * NOT a per-deposit withdrawal lock. Mirrors `MorphoVault.timelock`. */
5477
+ timelock?: number;
5478
+ /** Owner address, lowercased — may be absent if not set. */
5479
+ owner?: string;
5480
+ /** Curator / fee-recipient address, lowercased. */
5481
+ curator?: string;
5482
+ /** Human-readable curator label for UI, derived best-effort from the
5483
+ * (curator-branded) vault name — the subgraph exposes only the address.
5484
+ * Mirrors `MorphoVault.curatorName` so consumers can write generic UI. */
5485
+ curatorName?: string;
5486
+ /** Guardian address, lowercased — may be absent. */
5487
+ guardian?: string;
5488
+ /** Fee recipient, lowercased — may be absent. */
5489
+ feeRecipient?: string;
5490
+ /** Hydrated asset metadata from the provided token list, if any. */
5491
+ asset?: GenericCurrency;
5492
+ /** USD price of one underlying unit, if prices were supplied. */
5493
+ priceUsd?: number;
5494
+ /** Human-formatted total assets (`totalAssets / 10^assetDecimals`). */
5495
+ totalAssetsFormatted: number;
5496
+ /** Human-formatted total assets in USD. */
5497
+ totalAssetsUsd: number;
5498
+ /** Currently withdrawable underlying, raw integer as string. Computed
5499
+ * as `idle_cash + Σ min(allocated_i, evk_cash_i)` across the Earn
5500
+ * vault's strategies — accounts for per-EVK utilization caps. Falls
5501
+ * back to `totalAssets` (optimistic ceiling) when the EVK index is
5502
+ * unavailable. Never exceeds `totalAssets`. */
5503
+ liquidity: string;
5504
+ /** Human-formatted immediate withdrawable liquidity. */
5505
+ liquidityFormatted: number;
5506
+ /** Human-formatted immediate withdrawable liquidity in USD. */
5507
+ liquidityUsd: number;
5508
+ }
5509
+ /** Full parsed payload: per-vault-address map. */
5510
+ type EulerEarnVaults = {
5511
+ /** Keyed by lowercased vault address. */
5512
+ [vaultAddress: string]: EulerEarnVault;
5513
+ };
5514
+
5515
+ /**
5516
+ * Parsed TermMax curated vault entry.
5517
+ *
5518
+ * TermMax vaults are the CONTINUOUS earn side of a fixed-maturity protocol.
5519
+ * The per-market lend position (buying FT) expires; a vault holds a rolling
5520
+ * book of orders across many maturities and rolls them for the depositor, so
5521
+ * from the outside it behaves like an ordinary ERC-4626 yield vault. Same
5522
+ * relationship Fluid has between its per-vault borrow markets and its fTokens.
5523
+ *
5524
+ * Multiple vaults exist per underlying with different curators (Keyrock, MEV
5525
+ * Capital, Origami and TermMax itself all curate), so the map is keyed by
5526
+ * VAULT ADDRESS —
5527
+ * the `MorphoVaults` / `SiloVaults` / `EulerEarnVaults` convention, not the
5528
+ * key-by-underlying convention Fluid and Gearbox use.
5529
+ *
5530
+ * IMPORTANT — vault assets and lender-side depth are the SAME capital. A
5531
+ * vault's deposits are what appear as lend-side order depth in the TermMax
5532
+ * lender data, so summing "TermMax lender TVL + TermMax vault TVL" double
5533
+ * counts.
5534
+ */
5535
+ interface TermMaxVault extends VaultClassificationFields {
5536
+ /** Vault (share-token) contract address, lowercased. */
5537
+ address: string;
5538
+ /** Lowercased underlying ERC-20 address (the markets' debt token). */
5539
+ underlying: string;
5540
+ /** Vault share-token symbol, e.g. `TMKR-RLUSD`. */
5541
+ symbol: string;
5542
+ /** Vault share-token name, e.g. `Coinshift rlUSD vault`. */
5543
+ name: string;
5544
+ /** Share-token decimals. */
5545
+ decimals: number;
5546
+ /** Underlying asset decimals — read on-chain, may differ from `decimals`. */
5547
+ assetDecimals: number;
5548
+ /** Total underlying assets held, raw integer as string. */
5549
+ totalAssets: string;
5550
+ /** Total shares minted, raw integer as string. */
5551
+ totalSupply: string;
5552
+ /** Raw `convertToAssets(10**shareDecimals)` — 1 share → X underlying. */
5553
+ convertToAssets: string;
5554
+ /**
5555
+ * Supply APR in percent, **net of the performance fee**.
5556
+ *
5557
+ * TermMax's own formula (`OrderManagerV2`):
5558
+ * `annualizedInterest · (1e8 − performanceFeeRate) / accretingPrincipal`
5559
+ * `annualizedInterest` is GROSS — the fee is taken out of it on accrual
5560
+ * (`_accretingPrincipal += interest − performanceFeeToCurator`), so it must
5561
+ * be netted here to match the README's "net of performance fee" convention.
5562
+ *
5563
+ * Served directly by the API. The on-chain fallback derives it, because
5564
+ * `apr()` AND `accretingPrincipal()` both revert on the deployed 2.0.0
5565
+ * vaults — it substitutes `totalAssets` as the denominator, which is larger
5566
+ * and so understates rather than overstates. The derivation was validated
5567
+ * against the API on both funded Ethereum vaults: 2.579% and 1.449%,
5568
+ * matching to 3 decimals.
5569
+ */
5570
+ supplyRate: number;
5571
+ /** Extra rewards APR (TMX emissions). 0 on the on-chain fallback, which
5572
+ * cannot see them. */
5573
+ rewardsRate: number;
5574
+ /** `supplyRate + rewardsRate` — what a depositor actually earns. */
5575
+ depositRate: number;
5576
+ /** Performance fee in percent (e.g. `10` = 10%). */
5577
+ fee: number;
5578
+ /** Governance timelock in seconds for curator/guardian config changes. */
4021
5579
  timelock?: number;
4022
- /** Owner address, lowercased — may be absent if not set. */
4023
- owner?: string;
4024
- /** Curator / fee-recipient address, lowercased. */
5580
+ /** Curator address, lowercased. */
4025
5581
  curator?: string;
4026
- /** Human-readable curator label for UI, derived best-effort from the
4027
- * (curator-branded) vault name — the subgraph exposes only the address.
4028
- * Mirrors `MorphoVault.curatorName` so consumers can write generic UI. */
5582
+ /**
5583
+ * Human-readable curator label.
5584
+ *
5585
+ * ONLY the API carries this — the on-chain surface exposes an address and
5586
+ * nothing else. Do NOT derive it from `name`: the vault literally called
5587
+ * "Coinshift rlUSD vault" is curated by **Keyrock**.
5588
+ */
4029
5589
  curatorName?: string;
4030
- /** Guardian address, lowercased — may be absent. */
5590
+ /** Guardian address, lowercased. */
4031
5591
  guardian?: string;
4032
- /** Fee recipient, lowercased — may be absent. */
4033
- feeRecipient?: string;
4034
5592
  /** Hydrated asset metadata from the provided token list, if any. */
4035
5593
  asset?: GenericCurrency;
4036
5594
  /** USD price of one underlying unit, if prices were supplied. */
4037
5595
  priceUsd?: number;
4038
- /** Human-formatted total assets (`totalAssets / 10^assetDecimals`). */
5596
+ /** `totalAssets / 10^assetDecimals`. */
4039
5597
  totalAssetsFormatted: number;
4040
- /** Human-formatted total assets in USD. */
5598
+ /** `totalAssetsFormatted * priceUsd`. */
4041
5599
  totalAssetsUsd: number;
4042
- /** Currently withdrawable underlying, raw integer as string. Computed
4043
- * as `idle_cash + Σ min(allocated_i, evk_cash_i)` across the Earn
4044
- * vault's strategies — accounts for per-EVK utilization caps. Falls
4045
- * back to `totalAssets` (optimistic ceiling) when the EVK index is
4046
- * unavailable. Never exceeds `totalAssets`. */
5600
+ /**
5601
+ * Immediately withdrawable underlying, raw integer as string.
5602
+ *
5603
+ * A TermMax vault's capital is committed to maker orders until each order's
5604
+ * maturity, so only part is instantly exitable — the rest needs the curator
5605
+ * to unwind or a maturity to roll. Sourced from the API's `redeemableAmt`
5606
+ * (falling back to `idleFunds`), or the vault's own underlying balance
5607
+ * on-chain. Deliberately NOT `totalAssets`.
5608
+ */
4047
5609
  liquidity: string;
4048
- /** Human-formatted immediate withdrawable liquidity. */
4049
5610
  liquidityFormatted: number;
4050
- /** Human-formatted immediate withdrawable liquidity in USD. */
4051
5611
  liquidityUsd: number;
5612
+ /** Contract `getVersion()` / API `version`, e.g. `"2.0.0"` / `"v2"`. */
5613
+ version?: string;
5614
+ /** Vault is paused — deposits blocked, existing funds still visible. */
5615
+ isPaused?: boolean;
5616
+ /** Deposit cap in underlying base units (API `capacity`). */
5617
+ supplyCap?: string;
5618
+ /**
5619
+ * The vault's ERC-4626 base-yield pool for idle funds (v2_01 "composable
5620
+ * base yield"). Idle capital earns here instead of sitting dead.
5621
+ */
5622
+ basePool?: string;
4052
5623
  }
4053
5624
  /** Full parsed payload: per-vault-address map. */
4054
- type EulerEarnVaults = {
5625
+ type TermMaxVaults = {
4055
5626
  /** Keyed by lowercased vault address. */
4056
- [vaultAddress: string]: EulerEarnVault;
5627
+ [vaultAddress: string]: TermMaxVault;
4057
5628
  };
4058
5629
 
4059
5630
  /**
@@ -4308,11 +5879,41 @@ interface LstWithdrawalRequest {
4308
5879
  * ERC-7540 requestId, …). Encoded as a string for cross-protocol
4309
5880
  * uniformity. */
4310
5881
  requestId: string;
4311
- /** Raw underlying amount the request will return on claim. Wei-like
4312
- * integer string. Some protocols only know this at claim time
4313
- * (queue-finalization with floating finalization rate) — those
4314
- * surface the **expected** amount at request time. */
5882
+ /** Raw amount the request will return on claim, in the token that
5883
+ * escrow actually pays out. Wei-like integer string. Some protocols
5884
+ * only know this at claim time (queue-finalization with a floating
5885
+ * finalization rate) — those surface the **expected** amount at
5886
+ * request time.
5887
+ *
5888
+ * Strata is the one entry where the denomination is not simply the
5889
+ * vault's underlying, and it is easy to get wrong: the escrow is
5890
+ * **keyed** by the collateral token (`finalize(sUSDe, user)`) but the
5891
+ * amount it records is whatever that leg settles in — the tranche's
5892
+ * `asset()` (USDe) on the UnstakeCooldown, which books Ethena's
5893
+ * unstake output, and collateral-token shares on the ERC20Cooldown.
5894
+ * We do not normalize between them; read `withdrawQueue` to tell the
5895
+ * legs apart. Fork-verified for the UnstakeCooldown leg 2026-08-04
5896
+ * (10,000 USDe in → 9,997.5 USDe out at a 2.49 bps exit fee, with
5897
+ * zero sUSDe paid); the ERC20Cooldown denomination is read off the
5898
+ * strategy source, which escrows `sUSDe.previewWithdraw(baseAssets)`
5899
+ * shares. */
4315
5900
  amountUnderlying: string;
5901
+ /** Raw share amount of the request, for protocols whose claim call
5902
+ * takes shares (ERC-7540 `redeem`, sUSD3's plain 4626 `redeem`).
5903
+ * Passed back verbatim into the claim builder. */
5904
+ shares?: string;
5905
+ /** The escrow contract this request actually lives on, when the
5906
+ * protocol runs more than one and the registry's default is not
5907
+ * necessarily the right claim target. Strata gives each market both
5908
+ * an `UnstakeCooldown` (base-asset leg) and an `ERC20Cooldown`
5909
+ * (collateral-token leg) — a claim built against the wrong one is a
5910
+ * no-op — so the reader reports which. Passed back verbatim into
5911
+ * the claim builder. */
5912
+ withdrawQueue?: string;
5913
+ /** The token the escrow books this request under, when the claim
5914
+ * call takes it as an argument (Strata's
5915
+ * `finalize(claimToken, user)`). Passed back verbatim. */
5916
+ claimToken?: string;
4316
5917
  /** Status discriminator. */
4317
5918
  status: LstWithdrawalStatus;
4318
5919
  /** Unix seconds when the request becomes claimable. Set for
@@ -4349,7 +5950,7 @@ type LstWithdrawalStatus =
4349
5950
  | 'expired';
4350
5951
  /** Withdrawal-reader implementation kind — drives which enumeration
4351
5952
  * function the user is queried against. */
4352
- type LstWithdrawalReaderKind = 'lidoQueue' | 'etherfiNft' | 'yieldNestNft' | 'staderEthxQueue' | 'staderMaticXQueue' | 'renzoQueue' | 'kelpQueue' | 'benqiSavaxQueue' | 'beetsStSQueue' | 'hyperbeatBeHype7540' | 'erc7540' | 'ethenaCooldown' | 'swellNft' | 'stakeWiseSubgraph' | 'mantleCallerSuppliedIds' | 'pufferCallerSuppliedIds' | 'trufinCallerSuppliedIds' | 'lairCallerSuppliedIds' | 'stceloAccountQueue' | 'kinetiqQueue' | 'beHypeQueue' | 'valantisBurnQueue' | 'listaQueue' | 'iberaQueue' | 'primeStakingQueue' | 'berapawRedeemQueue' | 'stellaUnbondQueue' | 'eventsOnly' | 'noQueue' | 'unverified';
5953
+ type LstWithdrawalReaderKind = 'lidoQueue' | 'etherfiNft' | 'yieldNestNft' | 'staderEthxQueue' | 'staderMaticXQueue' | 'renzoQueue' | 'kelpQueue' | 'benqiSavaxQueue' | 'beetsStSQueue' | 'hyperbeatBeHype7540' | 'erc7540' | 'ethenaCooldown' | 'susd3Cooldown' | 'strataCooldown' | 'swellNft' | 'stakeWiseSubgraph' | 'mantleCallerSuppliedIds' | 'pufferCallerSuppliedIds' | 'trufinCallerSuppliedIds' | 'lairCallerSuppliedIds' | 'stceloAccountQueue' | 'kinetiqQueue' | 'beHypeQueue' | 'valantisBurnQueue' | 'listaQueue' | 'iberaQueue' | 'primeStakingQueue' | 'berapawRedeemQueue' | 'stellaUnbondQueue' | 'eventsOnly' | 'noQueue' | 'unverified';
4353
5954
  /** Map keyed by lowercased LST share-token address. The orchestrator
4354
5955
  * fetches all LSTs on a chain in parallel and returns this map
4355
5956
  * (possibly with empty arrays for LSTs the user has no requests
@@ -4395,6 +5996,17 @@ interface LstWithdrawalRegistryEntry {
4395
5996
  /** Polygon IStakeManager — only for `staderMaticXQueue`. The
4396
5997
  * finalization check requires `epoch()` + `withdrawalDelay()`. */
4397
5998
  polygonStakeManager?: string;
5999
+ /** Second escrow contract probed with the same reader — only for
6000
+ * `strataCooldown`, where a market runs both an `UnstakeCooldown`
6001
+ * (base-asset leg, in `withdrawalContract`) and an `ERC20Cooldown`
6002
+ * (collateral-token leg). Lowercased. */
6003
+ secondaryWithdrawalContract?: string;
6004
+ /** The token an escrow's requests are booked under — only for
6005
+ * `strataCooldown` (`balanceOf(escrowToken, user)` /
6006
+ * `finalize(escrowToken, user)`). The market's staked collateral
6007
+ * (sUSDe, sNUSD, mHYPER, …), NOT the tranche's `asset()`.
6008
+ * Lowercased. */
6009
+ escrowToken?: string;
4398
6010
  }
4399
6011
  /** Returns the withdrawal-registry entries for a chain, or `[]`. */
4400
6012
  declare const getLstWithdrawalRegistry: (chainId: string, extraEntries?: LstWithdrawalRegistryEntry[]) => LstWithdrawalRegistryEntry[];
@@ -4529,7 +6141,7 @@ declare const resolveStCeloDepositGroup: (user: Address, requestedGroup?: string
4529
6141
  * kept separate so the two providers can evolve independently without
4530
6142
  * one provider's withdrawal taxonomy creep affecting the other.
4531
6143
  */
4532
- type SavingsWithdrawalMode = 'instant' | 'fixed-cooldown' | 'queued' | 'request-based' | 'fee-or-queued';
6144
+ type SavingsWithdrawalMode = 'instant' | 'instant-capped' | 'fixed-cooldown' | 'queued' | 'request-based' | 'fee-or-queued';
4533
6145
  /**
4534
6146
  * Parsed savings-vault entry.
4535
6147
  *
@@ -4560,6 +6172,10 @@ interface SavingsVault extends VaultClassificationFields {
4560
6172
  displayName: string;
4561
6173
  /** Brand label — `Ethena`, `Sky`, `Maker`, `Angle`, `Falcon`, … */
4562
6174
  brand: string;
6175
+ /** Curated user-facing explainer (from the registry): what the
6176
+ * underlying is, where the yield comes from, and the exit
6177
+ * mechanics / trust caveat when load-bearing. */
6178
+ description: string;
4563
6179
  /** Alias for `brand` to match `curatorName` on the other providers'
4564
6180
  * vault types — keeps cross-provider UI code uniform. */
4565
6181
  curatorName: string;
@@ -4591,9 +6207,13 @@ interface SavingsVault extends VaultClassificationFields {
4591
6207
  /** Sum of `supplyRate + rewardsRate` — what a depositor actually
4592
6208
  * earns. */
4593
6209
  depositRate: number;
4594
- /** Always true for this provider — every entry is ERC-4626. Kept
4595
- * for API parity with `LstShareToken.isErc4626`. */
4596
- isErc4626: true;
6210
+ /** Whether the share token implements ERC-4626. True for every entry
6211
+ * except Native's wNLP, which is a bespoke wrapper (`asset()`,
6212
+ * `totalAssets()` and `convertToAssets()` all revert) — the
6213
+ * `convertTo*` / `exchangeRate` fields below are still populated for
6214
+ * it, derived from its own rate getter. Parity with
6215
+ * `LstShareToken.isErc4626`. */
6216
+ isErc4626: boolean;
4597
6217
  /** Whether the share token itself rebases. False for nearly every
4598
6218
  * savings vault (they're the non-rebasing wrapper); rebasing
4599
6219
  * surfaces sit on the underlying (e.g. USDe inside sUSDe). */
@@ -4607,12 +6227,51 @@ interface SavingsVault extends VaultClassificationFields {
4607
6227
  mintContract?: string;
4608
6228
  /** Withdrawal mechanism. */
4609
6229
  withdrawalMode: SavingsWithdrawalMode;
4610
- /** Fixed cooldown in seconds for `fixed-cooldown` mode. Some
4611
- * protocols (Ethena, Avant) expose this on-chain via
4612
- * `cooldownDuration()`. Static value here pinned in the registry
4613
- * re-read on-chain if you need the precise governance-current
4614
- * value. */
6230
+ /** Waiting period in seconds before a requested redemption can be
6231
+ * claimed. For `fixed-cooldown` entries (Ethena, Avant) this is the
6232
+ * registry-pinned value. For `fee-or-queued` entries it is the
6233
+ * **live** on-chain queue window, which varies per asset (Native
6234
+ * runs 8 h on some pools and 3 days on most). */
4615
6235
  withdrawalCooldownSeconds?: number;
6236
+ /**
6237
+ * Exit fee in basis points (`10` = 0.10 %) — same units and name as
6238
+ * `GearboxV3Pool.withdrawFeeBps`, so consumers read one field across
6239
+ * providers.
6240
+ *
6241
+ * **How it is charged** (Native): it is *not* a deposit fee, a
6242
+ * management fee, or a skim on yield — `exchangeRate` and `supplyRate`
6243
+ * are already net of everything Native takes on the way in. It is a
6244
+ * one-off haircut on the **instant** exit only, taken out of the
6245
+ * underlying paid to the receiver:
6246
+ *
6247
+ * received = shares × exchangeRate × (1 − withdrawFeeBps/10_000)
6248
+ *
6249
+ * so redeeming 9,867.98 wNLP-USDC worth 10,000 USDC returns 9,900 USDC
6250
+ * at 100 bps. The shares burn in full — the fee is deducted from the
6251
+ * payout, never charged as a separate transfer, so a caller does not
6252
+ * need to fund it or approve anything extra.
6253
+ *
6254
+ * The **queued** leg (`withdrawQueue`, after
6255
+ * `withdrawalCooldownSeconds`) pays out at par and does not touch this
6256
+ * field. Its cost is implicit instead: the payout is snapshotted when
6257
+ * the request is made, so yield accruing during the wait goes to the
6258
+ * protocol rather than the requester.
6259
+ *
6260
+ * `0` means the instant leg is free. Absent when the vault has no
6261
+ * instant leg at all.
6262
+ */
6263
+ withdrawFeeBps?: number;
6264
+ /** Whether the instant leg is enabled at all — some assets are
6265
+ * queue-only. When `false`, `liquidity` is `0` regardless of the
6266
+ * protocol's inventory and `withdrawFeeBps` is unreachable. */
6267
+ instantRedeemEnabled?: boolean;
6268
+ /** Contract the instant leg draws from — Native's per-chain
6269
+ * `CreditVault`. Its underlying balance is what `liquidity`
6270
+ * measures. */
6271
+ inventoryContract?: string;
6272
+ /** Contract a delayed redemption is requested from and claimed
6273
+ * against, when it is not the share token itself. */
6274
+ withdrawQueue?: string;
4616
6275
  /** Hydrated asset metadata from the provided token list, if any. */
4617
6276
  asset?: GenericCurrency;
4618
6277
  /** USD price of one underlying unit, if prices were supplied. */
@@ -4621,18 +6280,60 @@ interface SavingsVault extends VaultClassificationFields {
4621
6280
  totalAssetsFormatted: number;
4622
6281
  /** Human-formatted total assets in USD. */
4623
6282
  totalAssetsUsd: number;
4624
- /** Instantly-withdrawable underlying right now, raw integer string,
4625
- * derived from `withdrawalMode`: `instant` vaults are fully liquid
4626
- * (`= totalAssets`); `fixed-cooldown` / `queued` / `request-based` /
4627
- * `fee-or-queued` vaults require a waiting period, so their
4628
- * instantaneous withdrawable is `0`. (A cooldown vault's underlying
4629
- * may still be redeemable after the wait this field is the *right
4630
- * now* figure, matching the cross-provider `liquidity` semantic.) */
6283
+ /** Withdrawable underlying **right now**, raw integer string, per
6284
+ * `withdrawalMode`:
6285
+ * - `instant` fully liquid (`= totalAssets`).
6286
+ * - `instant-capped` settles in the same transaction, but only up
6287
+ * to a live inventory that is smaller than the vault: Spark
6288
+ * Savings V1 is capped by the PSM3 pocket's underlying balance
6289
+ * (6–29 % of TVL on the L2 deployments) and Spark Vaults V2 by the
6290
+ * vault's own idle balance (the rest is lent out through the Spark
6291
+ * Liquidity Layer). No fee and no cooldown on this leg — the
6292
+ * difference from `instant` is purely the size cap, and the
6293
+ * difference from `fee-or-queued` is that exceeding it costs
6294
+ * nothing extra, it simply cannot be done this block.
6295
+ * - `fee-or-queued` — the protocol's live instant-exit inventory,
6296
+ * clamped to `totalAssets`; `0` when the instant leg is disabled.
6297
+ * This is a **gross** figure: pulling it out instantly nets
6298
+ * `withdrawFeeBps` less (see that field). The queued leg is not
6299
+ * inventory-capped and pays at par, so `liquidity` is not a cap on
6300
+ * what the vault can ultimately return.
6301
+ * - `fixed-cooldown` / `queued` / `request-based` — `0`; these
6302
+ * require a waiting period.
6303
+ * A cooldown vault's underlying may still be redeemable after the
6304
+ * wait — this field is the *right now* figure, matching the
6305
+ * cross-provider `liquidity` semantic. */
4631
6306
  liquidity: string;
4632
6307
  /** Human-formatted withdrawable liquidity. */
4633
6308
  liquidityFormatted: number;
4634
6309
  /** Withdrawable liquidity in USD. */
4635
6310
  liquidityUsd: number;
6311
+ /**
6312
+ * `liquidity / totalAssets`, clamped to `0…1` — the share of the vault a
6313
+ * holder could exit **this block**. `1 − instantLiquidityRatio` is the
6314
+ * share that must wait, so this is the vault's **lockup indicator**.
6315
+ *
6316
+ * Deliberately *not* called `utilization`. For a lending vault
6317
+ * utilization is `borrowed / supplied`, read from a debt accumulator;
6318
+ * none of these protocols expose one (Native's CreditVault and NTLP have
6319
+ * no debt getter at all, and the CreditVault commingles market-maker
6320
+ * collateral with pool inventory, so its balance can exceed the pool).
6321
+ * What this measures is exit **coverage**, which is the quantity that
6322
+ * actually predicts lockup — and unlike utilization it stays meaningful
6323
+ * for cooldown vaults that have no borrow side whatsoever.
6324
+ *
6325
+ * Reads per mode:
6326
+ * - `instant` → always `1` (fully liquid by construction).
6327
+ * - `fee-or-queued` → the live CreditVault coverage; the observed spread
6328
+ * across Native pools is the full `0…1` range, so it carries real
6329
+ * information (BNB `wNLP-T4B` sits near `0`, Ethereum `wNLP-USDC` at
6330
+ * `1`). Below `1` the remainder is not lost, just queued.
6331
+ * - `fixed-cooldown` / `queued` / `request-based` → always `0`; nothing
6332
+ * is redeemable without waiting.
6333
+ *
6334
+ * An empty vault reports `1` — there is nothing to be locked up.
6335
+ */
6336
+ instantLiquidityRatio: number;
4636
6337
  }
4637
6338
  /**
4638
6339
  * Full parsed payload: per-share-token-address map.
@@ -5852,7 +7553,7 @@ interface VaultLookupEntry {
5852
7553
  declare function buildVaultLookup(data: VaultPublicDataAll): Map<string, VaultLookupEntry>;
5853
7554
 
5854
7555
  /** Supported ERC-4626 vault providers. */
5855
- type VaultProvider = 'fluid' | 'gearbox' | 'morpho' | 'lista' | 'silo' | 'euler-earn' | 'lst' | 'savings' | 'lagoon' | 'aave-earn' | 'upshift' | 'yearn' | 'hypercore' | 'gmx';
7556
+ type VaultProvider = 'fluid' | 'gearbox' | 'morpho' | 'lista' | 'silo' | 'euler-earn' | 'termmax' | 'lst' | 'savings' | 'lagoon' | 'aave-earn' | 'upshift' | 'yearn' | 'hypercore' | 'gmx';
5856
7557
  /**
5857
7558
  * Per-provider payload returned by `getVaultPublicDataAll`. Each entry is
5858
7559
  * present only when the matching provider was requested AND its fetch
@@ -5869,6 +7570,7 @@ interface VaultPublicDataAll {
5869
7570
  lista?: MorphoVaults;
5870
7571
  silo?: SiloVaults;
5871
7572
  'euler-earn'?: EulerEarnVaults;
7573
+ termmax?: TermMaxVaults;
5872
7574
  lst?: LstShareTokens;
5873
7575
  savings?: SavingsVaults;
5874
7576
  lagoon?: LagoonVaults;
@@ -6564,8 +8266,18 @@ declare function parseRawRpcBatchResponses(batches: RawRpcBatch[], batchResponse
6564
8266
  * Parses multicall3 aggregate3 responses
6565
8267
  * The response contains an array of {success, returnData} tuples
6566
8268
  * Each returnData needs to be decoded using the original call's ABI
8269
+ *
8270
+ * `permanentFailures`, when supplied, is filled with the indices of calls that
8271
+ * failed DETERMINISTICALLY. This path can tell them apart with certainty, which
8272
+ * the viem path can only infer: if the batch response itself came back, the
8273
+ * transport worked, so a `success: false` entry inside it is a revert — the
8274
+ * chain's answer, not a lost read. Only a batch-level error is a lost read.
8275
+ *
8276
+ * The distinction matters downstream: a cross-margin lender voids its whole set
8277
+ * on a lost read, and without this a single always-reverting market would void
8278
+ * a perfectly good position on every request.
6567
8279
  */
6568
- declare function parseMulticallRpcResponses(responses: RawRpcResponse[], batches: MulticallRpcBatch[], allowFailure?: boolean): any[];
8280
+ declare function parseMulticallRpcResponses(responses: RawRpcResponse[], batches: MulticallRpcBatch[], allowFailure?: boolean, permanentFailures?: Set<number>): any[];
6569
8281
 
6570
8282
  type TokenEntry = {
6571
8283
  chainId: string;
@@ -6658,4 +8370,1189 @@ interface FetchTokenBalancesOptions {
6658
8370
  */
6659
8371
  declare function fetchTokenBalances(chainId: string, account: string, tokens: string[], options?: FetchTokenBalancesOptions): Promise<TokenBalanceResult>;
6660
8372
 
6661
- export { type AaveMetadata, type AaveV2Public, type AaveV2UserReserveResponse, type AaveV3Public, type AaveV3UserReserveResponse, type AdditionalYields, ApiBookSource, type AprData, type BalanceData, type BaseLendingPosition, type BasicReserveResponse, type ChainDiagnostic, type ChainLinkResponse, type ChainQuery, type ChainSummary, type CompoundV2Metadata, type CompoundV3Public, type CompoundV3UserReserveResponse, type ConfigEntry, type CoreValidators, DEFAULT_MIDNIGHT_API, DEFAULT_OUTLIER_GUARD, DEFAULT_STALE_REJECT_SECONDS, type Denomination, type DepthMap, EMPTY_BALANCE, type EModeAssets, type EModeData, type EModeResult, type EulerEarnVault, type EulerEarnVaults, type EulerV2Metadata, type ExactlyFixedPool, type ExactlyFixedPosition, type ExactlyMarketAccount, type ExactlyMarketsRaw, type ExactlyUserFixedPosition, type ExtraValidationCall, type FeedObservation, type FeedStat, type FeedStatsMap, type FeedTimestampMap, type FetchListaVaultsFromChainOptions, type FetchMorphoVaultsFromChainOptions, type FetchOraclePricesOptions, type FetchTokenBalancesOptions, type FlattenPriorityConfig, type FluidFToken, type FluidFTokens, type FullLenderRewardsMap, GMX_API_HOSTS, GMX_READ_CONTRACTS, GMX_SUPPORTED_CHAINS, type GenericCurrency, type GenericTokenList, type GetVaultPublicDataAllOptions, type GmxExecutionFees, type GmxPendingDeposit, type GmxPendingWithdrawal, type GmxReadContracts, type GmxUserBalance, type GmxUserPositions, type GmxUserPositionsOptions, type GmxVault, type GmxVaultKind, type GmxVaults, type GmxVaultsFetchOptions, type GroupAccumulator, HYPERCORE_VAULT_REGISTRY, type HypercoreLockStatus, type HypercoreUserPositionsOptions, type HypercoreVault, type HypercoreVaultPosition, type HypercoreVaultRegistryEntry, type HypercoreVaults, type HypercoreVaultsFetchOptions, IDLE_MARKET_ID, INTERFACE_IDS, type InitMetadata, type InitPublic, type InitUserReserveResponse, type InterfaceKind, LAGOON_API_URL, LAGOON_CHAIN_IDS, type LagoonApiVault, type LagoonSyncMode, type LagoonVault, type LagoonVaults, type LenderConfigData, type LenderConfigMap, type LenderCrossPoolMeta, type LenderData, type LenderDataEntry, type LenderInfo, type LenderInfoMap, type LenderPublicBase, type LenderRewardsMap, type LenderSummary, type LenderToLenderCrossPoolMeta, type LenderTotalAmounts, type LenderUserQuery, type LenderUserResponse, type LenderYieldComplete, type LenderYields$1 as LenderYields, LendingMode, type LiquityBranchRaw, type LiquityDiscoveredTrove, type LiquityDiscovery, type LiquityMarketsRaw, type LiquitySpInfo, type LiquityTroveInfo, type ListaMarketOverrides, type LoopPostTradeMetrics, type LstDelegation, type LstDelegationKind, type LstValidator, type LstWithdrawalFetchOptions, type LstWithdrawalRegistryEntry, type LstWithdrawalRequest, type LstWithdrawalRequestsByLst, type LstWithdrawalStatus, MORPHO_LENS, MaxParamThresholds, type MergedUserData, type MidnightBook, type MidnightBookLevel, type MidnightBookSource, type ModeBase, type MorphoMarketOverrides, type MorphoSubgraphProxyConfig, type MorphoUserMarketBalance, type MorphoUserReserveResponse, type MorphoVault, type MorphoVaults, type MulticallRpcBatch, type NumberMap, type OracleDiagnostics, type OraclePriceEntry, type OraclePricesResult, type OutlierGuardConfig, type ParsedBalanceData, type ParsedResponse, type ParsedUserBalance, type PermissionParams, type PoolData, type PoolWithMeta, type PortfolioSummary, type PortfolioTotals, type PostTradeMetrics, type PreparedCall, type PreparedMergedMulticallParams, type PreparedMergedRpcCalls, type PreparedTokenBalanceRpcCalls, type PreparedUserDataRpcCalls, type PriceDerivation, type PriceSelection, type ProtocolParams, type ProviderOptions, type RawRpcBatch, type RawRpcCall, type RawRpcResponse, type RiverMarketRaw, type RiverMarketsRaw, type RiverPositionInfo, STABLECOIN_SYMBOLS, STCELO_MANAGER_ADDRESS, type SelectPricesOptions, type SiloVault, type SiloVaults, type StCeloValidatorGroup, type StaleFeedEntry, type StructuredOraclePrices, type SubAccountSummary, type SumerPositionInput, type SummaryAprData, type SummaryBalanceData, TELLER_CALLS_PER_BID, type TellerDiscoveredBid, type TellerDiscovery, type TellerMarketsRaw, type TellerPoolRaw, type TermAuctionOrder, type TermAuctionOrders, type TermBookSource, type TermBookTop, type TermListing, type TermMarketRaw, TermSubgraphSource, type TokenApprovalMeta, type TokenApprovalParams, type TokenBalanceEntry, type TokenBalanceQuery, type TokenBalanceResult, type TokenEntry, type TokenList, type TokenListInput, type TrackerDiagnostic, UPSHIFT_CHAIN_IDS, UPSHIFT_VAULTS_URL, type USDPriceMap, type UpshiftApiAsset, type UpshiftApiVault, type UpshiftVault, type UpshiftVaults, type UserApr, type UserConfig, type UserData, type UserDataResult, type UserLendingPosition, VAULT_SHARE_PRICE_PROBE, VOLATILE_VAULT_OVERRIDES, type VaultAprResult, type VaultClassification, type VaultClassificationFields, type VaultLookupEntry, type VaultMarketExposure, type VaultProvider, type VaultPublicDataAll, type VaultPublicDataResult, type VaultYieldSeries, type VaultYieldSnapshot, type YDaemonVault, YEARN_CHAIN_IDS, YEARN_YDAEMON_BASE, type YearnVault, type YearnVaultKind, type YearnVaults, type YieldProfile, accountDepositListKey, accountWithdrawalListKey, appendSnapshot, applyPositionDelta, attachPricesToFlashLiquidity, buildFluidFTokensCall, buildLiquityUserCall, buildLoopResult, buildMorphoTypeCall, buildMorphoTypeUserCallWithLens, buildPortfolioTotals, buildSumerAccumulators, buildSummaries, buildTellerUserCall, buildVaultLookup, calculateLeverage, calculateNetApr, calculateOverallNetApr, calculateWeightedAverage, classifyFreshness, classifyReliability, classifyVault, collectFeedObservations, computeBorrowDelta, computeCloseTradeDeltas, computeCollateralSwapDeltas, computeDebtSwapDeltas, computeDepositDelta, computeEModeAnalysis, computeOpenTradeDeltas, computePostTradeMetrics, computeRepayDelta, computeSumerBorrowDelta, computeSumerDepositDelta, computeSumerRepayDelta, computeSumerWaterfall, computeSumerWithdrawDelta, computeVaultApr, computeWithdrawDelta, computeZapTradeDeltas, consensusReference, convertExactlyMarketsToResponse, convertLenderUserDataResult, convertLiquityMarketsToResponse, convertRiverMarketsToResponse, convertTellerMarketsToResponse, convertTermMarketsToResponse, createMarketUid, createMidnightBookSource, createMulticallRpcCall, createRawRpcCalls, createTermBookSource, decodeListaMarkets, decodeMarkets, decodePackedListaUserDataset, decodePackedMorphoUserDataset, detectInterfaceKinds, encodeBalanceFetcherCalldata, exactlyLenderKey, exactlyMarketFromLenderKey, exactlyPairLtv, exactlyPenaltyRateToAprPercent, exactlyWadRateToPercent, feedKeyOf, feedStatKey, fetchDolomiteAccountNumbers, fetchEulerEarnVaults, fetchEulerEarnVaultsFromSubgraph, fetchEulerSubAccountIndexes, fetchExactlyMarkets, fetchFlashLiquidityForChain, fetchFluidFTokens, fetchGeneralYields, fetchGeneralYieldsByMarketUid, fetchGmxExecutionFees, fetchGmxTickerPrices, fetchGmxVaults, fetchHypercoreVaults, fetchLagoonApiVaults, fetchLagoonVaults, fetchLiquityMarkets, fetchListaVaultsFromChain, fetchMorphoUserBalances, fetchMorphoUserPositionMarkets, fetchMorphoVaults, fetchMorphoVaultsFromApi, fetchMorphoVaultsFromChain, fetchOraclePrices, fetchPendlePrices, fetchRiverMarkets, fetchSiloVaults, fetchTellerMarkets, fetchTermMarkets, fetchTokenBalances, fetchTokenMetadata, fetchUpshiftApiVaults, fetchUpshiftVaults, fetchYearnApiVaults, fetchYearnVaults, filterActiveLenders, filterLendersByProtocol, fuseLenderData, generateLendingPools, getAavesForChain, getAssetConfig, getBalanceForMarketUid, getBorrowCapacity, getCachedLiquityTroves, getCachedTellerBids, getCoreValidators, getFluidFTokensConverter, getGmxApiHost, getGmxReadContracts, getGmxUserPositions, getHealthFactor, getHypercoreUserPositions, getHypercoreVaultRegistry, getLenderAssets, getLenderPublicData, getLenderPublicDataAll, getLenderPublicDataViaApi, getLenderUserDataMulti, getLenderUserDataResult, getLendersForChain, getLstDelegation, getLstValidators, getLstWithdrawalRegistry, getLstWithdrawalRequests, getMaxAmountClose, getMaxAmountCollateralSwap, getMaxAmountDebtSwap, getMaxAmountOpen, getMergedUserData, getMorphoTypeMarketConverter, getResolvedDolomiteAccountNumbers, getStCeloValidatorGroups, getSubAccountAddress, getSubAccountIndex, getVaultPublicDataAll, getVaultWithdrawalRequests, hasEulerEarnVaultSubgraph, hasLagoonVaults, hasMorphoPositionIndex, hasMorphoUserApi, hasMorphoUserSubgraph, hasUpshiftVaults, hasYearnVaults, isStablecoinSymbol, isYearnV3, keysFromMaps, lenderFamily, liquityCandidateTroveIds, liquityKeyParts, liquityLenderKey, multicall3Abi, nanTo, needsLenderApproval, needsTokenApproval, noOpResult, normalizeToBytes, parseBalanceFetcherResult, parseMergedResult, parseMulticallRpcResponses, parseRawRpcBatchResponses, parseRawRpcResponses, parseTokenBalanceResult, positivePart, prepareLenderUserDataRpcCalls, prepareMergedMulticallParams, prepareMergedRpcCalls, prepareMulticallInputs, prepareTokenBalanceRpcCalls, priceGlvVaults, priceGmMarkets, probeAaveFeedTimestamps, pruneFeedStats, readVaultSharePrices, rejectOutliers as rejectPriceOutliers, resolveDerivation, resolveStCeloDepositGroup, riverKeyParts, riverLenderKey, selectAssetGroupPrices, stampVaultClassification, tellerBpsToPercent, tellerImpliedLtv, tellerLenderKey, tellerPoolFromLenderKey, termLenderKey, tickToAprNumber, tickToPrice, unflattenLenderData, updateFeedStats };
8373
+ /**
8374
+ * Term sheets — a structured, per-`marketUid` description of every lend and
8375
+ * borrow offer we serve.
8376
+ *
8377
+ * One shape for pool lenders, fixed-term lenders, CDPs and vaults, so an
8378
+ * integrator reads ONE object instead of ~15 protocol-specific fields plus a
8379
+ * per-lender mental model. See [TERM_SHEET_PLAN.md](../../../../TERM_SHEET_PLAN.md)
8380
+ * for the design rationale and the per-lender coverage matrix.
8381
+ *
8382
+ * ## Conventions that hold everywhere in this file
8383
+ *
8384
+ * - **Rates are nominal APR in PERCENT** (`3.85` = 3.85 %/yr), never a
8385
+ * fraction and never an APY. This is the package-wide convention.
8386
+ * - **Factors are fractions** (`0.85` = 85 % LTV) — matching `LenderConfigData`.
8387
+ * - **Durations are SECONDS**, timestamps are **unix seconds**.
8388
+ * - **Raw amounts are decimal strings** in base units; human amounts are
8389
+ * `number`.
8390
+ * - Every string union is deliberately OPEN (`| (string & {})`) so a new
8391
+ * lender can introduce a member without breaking a consumer's exhaustive
8392
+ * switch. Consumers MUST have a `default` branch and fall back to
8393
+ * `info.headline`.
8394
+ */
8395
+ /** Nominal APR in percent (`3.85` = 3.85 %/yr). Never a fraction, never APY. */
8396
+ type AprPercent = number;
8397
+ /**
8398
+ * Open-enum helper. `Open<'a' | 'b'>` keeps autocomplete for the known members
8399
+ * while still accepting any string, so adding a member later is an ADDITIVE
8400
+ * change rather than a breaking one.
8401
+ */
8402
+ type Open<T extends string> = T | (string & {});
8403
+ /** Compact token reference — enough to render without a second lookup. */
8404
+ interface TermAssetRef {
8405
+ chainId: string;
8406
+ /** Lowercased contract address. */
8407
+ address: string;
8408
+ symbol?: string;
8409
+ name?: string;
8410
+ decimals?: number;
8411
+ assetGroup?: string;
8412
+ logoURI?: string;
8413
+ }
8414
+ /**
8415
+ * Machine tags for filtering/faceting. DERIVED from the structured fields in
8416
+ * `tags.ts` — never hand-written per lender, so they cannot drift from the
8417
+ * numbers they summarize.
8418
+ */
8419
+ type TermTag = Open<'fixed-rate' | 'variable-rate' | 'user-set-rate' | 'zero-interest' | 'prepaid-interest' | 'nav-accrual' | 'has-maturity' | 'perpetual' | 'rolling-duration' | 'static-debt' | 'accruing-debt' | 'time-liquidation' | 'price-liquidation' | 'redeemable' | 'no-liquidation' | 'full-collateral-seizure' | 'early-exit-free' | 'early-exit-penalty' | 'early-exit-discount' | 'exit-instant' | 'exit-capped' | 'exit-cooldown' | 'exit-queued' | 'exit-market-sale' | 'exit-may-be-impossible' | 'permissioned' | 'capped' | 'cap-full' | 'first-loss' | 'socialized-loss' | 'physical-delivery' | 'undercollateralized' | 'nav-attested' | 'immutable' | 'no-timelock' | 'eoa-controlled' | 'points-rewards' | 'oracle-flagged' | 'no-oracle'>;
8420
+ /** Human-facing copy for one side of a term sheet. */
8421
+ interface TermInfo {
8422
+ /**
8423
+ * ≤ ~100 chars, templated from live numbers, ready to render.
8424
+ * `"Fixed 4.12 % until 3 Sep 2026 · repay any time at face value"`.
8425
+ *
8426
+ * ALWAYS populated — it is the graceful-degradation path for a consumer
8427
+ * that does not recognise a newer enum member.
8428
+ */
8429
+ headline: string;
8430
+ /** 1–3 sentences. Invariant prose lives on the profile; this interpolates
8431
+ * the market's own values. */
8432
+ description: string;
8433
+ /** Ready-to-display consequences, most important first. */
8434
+ implications?: string[];
8435
+ tags: TermTag[];
8436
+ }
8437
+ type RateKind = Open<
8438
+ /** Utilization IRM (Aave, Compound, Morpho, Silo, Euler, Fluid…). */
8439
+ 'variable-curve'
8440
+ /** Governance-set with no curve (USDD stability fee, Spark `vsr`). */
8441
+ | 'variable-managed'
8442
+ /** Borrower picks the rate (Liquity family). */
8443
+ | 'user-set'
8444
+ /** Locked for a maturity (Exactly, Midnight, Term, TermMax, Teller, Lista). */
8445
+ | 'fixed-term'
8446
+ /** Locked, no maturity. Reserved — nothing uses it today. */
8447
+ | 'fixed-open'
8448
+ /** No ongoing rate at all (River); cost is a one-off fee. */
8449
+ | 'zero-interest'
8450
+ /** Interest prepaid in a separate token (Inverse DBR). */
8451
+ | 'prepaid'
8452
+ /** Share price tracks an attested NAV (Re, Apyx, USPC). */
8453
+ | 'nav-accrual'
8454
+ /** Pure collateral leg — no yield. */
8455
+ | 'none'>;
8456
+ /** One reward program. Identity matters: points are not a bankable APR. */
8457
+ interface RewardTerm {
8458
+ /** Absent for points programs — that absence IS the signal. */
8459
+ asset?: TermAssetRef;
8460
+ kind: Open<'token' | 'points' | 'unknown'>;
8461
+ apr: AprPercent;
8462
+ side: 'supply' | 'borrow';
8463
+ /** How it is realized — decides whether the APR is actually bankable. */
8464
+ claim: Open<'accrual' | 'merkl' | 'manual' | 'none'>;
8465
+ /** Program end, where known. An APR with two weeks left is not an APR. */
8466
+ endsAt?: number;
8467
+ /**
8468
+ * `true` ⇒ not priceable (points). MUST be shown separately and is
8469
+ * deliberately EXCLUDED from `RateTerms.aprTotal`.
8470
+ */
8471
+ indicative?: boolean;
8472
+ }
8473
+ /** One entry in a fixed-term rate menu. Mirrors `MarketTermEntry`. */
8474
+ interface RateMenuEntry {
8475
+ /** LENDER-SPECIFIC: Exactly/TermMax = unix maturity, Teller = duration
8476
+ * seconds, Lista = broker product id, Midnight/Term = `0`. */
8477
+ termId: number;
8478
+ durationSecs: number;
8479
+ durationDays: number;
8480
+ /** Borrow APR at this term. */
8481
+ apr: AprPercent;
8482
+ /** Lend APR at this term, where the lender quotes both sides. */
8483
+ depositApr?: AprPercent;
8484
+ /** Borrowable liquidity at this term, human units. */
8485
+ available?: number;
8486
+ }
8487
+ interface RateTerms {
8488
+ kind: RateKind;
8489
+ /** Base rate only — no rewards, no intrinsic yield. */
8490
+ apr: AprPercent;
8491
+ components: {
8492
+ base: AprPercent;
8493
+ /** Priceable rewards only. */
8494
+ rewards?: AprPercent;
8495
+ /** Underlying/LST yield the asset earns by itself. */
8496
+ intrinsic?: AprPercent;
8497
+ };
8498
+ /** `base + priceable rewards + intrinsic` — the headline number. */
8499
+ aprTotal: AprPercent;
8500
+ /** Carried explicitly even though it has one value today: a silent
8501
+ * APR→APY change would be the classic undetectable break. */
8502
+ basis: 'apr-nominal';
8503
+ compounding: Open<'per-second' | 'per-block' | 'none' | 'unknown'>;
8504
+ source: Open<'utilization-curve' | 'orderbook' | 'auction' | 'governance' | 'borrower' | 'oracle' | 'api' | 'derived'>;
8505
+ /** Is the rate locked for the life of the position? */
8506
+ isLocked: boolean;
8507
+ /** Protocol-enforced bounds (Liquity min/max, Morpho rateCap/rateFloor). */
8508
+ minApr?: AprPercent;
8509
+ maxApr?: AprPercent;
8510
+ /**
8511
+ * `kind: 'user-set'` only — the contract a RATE SETTER needs.
8512
+ *
8513
+ * On the Liquity family the borrower picks their own rate at open, and the
8514
+ * choice is not cosmetic: it decides where you sit in the redemption queue,
8515
+ * so the cheapest rate is also the one that gets redeemed first. A borrow UI
8516
+ * that offers no input either cannot build the transaction or silently
8517
+ * accepts a default the user never saw.
8518
+ *
8519
+ * `default` is what the protocol applies when the caller omits a rate (the
8520
+ * branch average), so a UI can pre-fill it and stay consistent with what the
8521
+ * action would have done anyway.
8522
+ */
8523
+ userSet?: {
8524
+ /** Is a rate part of the open call at all? */
8525
+ required: boolean;
8526
+ /** Inclusive bounds, PERCENT. Outside these the open reverts. */
8527
+ min?: AprPercent;
8528
+ max?: AprPercent;
8529
+ /** Applied when the caller omits one — the branch average. */
8530
+ default?: AprPercent;
8531
+ /** Can it be changed after opening (Liquity `set-rate`)? */
8532
+ adjustable: boolean;
8533
+ /**
8534
+ * Changing the rate re-charges the upfront fee — so it is not free, and a
8535
+ * UI that presents it as a slider should say so.
8536
+ */
8537
+ adjustmentCostNote?: string;
8538
+ /** Seconds before the rate may be adjusted again without penalty. */
8539
+ adjustmentCooldownSecs?: number;
8540
+ };
8541
+ /** Per-program reward detail — token identity, claim path, end date. */
8542
+ rewards?: RewardTerm[];
8543
+ /** Rate menu when the market offers several terms at once. */
8544
+ menu?: RateMenuEntry[];
8545
+ /** Size the quote is valid for, when the rate is depth-dependent. */
8546
+ quote?: {
8547
+ assets: number;
8548
+ basis: Open<'marginal' | 'average'>;
8549
+ };
8550
+ lastChangedAt?: number;
8551
+ }
8552
+ interface MaturityTerms {
8553
+ kind: Open<'perpetual' | 'fixed-date' | 'rolling-duration'>;
8554
+ /** unix seconds; `kind: 'fixed-date'`. */
8555
+ maturity?: number;
8556
+ /** ISO-8601 mirror so consumers need not re-format. */
8557
+ maturityIso?: string;
8558
+ /** Snapshot — derive live from `maturity` for a countdown. */
8559
+ secondsToMaturity?: number;
8560
+ /** `kind: 'rolling-duration'` (Teller, Lista broker). */
8561
+ minDurationSecs?: number;
8562
+ maxDurationSecs?: number;
8563
+ /**
8564
+ * What happens at/after maturity if NOBODY acts. The field that most
8565
+ * surprises users — outcomes range from "interest simply stops" to
8566
+ * "liquidated within five minutes, losing all collateral".
8567
+ */
8568
+ atMaturity?: Open<'stops-earning' | 'penalty-accrues' | 'liquidatable' | 'default-seizure' | 'physical-delivery' | 'refinanced' | 'auto-roll' | 'none'>;
8569
+ /** Grace window before `atMaturity` bites (Teller ~300 s, TermMax 7200 s). */
8570
+ graceSecs?: number;
8571
+ }
8572
+ type FeeWhen = Open<'entry' | 'ongoing' | 'exit' | 'late' | 'liquidation' | 'performance'>;
8573
+ /**
8574
+ * One charge, in a shape general enough that a NEW fee is data rather than a
8575
+ * schema change. Replaces the current scatter: `originationFee`,
8576
+ * `withdrawFeeBps`, `rates.fee`, `fixedTerm.fees.*`, `river.mintFeeRate`,
8577
+ * `liquity.gasCompensation`.
8578
+ */
8579
+ interface FeeTerm {
8580
+ /** Stable slug — the join key for UI copy and filtering. */
8581
+ id: Open<'origination' | 'late-penalty' | 'early-repay-penalty' | 'early-repay-discount' | 'continuous' | 'settlement' | 'instant-exit' | 'performance' | 'reserve-factor' | 'gas-compensation' | 'redemption' | 'claim' | 'liquidation-bonus'>;
8582
+ /** Human label — lets an unknown `id` still render correctly. */
8583
+ label: string;
8584
+ when: FeeWhen;
8585
+ unit: Open<'apr-percent' | 'percent' | 'bps' | 'absolute'>;
8586
+ basis: Open<'principal' | 'face-value' | 'yield' | 'collateral' | 'shares' | 'debt-repaid'>;
8587
+ /**
8588
+ * A NEGATIVE value is legal and means a REBATE (Exactly's early-repay
8589
+ * discount). Sign is load-bearing — never take an absolute value.
8590
+ */
8591
+ value: number;
8592
+ payee?: Open<'protocol' | 'lenders' | 'liquidator' | 'curator' | 'gas-refund'>;
8593
+ /** Governance-mutable ⇒ this is a snapshot; re-verify before quoting. */
8594
+ mutable?: boolean;
8595
+ /** Only resolvable at action time (Exactly discount, TermMax curve price). */
8596
+ indicative?: boolean;
8597
+ /** Decaying/scheduled fees (Apyx: 3.40 % → 0 over 20 days). */
8598
+ schedule?: {
8599
+ afterSecs: number;
8600
+ value: number;
8601
+ }[];
8602
+ description?: string;
8603
+ }
8604
+ /** Superset of `SavingsWithdrawalMode` + `LstWithdrawalMode`, plus the two
8605
+ * lending-side exits neither covers. */
8606
+ type SupplyExitMode = Open<'instant' | 'instant-capped' | 'instant-or-queued' | 'fee-or-queued' | 'fixed-cooldown' | 'queued' | 'request-based'
8607
+ /** Sell the instrument on a book (Term, TermMax, Midnight). */
8608
+ | 'market-sale'
8609
+ /** No early exit at all. */
8610
+ | 'at-maturity' | 'off-chain' | 'dex-only'>;
8611
+ interface SupplyExitTerms {
8612
+ mode: SupplyExitMode;
8613
+ /** Coarse alias — identical semantics to `VaultClassificationFields.redemptionType`. */
8614
+ settlement: Open<'sync' | 'async'>;
8615
+ cooldownSecs?: number;
8616
+ /** Claim-window constraints (Apyx: blocked 3 d, free at 20 d). */
8617
+ claimWindow?: {
8618
+ earliestSecs?: number;
8619
+ freeAfterSecs?: number;
8620
+ };
8621
+ /** What can actually leave right now. */
8622
+ liquidity?: {
8623
+ assets: number;
8624
+ assetsUsd?: number;
8625
+ ratio?: number;
8626
+ };
8627
+ partialAllowed: boolean;
8628
+ /**
8629
+ * Does exiting early cost an UNKNOWN amount?
8630
+ * `none` par · `haircut-formula` deterministic discount ·
8631
+ * `market-price` you sell into a book · `may-be-impossible` the book can be
8632
+ * empty.
8633
+ */
8634
+ priceRisk: Open<'none' | 'haircut-formula' | 'market-price' | 'may-be-impossible'>;
8635
+ cancellable?: boolean;
8636
+ /** The `when: 'exit' | 'performance'` subset of the side's fees. */
8637
+ fees: FeeTerm[];
8638
+ }
8639
+ interface BorrowExitTerms {
8640
+ /** Three signs exist across our lenders: `discount` is a REBATE (Exactly). */
8641
+ earlyRepay: Open<'free' | 'discount' | 'penalty' | 'market-price' | 'not-allowed'>;
8642
+ atMaturityCost: Open<'face' | 'accrued'>;
8643
+ lateBehaviour: Open<'penalty-accrues' | 'liquidatable' | 'default-seizure' | 'refinanced' | 'none'>;
8644
+ partialAllowed: boolean;
8645
+ /** Dust floor — Liquity `minDebt`, Morpho/Lista `minLoan`. Raw base units. */
8646
+ minDebt?: string;
8647
+ /** Over-repay REVERTS (Midnight `uint128` underflow) — a real footgun. */
8648
+ overRepayReverts?: boolean;
8649
+ fees: FeeTerm[];
8650
+ }
8651
+ /** One named penalty, for models where a single number is not enough. */
8652
+ interface LiquidationPenaltyTerm {
8653
+ /** Stable slug: 'stability-pool' | 'redistribution' | 'hard' | 'protocol-fee'. */
8654
+ id: Open<string>;
8655
+ label: string;
8656
+ /** Fraction of the repaid debt (0.05 = 5 %). */
8657
+ value: number;
8658
+ description?: string;
8659
+ }
8660
+ interface LiquidationTerms {
8661
+ /**
8662
+ * HOW liquidation happens mechanically — distinct from WHAT triggers it.
8663
+ *
8664
+ * Without this, every market reads as the ordinary "a liquidator repays your
8665
+ * debt and seizes collateral plus a bonus" model, which is wrong for four of
8666
+ * the lenders we serve and materially misleading for one:
8667
+ *
8668
+ * - `soft-band` Collateral is converted GRADUALLY inside the market's
8669
+ * own AMM as the price enters a band range — with NO
8670
+ * penalty and REVERSIBLY (Curve LlamaLend). There is no
8671
+ * single liquidation price at all.
8672
+ * - `stability-pool` A pool absorbs the debt first and only falls back to
8673
+ * redistributing it across other borrowers (Liquity,
8674
+ * River) — the two paths carry DIFFERENT penalties.
8675
+ * - `auction` Price is discovered by a Dutch auction rather than an
8676
+ * oracle (Frankencoin challenges).
8677
+ * - `default-seizure` The whole escrow is forfeit on a missed payment
8678
+ * (Teller).
8679
+ * - `delivery` Unpaid collateral is delivered to lenders (TermMax).
8680
+ */
8681
+ model?: Open<'repay-seize' | 'soft-band' | 'stability-pool' | 'auction' | 'default-seizure' | 'delivery' | 'none'>;
8682
+ /** Who ends up holding the seized collateral. */
8683
+ absorber?: Open<'liquidator' | 'stability-pool' | 'other-borrowers' | 'lenders' | 'amm'>;
8684
+ /**
8685
+ * Can the position come BACK out of liquidation if the price recovers?
8686
+ * True only for `soft-band`: conversion is continuous and reverses, so
8687
+ * "being liquidated" is not terminal the way it is everywhere else.
8688
+ */
8689
+ reversible?: boolean;
8690
+ /**
8691
+ * Named penalties when `penalty` alone cannot express the model — Liquity
8692
+ * charges a different rate depending on whether the Stability Pool absorbs
8693
+ * the debt or it is redistributed. `penalty` stays the headline (worst or
8694
+ * primary) so a naive consumer is still correct.
8695
+ */
8696
+ penalties?: LiquidationPenaltyTerm[];
8697
+ /** Window between becoming liquidatable and the terminal outcome (TermMax). */
8698
+ windowSecs?: number;
8699
+ /** Only allowlisted keepers may liquidate. Absent/false ⇒ permissionless. */
8700
+ permissioned?: boolean;
8701
+ /** Where a shortfall goes when the collateral does not cover the debt. */
8702
+ badDebt?: Open<'socialized' | 'redistributed' | 'insurance-fund' | 'protocol-absorbed' | 'unknown'>;
8703
+ /**
8704
+ * `soft-band` only: the collateral factor is a FUNCTION of the band count
8705
+ * chosen at open (`{ [N]: ltv }` — 0.991 at N=4 vs 0.886 at N=50). `ltv`
8706
+ * reports the market's default N; a consumer quoting a different N must read
8707
+ * this curve instead.
8708
+ */
8709
+ bandLtv?: Record<string, number>;
8710
+ /** Band count `ltv` / `liquidationLtv` were computed at. */
8711
+ defaultBands?: number;
8712
+ /**
8713
+ * Aave-style escalation: the close factor rises to 1 once health falls below
8714
+ * this. Without it, `closeFactor: 0.5` understates the worst case.
8715
+ */
8716
+ fullCloseBelowHealthFactor?: number;
8717
+ trigger: Open<'price' | 'time' | 'price-and-time' | 'redemption' | 'none'>;
8718
+ /** Max LTV at open. */
8719
+ ltv?: number;
8720
+ /** Threshold at which liquidation becomes possible. */
8721
+ liquidationLtv?: number;
8722
+ /** Fraction of repaid debt paid to the liquidator on top of par. */
8723
+ penalty: number;
8724
+ closeFactor: number;
8725
+ targetHealthFactor?: number;
8726
+ /**
8727
+ * `full-collateral` is the Teller case: the liquidator takes the ENTIRE
8728
+ * escrow, not a proportional slice — ~2× the debt at 50 % LTV.
8729
+ */
8730
+ seizure: Open<'proportional' | 'full-collateral'>;
8731
+ /** Liquity/River: collateral redeemable at par while perfectly healthy. */
8732
+ redeemable?: boolean;
8733
+ gracePeriodSecs?: number;
8734
+ }
8735
+ interface CounterpartyTerms {
8736
+ kind: Open<'pool' | 'orderbook' | 'auction' | 'broker' | 'cdp' | 'p2p' | 'vault-strategy' | 'off-chain-credit'>;
8737
+ address?: string;
8738
+ /** The trust question, one field. */
8739
+ solvency: Open<'overcollateralized' | 'tranched-senior' | 'tranched-junior' | 'undercollateralized' | 'nav-attested'>;
8740
+ socializedLoss?: boolean;
8741
+ curator?: string;
8742
+ }
8743
+ /** Origination window for auction-gated markets (Term Finance). */
8744
+ interface AuctionWindow {
8745
+ status: Open<'upcoming' | 'open' | 'revealing' | 'closed'>;
8746
+ canBorrow: boolean;
8747
+ canLend: boolean;
8748
+ secondsUntilClose?: number;
8749
+ id?: string;
8750
+ startTime?: number;
8751
+ revealTime?: number;
8752
+ endTime?: number;
8753
+ minBorrowAmount?: string;
8754
+ minLendAmount?: string;
8755
+ }
8756
+ /** What must be granted BEFORE an action can even be built. */
8757
+ type PermissionKind = Open<'token-approval' | 'lender-delegation' | 'manager-authorization' | 'eip712-permit' | 'nft-approval' | 'operator-set'
8758
+ /** Contract callers must be governance-approved (Inverse, Fraxlend). */
8759
+ | 'caller-allowlist'>;
8760
+ interface AvailabilityTerms {
8761
+ /** Gate CTAs on THIS and nothing else — it already folds in caps, freezes,
8762
+ * auction windows and gating. */
8763
+ canOpen: boolean;
8764
+ canClose: boolean;
8765
+ /** Machine-readable reason when `canOpen` is false. */
8766
+ blockedBy?: Open<'frozen' | 'paused' | 'cap-full' | 'auction-closed' | 'no-liquidity' | 'not-whitelisted' | 'shutdown' | 'disabled'>;
8767
+ gating: Open<'permissionless' | 'whitelist' | 'attestation' | 'kyc' | 'allowlist-contract'>;
8768
+ /** Absent ⇒ no window applies. NOT the same as `closed`. */
8769
+ window?: AuctionWindow;
8770
+ /**
8771
+ * Minimum position size to OPEN, in RAW base units of THIS side's asset.
8772
+ *
8773
+ * On the borrow side this is a minimum DEBT (Comet `baseBorrowMin`, Liquity
8774
+ * `minDebt`, Maker-fork `dust`). On the supply/collateral side it is a
8775
+ * minimum COLLATERAL — a genuinely different gate, and the only one some
8776
+ * lenders have: Frankencoin caps nothing on the debt but refuses a position
8777
+ * below `minimumCollateral`, and dropping under it closes the position
8778
+ * permanently.
8779
+ */
8780
+ minSize?: string;
8781
+ cap?: string;
8782
+ /** 0..1 — how full the cap is. */
8783
+ capUtilization?: number;
8784
+ requires?: PermissionKind[];
8785
+ }
8786
+ interface PositionConstraints {
8787
+ /** Aave isolation mode: capped debt, no collateral mixing. */
8788
+ isolation?: {
8789
+ enabled: boolean;
8790
+ debtCeiling?: string;
8791
+ ceilingUtilization?: number;
8792
+ };
8793
+ /** Borrowing this asset forbids borrowing any other in the same account. */
8794
+ siloedBorrowing?: boolean;
8795
+ crossMargin: boolean;
8796
+ /**
8797
+ * How a position is ADDRESSED. `loanId` already means five different things
8798
+ * across our lenders and `termId` six — making the model explicit is
8799
+ * cheaper than making every integrator rediscover it.
8800
+ */
8801
+ positionModel: Open<'account' | 'sub-account' | 'nft' | 'cdp-id' | 'loan-id' | 'escrow'>;
8802
+ /** One line saying what the id in `loanId`/`posId` actually IS here. */
8803
+ positionIdMeaning?: string;
8804
+ maxPositions?: number;
8805
+ /**
8806
+ * What this fetch actually SAW, as opposed to what the lender family
8807
+ * implies. Kept separate from `crossMargin` / `positionModel` on purpose:
8808
+ * those are family-level truths from the registry, these are per-fetch
8809
+ * observations, and collapsing the two would let a thin chain (a lender
8810
+ * listing one asset today) masquerade as a structural property.
8811
+ *
8812
+ * Useful precisely where they DISAGREE — e.g. Fluid is registered isolated
8813
+ * because its T1 vaults dominate, but its T2–T4 "smart" vaults genuinely
8814
+ * pool two collaterals; a `collateralAssetCount > 1` on a Fluid row is the
8815
+ * signal that this particular vault is one of them.
8816
+ */
8817
+ observed?: {
8818
+ /** Distinct collateral assets this market actually accepts, this fetch. */
8819
+ collateralAssetCount: number;
8820
+ /** Markets seen under this lender key on this chain, this fetch. */
8821
+ marketCount: number;
8822
+ /** Does the lender key fan out to many markets (registry answer)? */
8823
+ multiMarketKey: boolean;
8824
+ };
8825
+ }
8826
+ type AdminKind = Open<'EOA' | 'SAFE' | 'TIMELOCK' | 'GOVERNOR' | 'GOVERNANCE' | 'CUSTOM' | 'UNKNOWN'>;
8827
+ type GovernancePower = Open<'pause-deposits' | 'pause-withdrawals' | 'pause-borrows' | 'freeze-market' | 'change-ltv' | 'change-rate' | 'change-fees' | 'set-caps' | 'add-collateral' | 'swap-oracle' | 'upgrade-implementation' | 'blacklist' | 'seize-funds' | 'reprice-pending-redemptions'>;
8828
+ interface GovernanceTerms {
8829
+ mutability: Open<'immutable' | 'governed' | 'unknown'>;
8830
+ /** The governance root, after hopping proxy admins / timelock admins. */
8831
+ controller?: string;
8832
+ controllerKind?: AdminKind;
8833
+ safe?: {
8834
+ threshold: number;
8835
+ owners: number;
8836
+ };
8837
+ /**
8838
+ * Enforced delay in SECONDS between a parameter change being queued and it
8839
+ * taking effect — the holder's NOTICE PERIOD. `0`, or any `controllerKind`
8840
+ * that is not `TIMELOCK`, means a parameter can change in the very next
8841
+ * block with no warning.
8842
+ *
8843
+ * **This is NOT a withdrawal lock.** `SupplyExitTerms.cooldownSecs` is how
8844
+ * long YOUR money is stuck; this is how long you have to react to someone
8845
+ * else changing the deal. Never merge or sum the two.
8846
+ */
8847
+ timelockSecs?: number;
8848
+ timelockSource?: Open<'on-chain' | 'screened' | 'metadata'>;
8849
+ /**
8850
+ * The controller IS a timelock but its delay could not be read. Distinct
8851
+ * from `timelockSecs: undefined` on a non-timelock root, which genuinely
8852
+ * means "no notice period" — conflating the two would raise a false alarm
8853
+ * on the safest governance shape.
8854
+ */
8855
+ timelockUnknown?: boolean;
8856
+ tier?: Open<'low' | 'medium' | 'high' | 'unknown'>;
8857
+ score?: number;
8858
+ powers?: GovernancePower[];
8859
+ roles?: {
8860
+ owner?: string;
8861
+ curator?: string;
8862
+ guardian?: string;
8863
+ feeRecipient?: string;
8864
+ };
8865
+ /** Governance screens refresh far slower than rates — own timestamp. */
8866
+ asOfScreen?: number;
8867
+ }
8868
+ type OracleBand = Open<'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'>;
8869
+ interface OracleTerms {
8870
+ /**
8871
+ * `none` is MEANINGFUL, not missing data: Teller liquidates on TIME and has
8872
+ * no oracle and no health factor anywhere in its trigger.
8873
+ */
8874
+ kind: Open<'price-feed' | 'nav-attested' | 'none'>;
8875
+ /**
8876
+ * THE oracle for this `marketUid` — SINGULAR, lowercased. Verified across
8877
+ * the full oracle classification: 8,993 marketUids, 0 with more than one
8878
+ * address. Singularity holds because `marketUid` granularity is already
8879
+ * per-asset; the array in the legacy `oracleInfo.feeds[]` is an artifact of
8880
+ * hanging off the lender/params level instead.
8881
+ *
8882
+ * For a composite/cross adapter this is the ADAPTER — the address whose
8883
+ * failure or replacement moves this market's price.
8884
+ */
8885
+ address?: string;
8886
+ /** Underlying feeds when the adapter composes several and the classifier
8887
+ * decomposed them. `address` stays the single source of truth. */
8888
+ components?: string[];
8889
+ provider?: string;
8890
+ /** Decoded reported pair, e.g. `"ETH / USD"`. */
8891
+ priceDescription?: string;
8892
+ /** What it SHOULD report, e.g. `"WETH / USD"`. */
8893
+ intendedPair?: string;
8894
+ correctAsset?: boolean | null;
8895
+ correctNumeraire?: boolean | null;
8896
+ fixedRate?: boolean;
8897
+ score?: number;
8898
+ band?: OracleBand;
8899
+ flags?: string[];
8900
+ /** Can the oracle be swapped/upgraded, and by whom. On an otherwise
8901
+ * IMMUTABLE market this is the ONLY mutable trust vector. */
8902
+ mutability?: {
8903
+ mutable: boolean;
8904
+ kind: Open<'IMMUTABLE' | 'PROXY' | 'AUTHORITY' | 'UNKNOWN'>;
8905
+ controller?: string;
8906
+ controllerKind?: AdminKind;
8907
+ timelockSecs?: number;
8908
+ };
8909
+ heartbeatSecs?: number;
8910
+ lastUpdateAt?: number;
8911
+ }
8912
+ interface AssetQuality {
8913
+ /** 1 (best) … 5 (worst). */
8914
+ riskScore?: number;
8915
+ source?: Open<'whitelist' | 'default' | 'curated'>;
8916
+ /** On-chain USD liquidity available to absorb a liquidation — the number
8917
+ * that decides whether the LLTV is actually enforceable. */
8918
+ liquidityUsd?: number;
8919
+ /** The TOKEN CONTRACT's own governance, distinct from the market's. An
8920
+ * upgradeable, pausable collateral is a supplier risk even on an
8921
+ * immutable market. */
8922
+ governanceScore?: number;
8923
+ governanceLevel?: Open<'green' | 'amber' | 'red'>;
8924
+ upgradeable?: boolean;
8925
+ canPause?: boolean;
8926
+ adminKind?: AdminKind;
8927
+ }
8928
+ interface ExposureEntry {
8929
+ asset: TermAssetRef;
8930
+ /** That asset's OWN row in this lender — join key to its full term sheet. */
8931
+ marketUid?: string;
8932
+ via: Open<'collateral' | 'vault-allocation' | 'strategy' | 'idle'>;
8933
+ assets?: number;
8934
+ assetsUsd?: number;
8935
+ /** 0..100. ABSENT when `weightBasis === 'unweighted'`. */
8936
+ weightPct?: number;
8937
+ ltv?: number;
8938
+ liquidationLtv?: number;
8939
+ liquidationPenalty?: number;
8940
+ /** The collateral's OWN oracle. Your deposit's safety depends on the oracle
8941
+ * pricing SOMEONE ELSE'S collateral. */
8942
+ oracle?: OracleTerms;
8943
+ quality?: AssetQuality;
8944
+ }
8945
+ interface ExposureTerms {
8946
+ count: number;
8947
+ /**
8948
+ * How `weightPct` was obtained, and therefore how much to trust it.
8949
+ * `unweighted` = POOLED lenders: Aave/Compound do not record which
8950
+ * collateral backs which borrow on-chain, so the list is the ACCEPTED SET,
8951
+ * not a measured split. Do not render a pie chart from it.
8952
+ */
8953
+ weightBasis: Open<'debt' | 'allocation' | 'unweighted'>;
8954
+ worstRiskScore?: number;
8955
+ worstOracleBand?: OracleBand;
8956
+ /** Largest single exposure's `weightPct` — the concentration signal. Only
8957
+ * meaningful when `weightBasis !== 'unweighted'`. */
8958
+ topWeightPct?: number;
8959
+ items: ExposureEntry[];
8960
+ }
8961
+ interface UtilizationTerms {
8962
+ /** borrowed / supplied, 0..1 — the IRM input for this market. */
8963
+ utilization: number;
8964
+ /**
8965
+ * The basis the ratio is computed over. NOT always this row: rates for
8966
+ * shared-liquidity protocols are set on a larger pool, and a simulation
8967
+ * must shift THAT, not the row totals.
8968
+ */
8969
+ basis: Open<'market' | 'hub' | 'liquidity-layer' | 'pool'>;
8970
+ irmTotalDeposits?: number;
8971
+ irmTotalDebt?: number;
8972
+ /** Where the curve steepens — headroom before the rate jumps. */
8973
+ targetUtilization?: number;
8974
+ kinkUtilization?: number;
8975
+ /** 0..1. `1` = cap full and the side is closed. */
8976
+ supplyCapUtilization?: number;
8977
+ borrowCapUtilization?: number;
8978
+ /** Fluid: share of collateral locked below the withdrawal limit. */
8979
+ lockupRatio?: number;
8980
+ }
8981
+ /**
8982
+ * A non-default risk category, expressed as a DELTA against the resolved
8983
+ * default. `config` is a MAP keyed by category (Aave e-modes, Dolomite
8984
+ * categories, Euler configs, Silo) — a single flat LTV silently reports the
8985
+ * default and hides the rest, which on an Aave ETH-correlated e-mode is the
8986
+ * difference between 80 % and 93 %.
8987
+ */
8988
+ interface ModeVariant {
8989
+ /** The `config` map key. `'0'` is the default on every lender. */
8990
+ modeId: string;
8991
+ label?: string;
8992
+ isDefault: boolean;
8993
+ entry?: Open<'automatic' | 'user-selected' | 'per-position'>;
8994
+ liquidation?: Partial<LiquidationTerms>;
8995
+ /**
8996
+ * Mode-scoped and usually RESTRICTED: an e-mode typically narrows the
8997
+ * accepted collateral to a correlated basket. Omitting it would make the
8998
+ * headline LTV look obtainable against collateral the mode forbids.
8999
+ */
9000
+ acceptedCollateral?: ExposureTerms;
9001
+ rate?: Partial<RateTerms>;
9002
+ availability?: Partial<AvailabilityTerms>;
9003
+ }
9004
+ interface SupplyTermSheet {
9005
+ /** Is this position earning, or is it just collateral? */
9006
+ role: Open<'yield' | 'collateral' | 'both'>;
9007
+ rate: RateTerms;
9008
+ maturity: MaturityTerms;
9009
+ exit: SupplyExitTerms;
9010
+ /** ALL fees on this side, including the exit subset. */
9011
+ fees: FeeTerm[];
9012
+ /** What secures the debt drawn against this deposit. */
9013
+ backedBy?: ExposureTerms;
9014
+ modes?: ModeVariant[];
9015
+ counterparty: CounterpartyTerms;
9016
+ availability: AvailabilityTerms;
9017
+ /** Is the supplied principal at risk beyond ordinary credit risk? */
9018
+ principal: {
9019
+ protected: boolean;
9020
+ risks: Open<'bad-debt' | 'physical-delivery' | 'nav-drawdown' | 'first-loss' | 'depeg'>[];
9021
+ };
9022
+ info: TermInfo;
9023
+ /** Namespaced escape hatch — see the promotion rule in TERM_SHEET_PLAN §13.5. */
9024
+ ext?: Record<string, unknown>;
9025
+ }
9026
+ interface BorrowTermSheet {
9027
+ rate: RateTerms;
9028
+ maturity: MaturityTerms;
9029
+ /**
9030
+ * Does the amount owed GROW, or is it a static face value fixed at trade
9031
+ * time? The single biggest departure from variable-rate intuition — four of
9032
+ * six fixed-term lenders are static.
9033
+ */
9034
+ debtShape: Open<'accruing' | 'static-face' | 'prepaid'>;
9035
+ exit: BorrowExitTerms;
9036
+ /** Fully resolved for the DEFAULT mode. `modes[]` carries the rest. */
9037
+ liquidation: LiquidationTerms;
9038
+ /** What you may post, each with its own LTV, oracle and quality. */
9039
+ acceptedCollateral?: ExposureTerms;
9040
+ modes?: ModeVariant[];
9041
+ fees: FeeTerm[];
9042
+ counterparty: CounterpartyTerms;
9043
+ availability: AvailabilityTerms;
9044
+ info: TermInfo;
9045
+ ext?: Record<string, unknown>;
9046
+ }
9047
+ /**
9048
+ * Distinguishes "not applicable" from "not implemented yet" — the affordance
9049
+ * that lets phases ship incrementally without lying. A missing `oracle` must
9050
+ * never read as "this market has no oracle" when the truth is "we have not
9051
+ * classified it".
9052
+ */
9053
+ interface CoverageInfo {
9054
+ /** Blocks genuinely computed for this market. */
9055
+ present: string[];
9056
+ /** Blocks that do NOT APPLY here — a positive fact. */
9057
+ notApplicable?: Record<string, string>;
9058
+ /** Blocks that WOULD apply but are not wired yet. */
9059
+ pending?: Record<string, string>;
9060
+ }
9061
+ /** Current schema version. Bumped ONLY for removals/semantic/unit changes;
9062
+ * new optional fields and new enum members are additive. */
9063
+ declare const TERM_SHEET_SCHEMA_VERSION = 1;
9064
+ interface TermSheet {
9065
+ schemaVersion: number;
9066
+ /** unix seconds at fetch — everything here is a snapshot. */
9067
+ asOf: number;
9068
+ /** `<family>.<variant>@v<n>`, e.g. `aave-v3.pool@v1`. Points at the prose
9069
+ * catalogue so the per-market payload stays small. */
9070
+ profileId: string;
9071
+ /** The anchor this sheet describes. */
9072
+ marketUid?: string;
9073
+ lender?: string;
9074
+ chainId?: string;
9075
+ /**
9076
+ * The market's own underlying asset.
9077
+ *
9078
+ * Load-bearing, not decoration: `availability.minSize`, `availability.cap`
9079
+ * and `exit.minDebt` are all RAW base units, and without decimals + symbol a
9080
+ * consumer cannot render any of them. The sheet described a market without
9081
+ * ever saying which asset it was about.
9082
+ */
9083
+ asset?: TermAssetRef;
9084
+ supply?: SupplyTermSheet;
9085
+ borrow?: BorrowTermSheet;
9086
+ /** Shared — these describe the MARKET, not a side. */
9087
+ governance?: GovernanceTerms;
9088
+ oracle?: OracleTerms;
9089
+ utilization?: UtilizationTerms;
9090
+ constraints?: PositionConstraints;
9091
+ coverage?: CoverageInfo;
9092
+ ext?: Record<string, unknown>;
9093
+ }
9094
+ /** Compact form for list endpoints — `?terms=digest`. */
9095
+ interface TermSheetDigest {
9096
+ schemaVersion: number;
9097
+ profileId: string;
9098
+ marketUid?: string;
9099
+ supply?: {
9100
+ rateKind: RateKind;
9101
+ aprTotal: AprPercent;
9102
+ maturityKind: MaturityTerms['kind'];
9103
+ maturity?: number;
9104
+ exitMode: SupplyExitMode;
9105
+ settlement: SupplyExitTerms['settlement'];
9106
+ canOpen: boolean;
9107
+ headline: string;
9108
+ tags: TermTag[];
9109
+ backedBy?: Omit<ExposureTerms, 'items'>;
9110
+ };
9111
+ borrow?: {
9112
+ rateKind: RateKind;
9113
+ apr: AprPercent;
9114
+ maturityKind: MaturityTerms['kind'];
9115
+ maturity?: number;
9116
+ debtShape: BorrowTermSheet['debtShape'];
9117
+ earlyRepay: BorrowExitTerms['earlyRepay'];
9118
+ liquidationTrigger: LiquidationTerms['trigger'];
9119
+ canOpen: boolean;
9120
+ headline: string;
9121
+ tags: TermTag[];
9122
+ acceptedCollateral?: Omit<ExposureTerms, 'items'>;
9123
+ };
9124
+ oracle?: Pick<OracleTerms, 'kind' | 'address' | 'provider' | 'band'>;
9125
+ governance?: Pick<GovernanceTerms, 'mutability' | 'controllerKind' | 'timelockSecs' | 'tier'>;
9126
+ utilization?: number;
9127
+ }
9128
+ /** A term profile — the invariant prose, one per lender family × variant. */
9129
+ interface TermProfile {
9130
+ id: string;
9131
+ /** Display name, e.g. `Aave V3 pool market`. */
9132
+ name: string;
9133
+ /** Which lender family this covers. */
9134
+ family: string;
9135
+ supply?: {
9136
+ description: string;
9137
+ implications?: string[];
9138
+ };
9139
+ borrow?: {
9140
+ description: string;
9141
+ implications?: string[];
9142
+ };
9143
+ docsUrl?: string;
9144
+ }
9145
+ /** Deep-partial, for adapters that return only what they override. */
9146
+ type DeepPartial<T> = {
9147
+ [K in keyof T]?: T[K] extends (infer U)[] ? U[] : T[K] extends object | undefined ? DeepPartial<NonNullable<T[K]>> : T[K];
9148
+ };
9149
+
9150
+ /**
9151
+ * The normalized input the builder reads.
9152
+ *
9153
+ * Deliberately NOT `PoolData` directly: the same market travels through this
9154
+ * codebase in two casings — the in-package shape (`totalDepositsUSD`,
9155
+ * `variableBorrowRate`) and the API-serialized shape (`totalDepositsUsd`,
9156
+ * nested `caps`/`flags`/`config`). The builder must work on both, because it
9157
+ * runs in-package during a fetch AND at the worker while proxying an origin
9158
+ * response.
9159
+ *
9160
+ * So: one tolerant reader (`toTermSheetInput`) normalizes either shape into
9161
+ * this interface, and the builder itself is pure over the normalized form.
9162
+ */
9163
+ interface TermConfigEntry {
9164
+ category: number | string;
9165
+ label?: string;
9166
+ borrowCollateralFactor?: number;
9167
+ collateralFactor?: number;
9168
+ borrowFactor?: number;
9169
+ liquidationPenalty?: number;
9170
+ closeFactor?: number;
9171
+ targetHealthFactor?: number;
9172
+ collateralDisabled?: boolean;
9173
+ debtDisabled?: boolean;
9174
+ }
9175
+ interface TermRewardInput {
9176
+ asset?: string;
9177
+ depositRate?: number;
9178
+ variableBorrowRate?: number;
9179
+ stableBorrowRate?: number;
9180
+ /** Merkl / points programs mark themselves; absent ⇒ a normal token. */
9181
+ kind?: string;
9182
+ endsAt?: number;
9183
+ claim?: string;
9184
+ }
9185
+ interface TermMenuInput {
9186
+ termId: number;
9187
+ durationSecs: number;
9188
+ durationDays: number;
9189
+ apr: number;
9190
+ depositApr?: number;
9191
+ available?: number;
9192
+ }
9193
+ /** Normalized market facts the generic builder needs. */
9194
+ interface TermSheetInput {
9195
+ marketUid: string;
9196
+ lender: string;
9197
+ chainId: string;
9198
+ /** Underlying asset of THIS row. */
9199
+ asset?: {
9200
+ chainId?: string;
9201
+ address?: string;
9202
+ symbol?: string;
9203
+ name?: string;
9204
+ decimals?: number;
9205
+ assetGroup?: string;
9206
+ logoURI?: string;
9207
+ };
9208
+ underlying?: string;
9209
+ decimals?: number;
9210
+ depositRate?: number;
9211
+ variableBorrowRate?: number;
9212
+ stableBorrowRate?: number;
9213
+ intrinsicYield?: number;
9214
+ rewards?: TermRewardInput[];
9215
+ rateModel?: string;
9216
+ originationFee?: number;
9217
+ totalDeposits?: number;
9218
+ totalDebt?: number;
9219
+ totalDebtStable?: number;
9220
+ totalLiquidity?: number;
9221
+ borrowLiquidity?: number;
9222
+ totalDepositsUsd?: number;
9223
+ totalDebtUsd?: number;
9224
+ totalLiquidityUsd?: number;
9225
+ utilization?: number;
9226
+ irmTotalDeposits?: number;
9227
+ irmTotalDebt?: number;
9228
+ lockupRatio?: number;
9229
+ supplyCap?: number;
9230
+ borrowCap?: number;
9231
+ debtCeiling?: string | number;
9232
+ /** Minimum borrow in RAW base units (Compound V3 `baseBorrowMin`). */
9233
+ minBorrow?: string;
9234
+ isActive?: boolean;
9235
+ isFrozen?: boolean;
9236
+ borrowingEnabled?: boolean;
9237
+ depositsEnabled?: boolean;
9238
+ collateralActive?: boolean;
9239
+ hasStable?: boolean;
9240
+ variableBorrowDisabled?: boolean;
9241
+ config?: Record<string, TermConfigEntry>;
9242
+ closeFactor?: number;
9243
+ targetHealthFactor?: number;
9244
+ fixedTerm?: {
9245
+ model?: string;
9246
+ maturity?: number;
9247
+ fees?: {
9248
+ continuousFeeApr?: number;
9249
+ settlementFee?: number;
9250
+ latePenaltyApr?: number;
9251
+ originationFeePercent?: number;
9252
+ };
9253
+ earlyRepay?: {
9254
+ kind?: string;
9255
+ };
9256
+ provider?: {
9257
+ kind?: string;
9258
+ address?: string;
9259
+ };
9260
+ auction?: Record<string, unknown>;
9261
+ };
9262
+ terms?: TermMenuInput[];
9263
+ /** Market-level params (`params.market`) when the lender has them. */
9264
+ market?: Record<string, unknown>;
9265
+ }
9266
+ /**
9267
+ * Normalize either the in-package `PoolData`-ish row or an API `LendingMarket`
9268
+ * item into {@link TermSheetInput}. Tolerant by design: a field missing under
9269
+ * one casing is looked up under the other, and nested `caps`/`flags` bundles
9270
+ * are unwrapped.
9271
+ */
9272
+ declare function toTermSheetInput(row: Record<string, any>, ctx?: {
9273
+ marketUid?: string;
9274
+ lender?: string;
9275
+ chainId?: string;
9276
+ market?: Record<string, any>;
9277
+ /**
9278
+ * Item-level fixed-term descriptor. `/lending/latest` attaches `fixedTerm`
9279
+ * to the LENDER item, not to each market row, so without this every
9280
+ * fixed-term market would silently lose its maturity, its fees and its
9281
+ * auction window. A row-level `fixedTerm` (how `/pools/latest` serializes
9282
+ * it) is more specific and wins.
9283
+ */
9284
+ fixedTerm?: Record<string, any>;
9285
+ }): TermSheetInput;
9286
+
9287
+ /**
9288
+ * Accepted-collateral / backing set from the SIBLING rows of the same lender.
9289
+ *
9290
+ * Pooled lenders (Aave, Compound) do NOT record on-chain which collateral
9291
+ * backs which borrow, so the result is the ACCEPTED SET with
9292
+ * `weightBasis: 'unweighted'` and NO `weightPct` on any item. Inventing a
9293
+ * TVL-proxy weight would look authoritative and be false — a large idle
9294
+ * collateral market is not a large exposure.
9295
+ */
9296
+ declare function buildExposures(input: TermSheetInput, siblings: TermSheetInput[], direction: 'backing' | 'accepted'): ExposureTerms | undefined;
9297
+ /** Deep merge an adapter's partial over the generic result. Arrays REPLACE. */
9298
+ declare function mergeDeep<T>(base: T, patch: DeepPartial<T> | undefined): T;
9299
+ /**
9300
+ * Fill `info` (headline / description / tags) LAST, after adapters have run,
9301
+ * so the prose always describes the final values rather than the generic
9302
+ * guess. This is the mechanism that stops copy drifting from numbers.
9303
+ */
9304
+ declare function finalizeInfo(sheet: TermSheet): TermSheet;
9305
+ interface BuildTermSheetOptions {
9306
+ /** Unix seconds; injected so tests are deterministic. */
9307
+ now?: number;
9308
+ /** Other rows of the SAME lender+chain — used to derive the exposure set. */
9309
+ siblings?: TermSheetInput[];
9310
+ /** Adapter output, merged over the generic result. */
9311
+ patch?: DeepPartial<TermSheet>;
9312
+ profileId?: string;
9313
+ }
9314
+ /** Build one complete term sheet for one market row. */
9315
+ declare function buildTermSheet(input: TermSheetInput, opts?: BuildTermSheetOptions): TermSheet;
9316
+
9317
+ /** Supply-side tags. Market-level tags are folded in by the caller. */
9318
+ declare function deriveSupplyTags(supply: SupplyTermSheet, market?: Pick<TermSheet, 'governance' | 'oracle'>): TermTag[];
9319
+ /** Borrow-side tags. */
9320
+ declare function deriveBorrowTags(borrow: BorrowTermSheet, market?: Pick<TermSheet, 'governance' | 'oracle'>): TermTag[];
9321
+
9322
+ /**
9323
+ * Severity model — PURE, derived only from structured fields.
9324
+ *
9325
+ * Space is the binding constraint at every display depth, so ranking has to be
9326
+ * principled rather than per-lender taste. There is deliberately NO
9327
+ * hand-maintained list of "scary markets": a newly integrated lender is
9328
+ * classified correctly the moment its adapter sets the right fields.
9329
+ *
9330
+ * - `critical` — you can lose MORE than the amount at stake, or lose it
9331
+ * without doing anything wrong. This is the only tier that should gate a
9332
+ * signature.
9333
+ * - `warn` — it costs money, or blocks you.
9334
+ * - `info` — everything else.
9335
+ */
9336
+ type Severity = 'critical' | 'warn' | 'info';
9337
+ interface SeverityFinding {
9338
+ severity: Severity;
9339
+ /** Stable slug — the join key for UI copy and for tests. */
9340
+ id: string;
9341
+ /** Ready-to-render sentence. */
9342
+ message: string;
9343
+ side: 'supply' | 'borrow' | 'market';
9344
+ }
9345
+ /** Sort findings most-severe-first, stable within a tier. */
9346
+ declare function rankFindings(findings: SeverityFinding[]): SeverityFinding[];
9347
+ declare function supplyFindings(supply: SupplyTermSheet): SeverityFinding[];
9348
+ declare function borrowFindings(borrow: BorrowTermSheet): SeverityFinding[];
9349
+ /**
9350
+ * All findings for one side of a sheet, ranked most-severe-first. Pass
9351
+ * `side: 'supply' | 'borrow'` — market-level findings are always included
9352
+ * because governance and oracle affect both sides.
9353
+ */
9354
+ declare function findingsFor(sheet: TermSheet, side: 'supply' | 'borrow'): SeverityFinding[];
9355
+ /** Does this side carry anything that should gate a signature? */
9356
+ declare function hasCritical(sheet: TermSheet, side: 'supply' | 'borrow'): boolean;
9357
+
9358
+ /** `4.1234` → `"4.12 %"`; trims to 2dp, drops a trailing `.00`. */
9359
+ /**
9360
+ * Format an already-PERCENT value (`49.88` → `"49.88%"`).
9361
+ *
9362
+ * Never pass a fraction. The convention across the term sheet is **rates are
9363
+ * percent, factors/ratios are fractions**, so `liquidationLtv`, `penalty`,
9364
+ * `utilization` and every `*Ratio` must be multiplied by 100 at the call site.
9365
+ *
9366
+ * Trailing zeros are trimmed only inside the FRACTIONAL part: a naive
9367
+ * `/\.?0+$/` strip eats integer zeros whenever `dp = 0` leaves no decimal
9368
+ * point, turning `100` into `"1"` and `1000` into `"1"`.
9369
+ *
9370
+ * No space before the `%` — the portal formats the same numbers without one,
9371
+ * and a headline reading "49.88 %" next to a table cell reading "49.88%" looks
9372
+ * like two different figures.
9373
+ */
9374
+ declare function pct(value: number | undefined, dp?: number): string;
9375
+ /** Duration in seconds → the coarsest human unit that stays honest. */
9376
+ declare function duration(secs: number | undefined): string;
9377
+ /** Unix seconds → `"3 Sep 2026"`. Locale-independent so snapshots are stable. */
9378
+ declare function shortDate(unixSecs: number | undefined): string;
9379
+ /** One fee → a self-contained phrase, correct even for an unrecognised `id`. */
9380
+ declare function feePhrase(fee: FeeTerm): string;
9381
+ /** Supply-side headline: ≤ ~100 chars, always populated. */
9382
+ declare function supplyHeadline(s: SupplyTermSheet): string;
9383
+ /** Borrow-side headline. */
9384
+ declare function borrowHeadline(b: BorrowTermSheet): string;
9385
+ /** Supply-side description — 1–3 sentences, market values interpolated. */
9386
+ declare function supplyDescription(s: SupplyTermSheet, sheet?: Pick<TermSheet, 'utilization'>): string;
9387
+ /** Borrow-side description. */
9388
+ declare function borrowDescription(b: BorrowTermSheet): string;
9389
+
9390
+ declare const TERM_PROFILES: TermProfile[];
9391
+ declare function getTermProfile(id: string): TermProfile | undefined;
9392
+ /** Fallback used when a family has no dedicated profile yet. */
9393
+ declare const DEFAULT_PROFILE_ID = "pool.variable@v1";
9394
+
9395
+ /**
9396
+ * Stamping — the single place term sheets are attached.
9397
+ *
9398
+ * Runs ONCE at the end of the public-data pipeline rather than inside each
9399
+ * lender's converter. That is the whole architecture: ~200 Aave/Compound forks
9400
+ * get correct sheets from the generic builder with zero per-fork work, and
9401
+ * only the ~13 exotic families need an adapter.
9402
+ */
9403
+ interface StampOptions {
9404
+ /** Unix seconds; injected so tests are deterministic. */
9405
+ now?: number;
9406
+ /** Attach ranked `implications[]` from the severity model. Default true. */
9407
+ withImplications?: boolean;
9408
+ /**
9409
+ * Derive `governance` / `oracle` / exposure quality from the rows' own
9410
+ * `oracleInfo` + `risk.breakdown`. Default true — set `false` only to test
9411
+ * the un-enriched builder in isolation.
9412
+ */
9413
+ enrich?: boolean;
9414
+ }
9415
+ /**
9416
+ * Build sheets for one lender's rows on one chain.
9417
+ *
9418
+ * Siblings matter: the exposure set (`backedBy` / `acceptedCollateral`) is
9419
+ * derived by cross-referencing the OTHER rows of the same lender, so the whole
9420
+ * group has to be built together.
9421
+ */
9422
+ declare function buildTermSheetsForGroup(rows: Record<string, any>[], ctx?: {
9423
+ lender?: string;
9424
+ chainId?: string;
9425
+ market?: Record<string, any>;
9426
+ /** Item-level `fixedTerm` from `/lending/latest` — see `toTermSheetInput`. */
9427
+ fixedTerm?: Record<string, any>;
9428
+ }, opts?: StampOptions): Map<string, TermSheet>;
9429
+ /**
9430
+ * Fill `info.implications[]` from the severity model, most severe first.
9431
+ *
9432
+ * Derived rather than hand-written, so a newly integrated lender gets correct
9433
+ * warnings the moment its adapter sets the right structured fields — and a
9434
+ * warning can never contradict the numbers next to it.
9435
+ */
9436
+ declare function attachImplications(sheet: TermSheet): TermSheet;
9437
+ /**
9438
+ * Build an {@link EnrichmentIndex} from the market rows themselves.
9439
+ *
9440
+ * The governance and oracle screens are NOT a separate fetch: the origin
9441
+ * already ships both on every row — `oracleInfo.feeds[]` (the oracle-risk
9442
+ * classification) and `risk.breakdown[]` (the governance screen under
9443
+ * `category: 'governance'`, the asset screen under `category: 'token'`). So
9444
+ * the join is local to the group being stamped, with no extra round-trip and
9445
+ * no cross-service dependency.
9446
+ *
9447
+ * The per-exposure enrichment falls out of the same data: an exposure item
9448
+ * points at a SIBLING row's `marketUid`, and that sibling is already in this
9449
+ * group — so its oracle and its asset quality are right there.
9450
+ */
9451
+ declare function enrichmentIndexFromRows(rows: Record<string, any>[]): EnrichmentIndex;
9452
+ /**
9453
+ * Collapse a sheet to its digest form (`?terms=digest`).
9454
+ *
9455
+ * Drops `items[]` from the exposure sets and the long prose — an Aave market
9456
+ * with 30 accepted collaterals is several kB on its own, and it is the SAME
9457
+ * accepted set repeated on every row of that lender. Every dropped item is
9458
+ * still reachable: each carries a `marketUid` for the bulk endpoint.
9459
+ */
9460
+ declare function toDigest(sheet: TermSheet): TermSheetDigest;
9461
+ /** Row shape of `~/risk-data/data/oracles/oracle-risk-flat.json`. */
9462
+ interface OracleRiskRow {
9463
+ marketUid: string;
9464
+ oracle?: string;
9465
+ provider?: string;
9466
+ priceDescription?: string;
9467
+ intendedPair?: string;
9468
+ correctOracle?: boolean | null;
9469
+ denominatorMatch?: boolean | null;
9470
+ fixedRate?: boolean;
9471
+ score?: number;
9472
+ band?: string;
9473
+ flags?: string[];
9474
+ /** Underlying feeds when an adapter composes several. `oracle` stays the
9475
+ * single source of truth — this is for auditability only. */
9476
+ components?: string[];
9477
+ }
9478
+ /** Row shape of `~/risk-data/data/lending/market-governance-flat.json`. */
9479
+ interface GovernanceRow {
9480
+ marketUid: string;
9481
+ tier?: string;
9482
+ score?: number;
9483
+ ownerKind?: string;
9484
+ signerThreshold?: number | null;
9485
+ signerCount?: number | null;
9486
+ mode?: string;
9487
+ /** Present once the flat builder carries it through (see TERM_SHEET_PLAN §5.4.1). */
9488
+ delaySeconds?: number | null;
9489
+ }
9490
+ /** Per-asset quality, keyed `chainId → lowercased address`. */
9491
+ type AssetRiskIndex = Record<string, Record<string, {
9492
+ riskScore?: number;
9493
+ source?: string;
9494
+ liquidityUsd?: number;
9495
+ governanceScore?: number;
9496
+ governanceLevel?: string;
9497
+ upgradeable?: boolean;
9498
+ canPause?: boolean;
9499
+ adminKind?: string;
9500
+ }>>;
9501
+ interface EnrichmentIndex {
9502
+ oracleByMarketUid?: Map<string, OracleRiskRow>;
9503
+ governanceByMarketUid?: Map<string, GovernanceRow>;
9504
+ assetRisk?: AssetRiskIndex;
9505
+ }
9506
+ /**
9507
+ * Merge governance / oracle / asset-quality onto a sheet at the SERVING layer.
9508
+ *
9509
+ * These cannot be computed in-package — they come from the risk-data
9510
+ * screeners, which key on the same `marketUid` grammar (a dictionary lookup,
9511
+ * not a fuzzy match). `margin-fetcher` emits the sheet with these blocks
9512
+ * absent; the worker fills them in, exactly as it already does for
9513
+ * `oracleInfo`.
9514
+ */
9515
+ declare function enrichTermSheet(sheet: TermSheet, index: EnrichmentIndex): TermSheet;
9516
+
9517
+ /**
9518
+ * Invariant checker — pure, and the mechanism that turns a derivation bug into
9519
+ * a loud failure instead of a plausible-looking wrong answer.
9520
+ *
9521
+ * Run over live fetched data in CI for every lender key and vault provider.
9522
+ * Every rule here encodes something that WOULD otherwise ship silently.
9523
+ */
9524
+ interface TermSheetViolation {
9525
+ /** Stable slug, so a test can assert on the specific rule. */
9526
+ rule: string;
9527
+ message: string;
9528
+ marketUid?: string;
9529
+ }
9530
+ declare function validateTermSheet(sheet: TermSheet): TermSheetViolation[];
9531
+ /** Validate a batch; returns every violation found, flattened. */
9532
+ declare function validateTermSheets(sheets: TermSheet[]): TermSheetViolation[];
9533
+
9534
+ /**
9535
+ * A term-sheet adapter: returns ONLY what the generic builder cannot derive,
9536
+ * as a `DeepPartial<TermSheet>` merged over the generic result.
9537
+ *
9538
+ * Adding a lender is one adapter plus one profile entry — no core edits. The
9539
+ * completeness test fails when a lender family reaches production without a
9540
+ * profile, so the extension point cannot be silently skipped.
9541
+ */
9542
+ interface TermAdapter {
9543
+ /** Stable id, for tests and debugging. */
9544
+ id: string;
9545
+ /** Does this adapter handle the given lender key? */
9546
+ matches: (lender: string) => boolean;
9547
+ /** The profile whose prose this market points at. */
9548
+ profileId: (input: TermSheetInput) => string;
9549
+ build: (input: TermSheetInput) => DeepPartial<TermSheet>;
9550
+ }
9551
+ /**
9552
+ * Order matters only where predicates could overlap; today they are disjoint.
9553
+ * The list is walked front-to-back and the first match wins.
9554
+ */
9555
+ declare const TERM_ADAPTERS: TermAdapter[];
9556
+ declare function resolveAdapter(lender: string): TermAdapter | undefined;
9557
+
9558
+ export { type AaveMetadata, type AaveV2Public, type AaveV2UserReserveResponse, type AaveV3Public, type AaveV3UserReserveResponse, type AdditionalYields, type AdminKind, ApiBookSource, type AprData, type AprPercent, type AssetQuality, type AssetRiskIndex, type AuctionWindow, type AvailabilityTerms, type BalanceData, type BaseLendingPosition, type BasicReserveResponse, type BorrowExitTerms, type BorrowTermSheet, type BuildTermSheetOptions, type ChainDiagnostic, type ChainLinkResponse, type ChainQuery, type ChainSummary, type CompoundV2Metadata, type CompoundV3Public, type CompoundV3UserReserveResponse, type ConfigEntry, type ConvertLenderUserDataOptions, type CoreValidators, type CounterpartyTerms, type CoverageInfo, DEFAULT_MIDNIGHT_API, DEFAULT_OUTLIER_GUARD, DEFAULT_PROFILE_ID, DEFAULT_STALE_REJECT_SECONDS, DEFAULT_TERMMAX_API, type DeepPartial, type Denomination, type DepthMap, EMPTY_BALANCE, type EModeAssets, type EModeData, type EModeResult, EXACTLY_LENDER_KEY, type EndpointFailure, type EnrichmentIndex, type EulerEarnVault, type EulerEarnVaults, type EulerV2Metadata, type ExactlyFixedPool, type ExactlyFixedPosition, type ExactlyMarketAccount, type ExactlyMarketsRaw, type ExactlyUserFixedPosition, type ExposureEntry, type ExposureTerms, type ExtraValidationCall, type FeeTerm, type FeeWhen, type FeedObservation, type FeedStat, type FeedStatsMap, type FeedTimestampMap, type FetchListaVaultsFromChainOptions, type FetchMorphoVaultsFromChainOptions, type FetchOraclePricesOptions, type FetchTokenBalancesOptions, type FlattenPriorityConfig, type FluidFToken, type FluidFTokens, type FrankencoinMarketRaw, type FrankencoinMarketsRaw, type FrankencoinPositionInfo, type FullLenderRewardsMap, GMX_API_HOSTS, GMX_READ_CONTRACTS, GMX_SUPPORTED_CHAINS, type GenericCurrency, type GenericTokenList, type GetVaultPublicDataAllOptions, type GmxExecutionFees, type GmxPendingDeposit, type GmxPendingWithdrawal, type GmxReadContracts, type GmxUserBalance, type GmxUserPositions, type GmxUserPositionsOptions, type GmxVault, type GmxVaultKind, type GmxVaults, type GmxVaultsFetchOptions, type GovernancePower, type GovernanceRow, type GovernanceTerms, type GroupAccumulator, HYPERCORE_VAULT_REGISTRY, type HypercoreLockStatus, type HypercoreUserPositionsOptions, type HypercoreVault, type HypercoreVaultPosition, type HypercoreVaultRegistryEntry, type HypercoreVaults, type HypercoreVaultsFetchOptions, IDLE_MARKET_ID, INTERFACE_IDS, type IncompleteLenderRead, type IncompleteReason, type InitMetadata, type InitPublic, type InitUserReserveResponse, type InterfaceKind, type InvariantViolation, type InverseMarketRaw, type InverseMarketsRaw, type InversePositionInfo, LAGOON_API_URL, LAGOON_CHAIN_IDS, type LagoonApiVault, type LagoonSyncMode, type LagoonVault, type LagoonVaults, type LenderConfigData, type LenderConfigMap, type LenderCrossPoolMeta, type LenderData, type LenderDataEntry, type LenderInfo, type LenderInfoMap, type LenderPublicBase, type LenderRewardsMap, type LenderSummary, type LenderToLenderCrossPoolMeta, type LenderTotalAmounts, type LenderUserQuery, type LenderUserResponse, type LenderYieldComplete, type LenderYields$1 as LenderYields, LendingMode, type LiquidationPenaltyTerm, type LiquidationTerms, type LiquityBranchRaw, type LiquityDiscoveredTrove, type LiquityDiscovery, type LiquityMarketsRaw, type LiquitySpInfo, type LiquityTroveInfo, type ListaMarketOverrides, type LlamaLendMarketRaw, type LlamaLendMarketsRaw, type LlamaLendPositionInfo, type LoopPostTradeMetrics, type LstDelegation, type LstDelegationKind, type LstValidator, type LstWithdrawalFetchOptions, type LstWithdrawalRegistryEntry, type LstWithdrawalRequest, type LstWithdrawalRequestsByLst, type LstWithdrawalStatus, MORPHO_LENS, MULTICALL_FAILURE, type MaturityTerms, MaxParamThresholds, type MergedUserData, type MidnightBook, type MidnightBookLevel, type MidnightBookSource, type ModeBase, type ModeVariant, type MorphoMarketOverrides, type MorphoSubgraphProxyConfig, type MorphoUserMarketBalance, type MorphoUserReserveResponse, type MorphoVault, type MorphoVaults, type MulticallEndpointOptions, type MulticallRpcBatch, type NumberMap, type Open, type OracleBand, type OracleDiagnostics, type OraclePriceEntry, type OraclePricesResult, type OracleRiskRow, type OracleTerms, type OutlierGuardConfig, type ParsedBalanceData, type ParsedResponse, type ParsedUserBalance, type PermissionKind, type PermissionParams, type PoolData, type PoolWithMeta, type PortfolioSummary, type PortfolioTotals, type PositionConstraints, type PostTradeMetrics, type PreparedCall, type PreparedMergedMulticallParams, type PreparedMergedRpcCalls, type PreparedTokenBalanceRpcCalls, type PreparedUserDataRpcCalls, type PriceDerivation, type PriceSelection, type ProtocolParams, type ProviderOptions, type RateKind, type RateMenuEntry, type RateTerms, type RawRpcBatch, type RawRpcCall, type RawRpcResponse, type ReadFailurePolicy, type ResupplyMarketsRaw, type ResupplyPairIdentity, type ResupplyPairRaw, type ResupplyPositionInfo, type ResupplyWrappedMarket, type RewardTerm, type RiverMarketRaw, type RiverMarketsRaw, type RiverPositionInfo, STABLECOIN_SYMBOLS, STCELO_MANAGER_ADDRESS, type SelectPricesOptions, type Severity, type SeverityFinding, type SiloVault, type SiloVaults, type StCeloValidatorGroup, type StaleFeedEntry, type StampOptions, type StructuredOraclePrices, type SubAccountSummary, type SumerPositionInput, type SummaryAprData, type SummaryBalanceData, type SupplyExitMode, type SupplyExitTerms, type SupplyTermSheet, TELLER_CALLS_PER_BID, TERMMAX_CALLS_PER_ACCOUNT, DECIMAL_BASE as TERMMAX_DECIMAL_BASE, TERMMAX_LIQUIDATION_PENALTY, TERMMAX_LIQUIDATION_WINDOW_SECS, TERMMAX_LIQUIDATOR_BONUS, TERMMAX_PARTIAL_CLOSE_FACTOR, TERMMAX_PARTIAL_LIQUIDATION_THRESHOLD_USD, TERM_ADAPTERS, TERM_PROFILES, TERM_SHEET_SCHEMA_VERSION, type TellerDiscoveredBid, type TellerDiscovery, type TellerMarketsRaw, type TellerPoolRaw, type TermAdapter, type TermAssetRef, type TermAuctionOrder, type TermAuctionOrders, type TermBookSource, type TermBookTop, type TermInfo, type TermListing, type TermMarketRaw, TermMaxApiSource, type TermMaxBookTop, type TermMaxCurveSegment, type TermMaxDataSource, type TermMaxDiscovery, type TermMaxFeeConfig, type TermMaxMarketConfig, type TermMaxMarketRaw, type TermMaxOrderState, type TermProfile, type TermSheet, type TermSheetDigest, type TermSheetInput, type TermSheetViolation, TermSubgraphSource, type TermTag, type TokenApprovalMeta, type TokenApprovalParams, type TokenBalanceEntry, type TokenBalanceQuery, type TokenBalanceResult, type TokenEntry, type TokenList, type TokenListInput, type TrackerDiagnostic, UPSHIFT_CHAIN_IDS, UPSHIFT_VAULTS_URL, type USDPriceMap, type UpshiftApiAsset, type UpshiftApiVault, type UpshiftVault, type UpshiftVaults, type UsddMarketRaw, type UsddMarketsRaw, type UsddPositionInfo, type UserApr, type UserConfig, type UserData, type UserDataResult, type UserLendingPosition, type UtilizationTerms, VAULT_SHARE_PRICE_PROBE, VOLATILE_VAULT_OVERRIDES, type ValidationResult, type VaultAprResult, type VaultClassification, type VaultClassificationFields, type VaultLookupEntry, type VaultMarketExposure, type VaultProvider, type VaultPublicDataAll, type VaultPublicDataResult, type VaultYieldSeries, type VaultYieldSnapshot, type YDaemonVault, YEARN_CHAIN_IDS, YEARN_YDAEMON_BASE, type YearnVault, type YearnVaultKind, type YearnVaults, type YieldProfile, __resetResupplyUserCaches, accountDepositListKey, accountWithdrawalListKey, appendSnapshot, applyPositionDelta, attachImplications, attachPricesToFlashLiquidity, borrowDescription, borrowFindings, borrowHeadline, buildExposures, buildFluidFTokensCall, buildLiquityUserCall, buildLoopResult, buildMorphoTypeCall, buildMorphoTypeUserCallWithLens, buildPortfolioTotals, buildSumerAccumulators, buildSummaries, buildTellerUserCall, buildTermMaxUserCall, buildTermSheet, buildTermSheetsForGroup, buildVaultLookup, calculateLeverage, calculateNetApr, calculateOverallNetApr, calculateWeightedAverage, classifyFreshness, classifyReliability, classifyVault, collectFeedObservations, computeBorrowDelta, computeCloseTradeDeltas, computeCollateralSwapDeltas, computeDebtSwapDeltas, computeDepositDelta, computeEModeAnalysis, computeOpenTradeDeltas, computePostTradeMetrics, computeRepayDelta, computeSumerBorrowDelta, computeSumerDepositDelta, computeSumerRepayDelta, computeSumerWaterfall, computeSumerWithdrawDelta, computeVaultApr, computeWithdrawDelta, computeZapTradeDeltas, consensusReference, convertExactlyMarketsToResponse, convertFrankencoinMarketsToResponse, convertInverseMarketsToResponse, convertLenderUserDataResult, convertLiquityMarketsToResponse, convertLlamaLendMarketsToResponse, convertResupplyMarketsToResponse, convertRiverMarketsToResponse, convertTellerMarketsToResponse, convertTermMarketsToResponse, convertTermMaxMarketsToResponse, convertUsddMarketsToResponse, createMarketUid, createMidnightBookSource, createMulticallRpcCall, createRawRpcCalls, createTermBookSource, createTermMaxDataSource, decodeListaMarkets, decodeMarkets, decodePackedListaUserDataset, decodePackedMorphoUserDataset, deriveBorrowTags, deriveSupplyTags, detectInterfaceKinds, duration, encodeBalanceFetcherCalldata, enrichTermSheet, enrichmentIndexFromRows, exactlyPairLtv, exactlyPenaltyRateToAprPercent, exactlyWadRateToPercent, feePhrase, feedKeyOf, feedStatKey, fetchDolomiteAccountNumbers, fetchEulerEarnVaults, fetchEulerEarnVaultsFromSubgraph, fetchEulerSubAccountIndexes, fetchExactlyMarkets, fetchFlashLiquidityForChain, fetchFluidFTokens, fetchFrankencoinMarkets, fetchGeneralYields, fetchGeneralYieldsByMarketUid, fetchGmxExecutionFees, fetchGmxTickerPrices, fetchGmxVaults, fetchHypercoreVaults, fetchInverseMarkets, fetchLagoonApiVaults, fetchLagoonVaults, fetchLiquityMarkets, fetchListaVaultsFromChain, fetchLlamaLendMarkets, fetchMorphoUserBalances, fetchMorphoUserPositionMarkets, fetchMorphoVaults, fetchMorphoVaultsFromApi, fetchMorphoVaultsFromChain, fetchOraclePrices, fetchPendlePrices, fetchResupplyMarkets, fetchRiverMarkets, fetchSiloVaults, fetchTellerMarkets, fetchTermMarkets, fetchTermMaxMarkets, fetchTokenBalances, fetchTokenMetadata, fetchUpshiftApiVaults, fetchUpshiftVaults, fetchUsddMarkets, fetchYearnApiVaults, fetchYearnVaults, filterActiveLenders, filterLendersByProtocol, finalizeInfo, findingsFor, frankencoinKeyParts, frankencoinLenderKey, fuseLenderData, generateLendingPools, getAavesForChain, getAssetConfig, getBalanceForMarketUid, getBorrowCapacity, getCachedLiquityTroves, getCachedTellerBids, getCachedTermMaxDiscovery, getCachedTermMaxMarket, getCachedTermMaxMarkets, getCoreValidators, getFluidFTokensConverter, getGmxApiHost, getGmxReadContracts, getGmxUserPositions, getHealthFactor, getHypercoreUserPositions, getHypercoreVaultRegistry, getLenderAssets, getLenderPublicData, getLenderPublicDataAll, getLenderPublicDataViaApi, getLenderUserDataMulti, getLenderUserDataResult, getLendersForChain, getLstDelegation, getLstValidators, getLstWithdrawalRegistry, getLstWithdrawalRequests, getMaxAmountClose, getMaxAmountCollateralSwap, getMaxAmountDebtSwap, getMaxAmountOpen, getMergedUserData, getMorphoTypeMarketConverter, getReadFailurePolicy, getResolvedDolomiteAccountNumbers, getStCeloValidatorGroups, getSubAccountAddress, getSubAccountIndex, getTermProfile, getVaultPublicDataAll, getVaultWithdrawalRequests, hasCritical, hasEulerEarnVaultSubgraph, hasLagoonVaults, hasMorphoPositionIndex, hasMorphoUserApi, hasMorphoUserSubgraph, hasUpshiftVaults, hasYearnVaults, inverseKeyParts, inverseLenderKey, isFailedCall, isStablecoinSymbol, isYearnV3, keysFromMaps, lenderApiOnly, lenderFamily, liquityCandidateTroveIds, liquityKeyParts, liquityLenderKey, llamaLendKeyParts, llamaLendLenderKey, mergeDeep, multicall3Abi, nanTo, needsLenderApproval, needsTokenApproval, noOpResult, normalizeToBytes, parseBalanceFetcherResult, parseMergedResult, parseMulticallRpcResponses, parseRawRpcBatchResponses, parseRawRpcResponses, parseTermMaxLtv, parseTokenBalanceResult, pct, positivePart, predictInverseEscrow, prepareLenderUserDataRpcCalls, prepareMergedMulticallParams, prepareMergedRpcCalls, prepareMulticallInputs, prepareTokenBalanceRpcCalls, priceGlvVaults, priceGmMarkets, probeAaveFeedTimestamps, pruneFeedStats, rankFindings, readVaultSharePrices, rejectOutliers as rejectPriceOutliers, resolveAdapter, resolveDerivation, resolveStCeloDepositGroup, resupplyKeyParts, resupplyLenderKey, riverKeyParts, riverLenderKey, selectAssetGroupPrices, shortDate, stampVaultClassification, supplyDescription, supplyFindings, supplyHeadline, tellerBpsToPercent, tellerImpliedLtv, tellerLenderKey, tellerPoolFromLenderKey, termLenderKey, termMaxApiBase, borrowNif as termMaxBorrowNif, curveApr as termMaxCurveApr, curveAprNumber as termMaxCurveAprNumber, daysToMaturity as termMaxDaysToMaturity, lendNif as termMaxLendNif, mintGtFeeRatio as termMaxMintGtFeeRatio, ratioToNumber as termMaxRatioToNumber, ratioToPercent as termMaxRatioToPercent, tickToAprNumber, tickToPrice, toDigest, toTermSheetInput, unflattenLenderData, updateFeedStats, usddIlkBytes32, usddKeyParts, usddLenderKey, validateTermSheet, validateTermSheets, validateUserData };