import BigNumber from "bignumber.js"; import { Decimal } from "../_generated/suilend/decimal/structs"; import { PoolRewardManager, UserRewardManager } from "../_generated/suilend/liquidity-mining/structs"; import { Borrow, Deposit, Obligation } from "../_generated/suilend/obligation/structs"; import { Reserve } from "../_generated/suilend/reserve/structs"; import { ReserveConfig } from "../_generated/suilend/reserve-config/structs"; import { normalizeFeedId } from "./feedId"; /** * @deprecated since version 1.0.8. Use `calculateUtilizationPercent` instead. */ export declare const calculateUtilizationRate: (reserve: Reserve) => BigNumber; export declare const calculateUtilizationPercent: (reserve: Reserve) => BigNumber; /** * @deprecated since version 1.0.8. Use `calculateBorrowAprPercent` instead. */ export declare const calculateBorrowApr: (reserve: Reserve) => BigNumber; export declare const calculateBorrowAprPercent: (reserve: Reserve) => BigNumber; /** * @deprecated since version 1.0.8. Use `calculateDepositAprPercent` instead. */ export declare const calculateSupplyApr: (reserve: Reserve) => BigNumber; export declare const calculateDepositAprPercent: (reserve: Reserve) => BigNumber; export declare const compoundReserveInterest: (reserve: Reserve, nowS: number) => Reserve; export declare const updatePoolRewardsManager: (manager: PoolRewardManager, nowMs: number) => PoolRewardManager; /** * A price read from Pyth, normalised away from any one client's response shape. * * Hermes returns `{ price: "12345", expo: -8, publish_time }`; v2's `PriceFeed` * exposed `getPriceAsNumberUnchecked()` instead. Normalising at the boundary * keeps `price * 10 ** expo` in exactly one place, and keeps the reserve-pricing * code below independent of which client fetched it. */ export interface FeedPrice { id: string; price: number; emaPrice: number; publishTimeS: number; /** Pyth's confidence interval for `price`, in the same units as `price`. */ conf?: number; } export declare const feedConfidenceOf: (reserve: Reserve) => BigNumber | undefined; export declare const withUnclampedPrices: (reserve: Reserve, price: Decimal, smoothedPrice: Decimal) => Reserve; /** Mirrors reserve.move's `price_range`. */ export declare const priceRange: (reserve: Reserve) => { low: BigNumber; high: BigNumber; }; /** Mirrors reserve.move's `unclamped_price`: the spot a reserve's conf was quoted with. */ export declare const unclampedSpot: (reserve: Reserve) => BigNumber; /** * A source of latest prices. Structural on purpose: any object with this method * qualifies, so a caller can inject its own client (authenticated, failover, * throttled) without this package depending on it. */ export interface PriceFeedSource { getLatestPriceFeeds(ids: string[]): Promise; } export { normalizeFeedId }; export declare const reservePriceFeedIds: (reserves: Reserve[]) => string[]; export declare const applyReservePriceFeeds: (reserves: Reserve[], priceFeeds: FeedPrice[] | undefined) => Reserve[]; export declare const refreshReservePrice: (reserves: Reserve[], pythConnection: PriceFeedSource) => Promise[]>; /** * A connection that can report which of the requested feeds it could not serve, * instead of failing the whole batch. Structural on purpose: any object with * this method qualifies, so a caller can inject its own client without this * package depending on it. */ export interface PartialPriceFeedSource { getLatestPriceFeedsPartial(ids: string[]): Promise<{ feeds: FeedPrice[]; missing: string[]; }>; } /** * Whether this connection can report WHICH requested feeds it could not serve. * * Exported because it is a precondition callers need to check, not just an * internal branch: `tolerateMissingPriceFeeds` is meaningless without it (see * `refreshReservesForInitialize`, which refuses the combination). */ export declare function canReportMissingPriceFeeds(connection: PriceFeedSource | PartialPriceFeedSource): connection is PartialPriceFeedSource; /** * The remedy both gap-tolerance failure paths point at. * * One constant because the two paths fail at different moments for the same * underlying reason — `refreshReservesForInitialize` refuses up front on * capability, `refreshReservePriceTolerant` only once an endpoint actually * rejects a batch — and separately worded advice for one cause is how the two * drift apart. */ export declare const PARTIAL_FETCH_REQUIRED_HINT = "inject a connection implementing getLatestPriceFeedsPartial (e.g. a failover connection that reports per-feed gaps)"; /** * Like `refreshReservePrice`, but a reserve whose feed no endpoint can serve * keeps the price already in its on-chain state instead of failing every other * reserve with it. * * `refreshReservePrice` is all-or-nothing in both directions, and both are * hazardous once an endpoint stops carrying every feed: a missing feed throws * (so one retired market blinds pricing for the whole lending market), while an * undefined response silently returns EVERY reserve at its stale price with no * signal at all. Hermes v2 rejects an entire batch when any requested id is * unknown, which makes both reachable in practice. * * This reports `unpricedCoinTypes` rather than deciding what an unpriceable * reserve means — that judgement needs reserve config the caller has and this * function should not second-guess. Nothing is ever fabricated here. * * TWO THINGS A CALLER MUST KNOW: * * 1. An unpriced reserve is returned in `reserves` carrying whatever price its * on-chain state holds — which may be ZERO for a reserve that was never * priced on-chain. Folding `reserves` into collateral or borrow-limit math * without first excluding `unpricedCoinTypes` therefore prices that reserve * at literal zero. `unpricedCoinTypes` is the only thing distinguishing a * real price from a kept one; it is not optional to read. * 2. Tolerance requires a connection that can REPORT a gap * (`getLatestPriceFeedsPartial`). Handed a plain all-or-nothing source, * this falls back to the all-or-nothing fetch, which Hermes v2 answers with * a 404 for the whole batch when any id is unknown — so there is nothing * left to be tolerant with. That fetch is kept because it is correct for an * endpoint that OMITS unknown ids rather than rejecting the batch, but the * failure is re-thrown with attribution rather than surfacing as a bare HTTP * error. `refreshReservesForInitialize` rejects the combination outright. */ export declare const fetchReservePriceFeedsTolerant: (reserves: Reserve[], pythConnection: PriceFeedSource | PartialPriceFeedSource) => Promise; export declare const applyReservePriceFeedsTolerant: (reserves: Reserve[], priceFeeds: FeedPrice[] | undefined) => { reserves: Reserve[]; unpricedCoinTypes: string[]; }; export declare const refreshReservePriceTolerant: (reserves: Reserve[], pythConnection: PriceFeedSource | PartialPriceFeedSource) => Promise<{ reserves: Reserve[]; unpricedCoinTypes: string[]; }>; export declare const updateUserRewardManager: (poolManager: PoolRewardManager, userRewardManager: UserRewardManager, nowMs: number) => UserRewardManager; export declare const findReserveOrThrow: (reserves: Reserve[], coinType: string) => Reserve; export declare const computeBorrowWeight: (config: ReserveConfig) => BigNumber; export declare const computeCloseLtv: (config: ReserveConfig) => BigNumber; export type ObligationRefresh = { deposits: Deposit[]; borrows: Borrow[]; userRewardManagers: UserRewardManager[]; depositedValueUsd: BigNumber; allowedBorrowValueUsd: BigNumber; unhealthyBorrowValueUsd: BigNumber; unweightedBorrowedValueUsd: BigNumber; weightedBorrowedValueUsd: BigNumber; weightedBorrowedValueUpperBoundUsd: BigNumber; }; /** * `conf / spot` from a single price read: what a reserve stores in * `ConfidenceKey`. Branded so a USD conf cannot be passed where a share is * expected; build one with `confidenceShare`. */ export type ConfidenceShare = BigNumber & { readonly __brand: "ConfidenceShare"; }; /** Mirrors reserve.move's `record_confidence`: a zero spot records a zero share. */ export declare const confidenceShare: (conf: BigNumber, spot: BigNumber) => ConfidenceShare; export type ConfidenceByCoinType = Map; export declare const normalizeConfidenceByCoinType: (confidenceByCoinType: ConfidenceByCoinType) => ConfidenceByCoinType; export declare const isRefreshedFromFeed: (reserve: Reserve) => boolean; export declare const resolveConfidence: (entry: { share: ConfidenceShare | undefined; kBps: BigNumber; } | undefined, reserve: Reserve) => { share: ConfidenceShare; kBps: BigNumber; } | undefined; /** * Pure computation half of `refreshObligation`: same math, but returns fresh * deposits/borrows/reward-manager arrays and the five USD aggregates instead * of writing them onto `obligation`. Never touches `obligation` or any of its * nested arrays — `refreshObligation`'s in-place `obligation.deposits[i] = ...` * mutated the caller's original array too (only the top-level object was * shallow-copied), silently corrupting `unrefreshedObligation` for anyone still * holding a reference to it. */ export declare function computeObligationRefresh(obligation: Obligation, refreshedReserves: Reserve[], nowMs: number, confidenceByCoinType?: ConfidenceByCoinType): ObligationRefresh; export declare const refreshObligation: (unrefreshedObligation: Obligation, refreshedReserves: Reserve[], confidenceByCoinType?: ConfidenceByCoinType) => Obligation; export declare const numberToDecimal: (value: number) => Decimal; export declare const stringToDecimal: (value: string) => Decimal; export declare const decimalToBigNumber: (value: Decimal) => BigNumber; /** * Mirrors reserve.move's `price_lower_bound`/`price_upper_bound`: the one * off-chain place this formula lives. `low` and `high` are the ends of the * reserve's price range (`priceRange`), which the bounds widen by * `k * share`. `share` is `conf / spot` from a single price read -- what the * reserve stores in `ConfidenceKey` -- and `kBps` is its `ReserveConfig` * confidence multiplier (in bps, e.g. 30000 for a logical k=3); both are * plain human-scale BigNumbers, not WAD-scaled Decimals. `kBps=0` or * `share=0` disables the margin entirely: both bounds collapse to `low` and * `high`. */ export declare const applyConfidenceMargin: (low: BigNumber, high: BigNumber, share: ConfidenceShare, kBps: BigNumber) => { lowerBound: BigNumber; upperBound: BigNumber; }; export declare const getCTokenMarketValue: (reserve: Reserve, depositedCTokenAmount: BigNumber) => BigNumber; export declare const getCTokenMarketValueLowerBound: (reserve: Reserve, depositedCTokenAmount: BigNumber, share?: ConfidenceShare, kBps?: BigNumber) => BigNumber; export declare const cTokenRatio: (reserve: Reserve) => BigNumber; export declare const totalSupply: (reserve: Reserve) => BigNumber; export declare const compoundBorrowedAmount: (snapshotAmount: BigNumber, currentCumulativeBorrowRate: BigNumber, snapshotCumulativeBorrowRate: BigNumber) => BigNumber; /** * @deprecated Rounds the compounded amount through `stringToDecimal` * (BigNumber -> JS `number` -> `Math.round(value * WAD)`), which is lossy at * WAD scale (LEN-745) — don't feed this into further aggregate/liquidation * math. Kept only because `@suilend/sdk/utils/simulate` is externally * reachable and deleting it would be a breaking change. For an exact * compounded amount, call `compoundBorrowedAmount` directly instead. */ export declare const compoundDebt: (borrow: Borrow, reserve: Reserve) => Borrow; export type ObligationHealthDepositInput = { /** Matches `reserve.coinType` (unprefixed struct tag) — same field `refreshObligation` compares deposits against. */ coinType: string; /** Raw ctoken unit count, i.e. `deposit.depositedCtokenAmount` as stored (Positions.quantity for a deposit row). */ depositedCtokenAmount: BigNumber; }; export type ObligationHealthBorrowInput = { coinType: string; /** Human-scale underlying amount as of the stored snapshot (Positions.quantity for a borrow row — already ÷ WAD at write time, see dbClient.ts writeCachedObligations). */ borrowedAmountUnderlying: BigNumber; /** Human-scale cumulative borrow rate factor as of the stored snapshot (Positions.cumulativeBorrowRate, a raw WAD integer, ÷ WAD). */ cumulativeBorrowRateAtSnapshot: BigNumber; }; export type ObligationHealth = { depositedValueUsd: BigNumber; unhealthyBorrowValueUsd: BigNumber; weightedBorrowedValueUsd: BigNumber; }; /** * The health-relevant subset of `refreshObligation` (deposit/borrow valuation * and the two USD thresholds `shouldAttemptLiquidations` compares), computed * from stored position state rather than a live on-chain `Obligation`. * * Deliberately does NOT touch reward-manager state (`refreshObligation`'s * `updateUserRewardManager` calls) — that state isn't stored in `Positions` * and doesn't participate in either threshold (LEN-576). Deliberately does * NOT compute `allowedBorrowValueUsd`/`unweightedBorrowedValueUsd`/ * `weightedBorrowedValueUpperBoundUsd` — those exist for the risky-subset * re-scan optimization in `obligationIsRisky`, which has no equivalent here * (this function is cheap enough to run over every obligation every cycle). * * All math stays in BigNumber end-to-end — never construct a `Decimal` from * a stored quantity; `numberToDecimal`/`stringToDecimal` round through a JS * `number` (`Math.round`) and are lossy at WAD scale. `decimalToBigNumber` is * only ever called on a genuine on-chain `Reserve` field, in * `buildReserveHealthTable`. * * Per-position operation order matches `getCTokenMarketValue` and * `compoundBorrowedAmount` exactly, so the result is bit-identical to * recomputing every reserve value per position; reordering the divisions * would round differently and can flip a verdict at the threshold. */ export declare function computeObligationHealth(deposits: ObligationHealthDepositInput[], borrows: ObligationHealthBorrowInput[], reserveHealth: ReserveHealthTable): ObligationHealth; export type ReserveDepositHealthValues = { price: BigNumber; cTokenRatio: BigNumber; mintScale: BigNumber; closeLtv: BigNumber; }; export type ReserveBorrowHealthValues = { price: BigNumber; mintScale: BigNumber; cumulativeBorrowRate: BigNumber; borrowWeight: BigNumber; }; /** * Each lookup throws, as `findReserveOrThrow` does, when no reserve has * this coin type. The two sides are separate so a reserve is only read for * the fields its positions actually use. */ export type ReserveHealthTable = { deposit: (coinType: string) => ReserveDepositHealthValues; borrow: (coinType: string) => ReserveBorrowHealthValues; }; /** * The per-reserve inputs to `computeObligationHealth`, derived once per * reserve and side on first lookup instead of once per position. Build one * per pass over a fixed set of priced reserves and drop it after: the values * are a snapshot, and a caller that later rewrites a reserve's price in place * would otherwise keep reading the old one. */ export declare function buildReserveHealthTable(reserves: Reserve[]): ReserveHealthTable; export declare function computeObligationHealthFromPositions(deposits: ObligationHealthDepositInput[], borrows: ObligationHealthBorrowInput[], refreshedReserves: Reserve[]): ObligationHealth;