@xoxno/sdk-js 1.0.217 → 1.0.218

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.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Fixture-driven tests for `computeAccountRisk`, the limit helpers and the
3
+ * projections. Expected health factors are worked by hand from
4
+ * `rs-lending-xlm/docs/reference/formulas.md` §Valuation and health:
5
+ * value_usd = round(round(round(shares·index/RAY)/1e9)·price/WAD)
6
+ * ltv_collateral = Σ floor(floor-value · min(ltv, threshold) / BPS)
7
+ * weighted_collateral = Σ floor(floor-value · threshold / BPS)
8
+ * debt = Σ ceil-value; HF = floor(weighted_collateral · WAD / debt)
9
+ */
10
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Parity between the SDK math and the xoxno-ui mirrors it replaces. The UI
3
+ * functions are re-implemented inline below as the oracle, ported verbatim
4
+ * (BigNumber integer ops → BigInt, floats kept as floats) from:
5
+ * - xoxno-ui/src/modules/lending/stellar-scaled-math.ts
6
+ * (`unscaleSupplyFloor`, `unscaleBorrowCeil`)
7
+ * - xoxno-ui/src/modules/lending/utils.ts
8
+ * (`getBorrowApy`, `getSupplyApy`, `getNewCollateral` + `getHealthPercentage`,
9
+ * `calculateMaxBorrow` cap headroom)
10
+ * - xoxno-ui/src/modules/lending/stellar-limits.ts
11
+ * (`resolveMaxUtilization`, `utilCapBorrowUsd`, `utilCapWithdrawUsd`)
12
+ * Inputs are random but deterministic (mulberry32, fixed seed).
13
+ */
14
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Hand-checked unit tests for the fixed-point primitives (`scaled.ts`) and the
3
+ * rate curve (`rates.ts`). Expected values are worked by hand from
4
+ * `rs-lending-xlm/common/src/math/fp_core.rs` and `common/src/rates/curve.rs`.
5
+ */
6
+ export {};
@@ -18,5 +18,6 @@ export * from './prepare.js';
18
18
  export * from './position-mode.js';
19
19
  export * from './repay-swap.js';
20
20
  export * from './cctp.js';
21
+ export * from './math/index.js';
21
22
  export type { StellarStrategySwapInput, StellarStrategySwapPathInput, StellarStrategySwapHopInput, } from './scval-encode.js';
22
23
  export { mapSorobanError } from '../../utils/soroban-errors.js';
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Account valuation and risk — the client mirror of
3
+ * `rs-lending-xlm/contracts/controller/src/risk/totals.rs`
4
+ * (`calculate_account_risk_totals`) and the post-action gate in
5
+ * `contracts/controller/src/risk/validation.rs` (`require_post_pool_risk_gates`),
6
+ * as specified in `docs/reference/formulas.md` §Valuation and health.
7
+ *
8
+ * Inputs are the generated API DTOs; all integer math runs in `BigInt` on the
9
+ * raw string fields. Outputs carry both the raw WAD strings and display numbers.
10
+ */
11
+ import type { AccountPositionDto, ReserveDto, StellarLendingLiveStateDto, StellarMarketIndexByHub } from '../lending-api-types.js';
12
+ /**
13
+ * `common/src/constants/shared.rs::DEFAULT_MIN_BORROW_COLLATERAL_USD_WAD` —
14
+ * the floor `get_min_borrow_collateral_usd` returns until governance changes
15
+ * it. Used only when `liveState.minBorrowCollateralUsdWad` is absent or null.
16
+ */
17
+ export declare const DEFAULT_MIN_BORROW_COLLATERAL_USD_WAD: bigint;
18
+ export type PositionSide = 'supply' | 'borrow';
19
+ /** One valued hub-asset leg of an account. */
20
+ export type PositionValue = {
21
+ side: PositionSide;
22
+ hubId: number;
23
+ asset: string;
24
+ decimals: number;
25
+ /** Scaled shares (RAY) the valuation started from. */
26
+ scaledRay: string;
27
+ /** Index (RAY) applied to the shares. */
28
+ indexRay: string;
29
+ /** USD price per whole token (WAD) applied. */
30
+ usdPriceWad: string;
31
+ usdPrice: number;
32
+ /** Token base units: supply = `unscale_supply_floor`, borrow = `unscale_borrow_ceil`. */
33
+ amountBase: string;
34
+ amountShort: number;
35
+ /** USD (WAD): supply = half-up `position_value` (feeds `total_collateral`); borrow = `position_value_ceil` (risk debt). */
36
+ usdWad: string;
37
+ usd: number;
38
+ /** USD (WAD) the risk gates weight: supply = `position_value_floor`; borrow = same as `usdWad`. */
39
+ gateUsdWad: string;
40
+ entryLtvBps: number;
41
+ entryLiquidationThresholdBps: number;
42
+ entryLiquidationBonusBps: number;
43
+ entryLiquidationFeesBps: number;
44
+ /** Supplies: `floor(gateUsd * min(entryLtv, entryThreshold) / BPS)`; borrows: "0". */
45
+ ltvWeightedUsdWad: string;
46
+ /** Supplies: `floor(gateUsd * entryThreshold / BPS)`; borrows: "0". */
47
+ liquidationWeightedUsdWad: string;
48
+ /** Both the index and the price came from an accepted live-state row. */
49
+ liveDataUsed: boolean;
50
+ };
51
+ export type AccountRisk = {
52
+ accountId: string;
53
+ owner: string;
54
+ spokeId: number;
55
+ /** `AccountPositionDto.positionMode`: 0 normal, 1 multiply, 2 long, 3 short. */
56
+ mode: number;
57
+ supplies: PositionValue[];
58
+ borrows: PositionValue[];
59
+ /** Unweighted collateral, half-up (`AccountRiskTotals.total_collateral`). */
60
+ totalCollateralUsdWad: string;
61
+ totalCollateralUsd: number;
62
+ /** Floor-valued collateral weighted by stored `min(LTV, threshold)` (`ltv_collateral`). */
63
+ ltvWeightedCollateralUsdWad: string;
64
+ ltvWeightedCollateralUsd: number;
65
+ /** Floor-valued collateral weighted by stored liquidation threshold (`weighted_collateral`). */
66
+ liquidationWeightedCollateralUsdWad: string;
67
+ liquidationWeightedCollateralUsd: number;
68
+ /** Ceil-valued debt (`total_debt`). */
69
+ totalDebtUsdWad: string;
70
+ totalDebtUsd: number;
71
+ /** Contract convention `floor(weighted_collateral * WAD / debt)`; null when debt-free (the contract returns `i128::MAX`). */
72
+ healthFactorWad: string | null;
73
+ healthFactor: number | null;
74
+ /** UI convention: `debt / threshold-weighted collateral × 100`; higher is riskier, 0 without collateral. */
75
+ healthPercent: number;
76
+ /** Debt the LTV gate admits: `ltv_collateral` (`require_post_pool_risk_gates`). */
77
+ borrowLimitUsdWad: string;
78
+ borrowLimitUsd: number;
79
+ /** `max(0, borrowLimit - debt)`; 0 when `ltv_collateral` is below the min-collateral floor. */
80
+ availableBorrowUsdWad: string;
81
+ availableBorrowUsd: number;
82
+ /** Floor applied while debt remains (`get_min_borrow_collateral_usd`). */
83
+ minBorrowCollateralUsdWad: string;
84
+ minBorrowCollateralUsd: number;
85
+ /** Debt > 0 and health factor < 1 WAD. */
86
+ isLiquidatable: boolean;
87
+ /** `liveState` was supplied and every position was valued from an accepted live row. */
88
+ liveDataValid: boolean;
89
+ };
90
+ export type ReserveInput = Record<string, ReserveDto> | ReserveDto[];
91
+ /** Key used by `StellarLendingContextDto.reserveDetailsByKey`. */
92
+ export declare const reserveKey: (spokeId: number, hubId: number, asset: string) => string;
93
+ /** Find the reserve backing a `(spokeId, hubId, asset)` coordinate; throws when missing. */
94
+ export declare function resolveReserve(reserves: ReserveInput, spokeId: number, hubId: number, asset: string): ReserveDto;
95
+ export type LiveSnapshot = {
96
+ /** Accepted index rows keyed `${hubId}:${asset}`. */
97
+ indexes: Map<string, StellarMarketIndexByHub>;
98
+ /** Accepted USD prices (WAD) keyed by asset. */
99
+ prices: Map<string, bigint>;
100
+ rejectedIndexKeys: Set<string>;
101
+ rejectedAssets: Set<string>;
102
+ };
103
+ export declare const liveIndexKey: (hubId: number, asset: string) => string;
104
+ /** Build the divergence-guarded snapshot from `liveState.indexes`. */
105
+ export declare function buildLiveSnapshot(liveState: StellarLendingLiveStateDto | undefined): LiveSnapshot;
106
+ type PositionSeed = {
107
+ side: PositionSide;
108
+ hubId: number;
109
+ asset: string;
110
+ decimals: number;
111
+ scaledRay: bigint;
112
+ indexRay: bigint;
113
+ priceWad: bigint;
114
+ entryLtvBps: number;
115
+ entryLiquidationThresholdBps: number;
116
+ entryLiquidationBonusBps: number;
117
+ entryLiquidationFeesBps: number;
118
+ liveDataUsed: boolean;
119
+ };
120
+ /**
121
+ * Value scaled shares exactly as `calculate_account_risk_totals` does:
122
+ * supply → `position_value` (half-up display/total) and
123
+ * `position_value_floor` (gate); borrow → `position_value_ceil`.
124
+ */
125
+ export declare function valueScaledPosition(seed: PositionSeed): PositionValue;
126
+ /**
127
+ * Shares minted when `amountBase` enters a leg — `scaling.rs`
128
+ * `calculate_scaled_supply` (floor) / `calculate_scaled_borrow` (ceil), the
129
+ * only entry conversions the pool performs (`ops/supply.rs`, `ops/borrow.rs`).
130
+ */
131
+ export declare function mintShares(side: PositionSide, amountBase: bigint, decimals: number, indexRay: bigint): bigint;
132
+ /**
133
+ * Shares burned by a partial exit of `amountBase` — `formulas.md` rounding
134
+ * table: "Partial withdrawal burn: ceil", "Partial repayment burn: floor".
135
+ * Full closes (withdraw ≥ half-up balance, repay ≥ ceil debt) burn every
136
+ * share instead; callers handle that boundary.
137
+ */
138
+ export declare function burnShares(side: PositionSide, amountBase: bigint, decimals: number, indexRay: bigint): bigint;
139
+ /**
140
+ * Value a fresh leg entered with `amountBase`: shares come from
141
+ * {@link mintShares} and the valuation runs on those shares exactly as
142
+ * {@link valueScaledPosition} does (`position_value_*` never sees the base
143
+ * amount). `amountBase` on the result is therefore the share-derived balance,
144
+ * which can differ from the input by rounding when the index is not `RAY`.
145
+ */
146
+ export declare function valueBasePosition(seed: Omit<PositionSeed, 'scaledRay'>, amountBase: bigint): PositionValue;
147
+ export type AccountIdentity = Pick<AccountRisk, 'accountId' | 'owner' | 'spokeId' | 'mode'>;
148
+ /**
149
+ * Fold valued positions into the contract's risk totals and the derived
150
+ * limits. `formulas.md`: `health_factor_wad = floor(weighted_collateral_wad *
151
+ * WAD / debt_wad)`; the post-action gate requires `debt <= ltv_collateral`,
152
+ * `HF >= 1 WAD` and `ltv_collateral >= floor` while debt remains.
153
+ */
154
+ export declare function aggregateAccountRisk(identity: AccountIdentity, supplies: PositionValue[], borrows: PositionValue[], minBorrowCollateralUsdWad: bigint, liveDataValid: boolean): AccountRisk;
155
+ export type ComputeAccountRiskInput = {
156
+ /** Every row of one account (same `accountId`), as `/accounts/{id}/positions` returns them. */
157
+ positions: AccountPositionDto[];
158
+ /** `context.reserveDetailsByKey` (keyed `${spokeId}:${hubId}:${asset}`) or a reserve array. */
159
+ reserves: ReserveInput;
160
+ /** `/live-state`: live indexes, prices and the min-collateral floor. Optional; positions then fall back to their own applied indexes and the reserve price. */
161
+ liveState?: StellarLendingLiveStateDto;
162
+ };
163
+ /**
164
+ * Value one account exactly as the controller's risk gates do.
165
+ *
166
+ * - Index: live-state row for `(hubId, asset)` when it passed the divergence
167
+ * guard, else the position's `liveSupplyIndexRay` / `liveBorrowIndexRay`,
168
+ * then the reserve's `live*IndexRay`, and last the position's stored
169
+ * `supplyIndexRay` / `borrowIndexRay` (undocumented in the API schema; the
170
+ * documented fields say "stored index on fallback", so it is treated as the
171
+ * stalest snapshot). The contract accrues to the current index before every
172
+ * gate, so fresher candidates win.
173
+ * - Price: live-state `usdPrice` (WAD) when accepted, else `reserve.usdPrice`
174
+ * converted to WAD.
175
+ * - Weights: the position's own `entry*Bps`, as the contract stores them.
176
+ * - Min-collateral floor: `liveState.minBorrowCollateralUsdWad`, defaulting
177
+ * to 5 USD (`DEFAULT_MIN_BORROW_COLLATERAL_USD_WAD`) when absent or null.
178
+ *
179
+ * Throws when `positions` is empty, mixes account ids, or names a reserve
180
+ * missing from `reserves`.
181
+ */
182
+ export declare function computeAccountRisk(input: ComputeAccountRiskInput): AccountRisk;
183
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Stellar lending math: contract-exact share conversion, rate curve, account
3
+ * risk, per-action limits and projections in native `BigInt`. Ground truth is
4
+ * `rs-lending-xlm` (`common/src/rates/*.rs`, `contracts/controller/src/risk/*`,
5
+ * `docs/reference/formulas.md`); each function names the source it mirrors.
6
+ * @module Stellar lending math
7
+ */
8
+ export * from './scaled.js';
9
+ export * from './rates.js';
10
+ export * from './account.js';
11
+ export * from './limits.js';
12
+ export * from './projection.js';
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Largest valid amount per action, in token base units, with the reason that
3
+ * binds. Mirrors the gates the contract enforces rather than capping:
4
+ *
5
+ * - risk gate: `contracts/controller/src/risk/validation.rs`
6
+ * `require_post_pool_risk_gates` (debt ≤ `min(LTV, threshold)`-weighted
7
+ * collateral, HF ≥ 1, `ltv_collateral ≥ min_borrow_collateral_usd`);
8
+ * - listing flags: `positions/mod.rs::enforce_spoke_asset_flags`
9
+ * (`paused` blocks every verb, `frozen` blocks entry only);
10
+ * - spoke caps: `formulas.md` §Caps (native units, no unlimited sentinel);
11
+ * - pool cash and the 200 BPS liquidation buffer:
12
+ * `contracts/pool/src/guards.rs::require_liquidation_buffer`;
13
+ * - hub utilization ceiling: `guards.rs::require_utilization_below_max`
14
+ * (skipped when `max_utilization >= RAY`; repay and liquidation are exempt).
15
+ *
16
+ * Hub totals and pool cash come from the `*Short` display numbers on
17
+ * `ReserveDto`, so cap / liquidity / utilization candidates are exact only to
18
+ * the double precision of those fields; the risk-gate candidate is exact.
19
+ * xoxno-ui parity: `stellar-limits.ts::computeStellarLimits`,
20
+ * `utils.ts::calculateMaxBorrow / calculateMaxSupply / calculateMaxRemove`.
21
+ */
22
+ import type { ReserveDto, StellarLendingLiveStateDto } from '../lending-api-types.js';
23
+ import type { AccountRisk } from './account.js';
24
+ export type LimitReason = 'healthFactor' | 'ltv' | 'cap' | 'liquidity' | 'utilizationCap' | 'minCollateral' | 'paused' | 'frozen' | null;
25
+ export type Limit = {
26
+ /** Token base units. For a full-close withdraw this is the floor payout; sign `"0"` (the builder's withdraw-all sentinel) instead. */
27
+ amountBase: bigint;
28
+ amountShort: number;
29
+ /** The amount closes the whole position (withdraw: pays the floor balance; repay: burns every debt share and refunds excess). */
30
+ isFullClose: boolean;
31
+ /** Which gate produced `amountBase`; null when only the position size bounds it. */
32
+ reason: LimitReason;
33
+ /** Hub utilization (0..1) after the action at `amountBase`; null when not applicable. */
34
+ projectedUtilization: number | null;
35
+ };
36
+ /** `common/src/constants/pool.rs::LIQUIDATION_BUFFER_BPS`. */
37
+ export declare const LIQUIDATION_BUFFER_BPS = 200n;
38
+ /**
39
+ * Largest borrow of `reserve` the account can open now. Candidates, all in
40
+ * base units:
41
+ *
42
+ * - LTV gate: the contract mints `calculate_scaled_borrow` (ceil) shares,
43
+ * merges them into the existing debt leg and requires
44
+ * `ltv_collateral >= total_debt` with the merged leg valued by
45
+ * `position_value_ceil` (`ops/borrow.rs::mint_debt`, `risk/totals.rs`,
46
+ * `risk/validation.rs`). The search runs on exactly that predicate, with
47
+ * the index and price `computeAccountRisk` used for the leg (else the
48
+ * accepted live row, else `reserve.liveBorrowIndexRay` / `reserve.usdPrice`).
49
+ * - min-collateral floor, spoke borrow-cap headroom, pool cash minus the
50
+ * liquidation buffer, and the hub utilization ceiling.
51
+ *
52
+ * A reserve that is not borrowable reports 0 with reason `cap` (same effect
53
+ * as a zero `borrowCap`).
54
+ */
55
+ export declare function maxBorrow(risk: AccountRisk, reserve: ReserveDto, liveState?: StellarLendingLiveStateDto): Limit;
56
+ /**
57
+ * Largest withdraw of the account's `reserve` supply. Candidates: the risk
58
+ * gate (LTV-weighted collateral must stay ≥ `max(debt, floor)` — reported as
59
+ * `healthFactor`, or `minCollateral` when the floor is the stricter bound),
60
+ * pool cash, and the hub utilization ceiling. `paused` reports 0. When the
61
+ * whole position can go, `amountBase` is the floor payout and `isFullClose`
62
+ * is true — sign `"0"` so the contract takes the withdraw-all path
63
+ * (`resolve_withdrawal` treats any request ≥ the half-up balance as full).
64
+ * The position is priced as `risk` valued it, so `_liveState` is accepted for
65
+ * signature symmetry with {@link maxBorrow} but not consulted.
66
+ */
67
+ export declare function maxWithdraw(risk: AccountRisk, reserve: ReserveDto, _liveState?: StellarLendingLiveStateDto): Limit;
68
+ /**
69
+ * Largest supply into `reserve`: the spoke supply-cap headroom
70
+ * (`supplyCap - spoke supplied`, always enforced, `0` closes the side).
71
+ * `paused` and `frozen` both block entry.
72
+ */
73
+ export declare function maxSupply(reserve: ReserveDto): Limit;
74
+ /**
75
+ * Largest repay of the account's `reserve` debt: the ceil-rounded debt
76
+ * (`unscale_borrow_ceil`, which `resolve_repay` treats as a full close and
77
+ * refunds anything above), bounded by `walletBalance` (base units) when
78
+ * given — reported as `liquidity`. `paused` reports 0.
79
+ */
80
+ export declare function maxRepay(risk: AccountRisk, reserve: ReserveDto, walletBalance?: string | bigint): Limit;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * What-if projections: account risk after hypothetical position changes and
3
+ * reserve rates after hypothetical pool flows. Reuses the same aggregation
4
+ * (`aggregateAccountRisk`) and curve (`rates.ts`) as the live computations.
5
+ * xoxno-ui parity: `utils.ts::getProjectedHealthPercentage`,
6
+ * `getProjectedUtilization`, `getApyProjection`.
7
+ */
8
+ import type { ReserveDto } from '../lending-api-types.js';
9
+ import type { AccountRisk, PositionSide } from './account.js';
10
+ export type PositionDelta = {
11
+ side: PositionSide;
12
+ hubId: number;
13
+ asset: string;
14
+ /** Signed token base units: positive supplies/borrows, negative withdraws/repays. */
15
+ amountBase: bigint | string;
16
+ /** Required when the account has no such position yet: decimals, `usdPrice` and the `collateralFactorBps` / `liquidationThresholdBps` a new position would snapshot. */
17
+ reserve?: ReserveDto;
18
+ };
19
+ /**
20
+ * Re-value the account after applying `deltas`, moving shares the way the
21
+ * pool does. Existing legs change in shares (`mintShares` on entry,
22
+ * `burnShares` on a partial exit; a withdraw ≥ the half-up balance or a
23
+ * repay ≥ the ceil debt burns every share) and are revalued with
24
+ * `valueScaledPosition`; a new leg is minted from `amountBase`
25
+ * (`valueBasePosition`) and takes its index, price and entry weights from
26
+ * `delta.reserve` (no live price: pass the reserve you would supply into).
27
+ * A leg driven to zero is dropped. The min-collateral floor and
28
+ * `liveDataValid` carry over from `risk`.
29
+ */
30
+ export declare function projectAccountRisk(risk: AccountRisk, deltas: PositionDelta[]): AccountRisk;
31
+ export type ReserveApyProjection = {
32
+ /** Hub utilization after the flows, 0..1. */
33
+ utilization: number;
34
+ utilizationRay: string;
35
+ /** Annual RAY rates from the curve. */
36
+ borrowAprRay: string;
37
+ supplyAprRay: string;
38
+ /** Simple annual rates as fractions (`0.05` = 5%). */
39
+ borrowApr: number;
40
+ supplyApr: number;
41
+ /** Continuously compounded `e^apr - 1`, fractions. */
42
+ borrowApy: number;
43
+ supplyApy: number;
44
+ };
45
+ /**
46
+ * Hub rates after adding `supplyDeltaBase` / `borrowDeltaBase` (signed base
47
+ * units) to the reserve's hub-pool totals. Utilization is
48
+ * `curve.rs::utilization` over the projected totals; rates follow
49
+ * `annualBorrowRateRay` / `depositRateRay`. Whether `ReserveDto.supplyApy`
50
+ * / `borrowApy` are simple or compounded is not pinned by the API contract,
51
+ * so both `*Apr` and `*Apy` are returned.
52
+ */
53
+ export declare function projectReserveApy(reserve: ReserveDto, supplyDeltaBase?: bigint | string, borrowDeltaBase?: bigint | string): ReserveApyProjection;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Interest-rate curve mirror of `rs-lending-xlm/common/src/rates/curve.rs`.
3
+ * Annual RAY in, annual RAY out; `Ray::mul` / `Ray::div` are half-up
4
+ * (`fp.rs`), matching the pool's view getters.
5
+ */
6
+ import type { ReserveIrmCurveDto } from '../lending-api-types.js';
7
+ /**
8
+ * `curve.rs::utilization` — `borrowed / supplied` half-up in RAY; zero when
9
+ * nothing is supplied. The contract feeds it half-up-valued debt and supply
10
+ * (`formulas.md` §Utilization and annual rates).
11
+ */
12
+ export declare function utilizationRay(borrowedRay: bigint, suppliedRay: bigint): bigint;
13
+ /**
14
+ * `curve.rs::calculate_annual_borrow_rate` — three-segment piecewise-linear
15
+ * curve. Utilization is clamped to one RAY; below `mid` the rate ramps from
16
+ * `base` by `slope1`, between `mid` and `optimal` by `slope2`, above
17
+ * `optimal` by `slope3`; the result is capped at `maxBorrowRate`.
18
+ */
19
+ export declare function annualBorrowRateRay(utilization: bigint, irm: ReserveIrmCurveDto): bigint;
20
+ /**
21
+ * `curve.rs::calculate_deposit_rate` — supplier rate after the reserve factor:
22
+ * `half_up(half_up(u * borrowRate / RAY) * (BPS - rf) / BPS)`. Zero at zero
23
+ * utilization or a reserve factor outside `0..BPS`.
24
+ */
25
+ export declare function depositRateRay(utilization: bigint, borrowRateRay: bigint, reserveFactorBps: number): bigint;
26
+ /** Annual RAY rate → simple APR as a fraction (`0.05` = 5%). */
27
+ export declare const rayAprToNumber: (rateRay: bigint) => number;
28
+ /**
29
+ * Annual RAY rate → continuously compounded APY fraction, `e^apr − 1`. The
30
+ * pool compounds per millisecond with an eighth-order Taylor series
31
+ * (`compound.rs`), which the continuous exponential approximates to well
32
+ * within display precision.
33
+ */
34
+ export declare const rayAprToApy: (rateRay: bigint) => number;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Contract-exact fixed-point primitives for the Stellar lending math module.
3
+ *
4
+ * Mirrors `rs-lending-xlm/common/src/math/fp_core.rs` (`mul_div_*`,
5
+ * `rescale_*`, `div_by_int_half_up`), `common/src/math/fp.rs` (`Ray`, `Wad`)
6
+ * and `common/src/rates/scaling.rs` (`unscale_supply_floor`,
7
+ * `unscale_borrow_ceil`). All arithmetic is native `BigInt` on the raw decimal
8
+ * strings the API serves; nothing here touches floating point except the
9
+ * `*Short` display converters at the bottom.
10
+ *
11
+ * Every helper assumes the contract's non-negative domain (`Ray`, `Wad` and
12
+ * position balances are never negative on-chain); {@link toBigInt} enforces
13
+ * that at the string boundary.
14
+ */
15
+ /** 10^27 — shares, indexes, rates and utilization. */
16
+ export declare const RAY: bigint;
17
+ /** 10^18 — USD values, prices per whole token, health factor. */
18
+ export declare const WAD: bigint;
19
+ /** 10,000 — risk ratios and fees. */
20
+ export declare const BPS = 10000n;
21
+ export declare const RAY_DECIMALS = 27;
22
+ export declare const WAD_DECIMALS = 18;
23
+ /** Parse a raw integer string (or bigint/number) into a non-negative bigint. */
24
+ export declare function toBigInt(value: string | number | bigint, label?: string): bigint;
25
+ /** `fp_core::mul_div_floor` — floor(x * y / d) for non-negative operands. */
26
+ export declare function mulDivFloor(x: bigint, y: bigint, d: bigint): bigint;
27
+ /** `fp_core::mul_div_ceil` — ceil(x * y / d) for non-negative operands. */
28
+ export declare function mulDivCeil(x: bigint, y: bigint, d: bigint): bigint;
29
+ /** `fp_core::mul_div_half_up` — floor((x * y + d/2) / d) for non-negative operands. */
30
+ export declare function mulDivHalfUp(x: bigint, y: bigint, d: bigint): bigint;
31
+ /** `fp_core::div_by_int_half_up` — a / b rounded half up (non-negative a, b > 0). */
32
+ export declare function divByIntHalfUp(a: bigint, b: bigint): bigint;
33
+ /** `fp_core::rescale_floor` — move `a` between decimal scales, truncating when dropping digits. */
34
+ export declare function rescaleFloor(a: bigint, fromDecimals: number, toDecimals: number): bigint;
35
+ /** `fp_core::rescale_ceil` — like {@link rescaleFloor} but rounds a dropped remainder up. */
36
+ export declare function rescaleCeil(a: bigint, fromDecimals: number, toDecimals: number): bigint;
37
+ /** `fp_core::rescale_half_up` — like {@link rescaleFloor} but rounds a dropped remainder half up. */
38
+ export declare function rescaleHalfUp(a: bigint, fromDecimals: number, toDecimals: number): bigint;
39
+ export type Rounding = 'floor' | 'ceil' | 'halfUp';
40
+ /**
41
+ * Scaled shares → token base units with one rounding direction at both steps.
42
+ * `scaling.rs`: `unscale_supply_floor` = `mul_floor` + `to_asset_floor`,
43
+ * `unscale_borrow_ceil` = `mul_ceil` + `to_asset_ceil`, `unscale_supply` /
44
+ * `unscale_borrow` (displayed balance) = `mul` (half-up) + `to_asset` (half-up).
45
+ */
46
+ export declare function unscale(scaledRay: bigint, indexRay: bigint, decimals: number, rounding: Rounding): bigint;
47
+ /**
48
+ * Current supplied amount in token base units — `scaling.rs::unscale_supply_floor`
49
+ * (`mul_floor` then `to_asset_floor`). This is what the pool pays on a full
50
+ * withdrawal, so the SDK never reports more collateral than the contract releases.
51
+ */
52
+ export declare function unscaleSupplyFloor(scaledRay: string | bigint, indexRay: string | bigint, decimals: number): bigint;
53
+ /**
54
+ * Current borrowed amount in token base units — `scaling.rs::unscale_borrow_ceil`
55
+ * (`mul_ceil` then `to_asset_ceil`). A repayment of at least this amount burns
56
+ * every debt share (`resolve_repay`), so a max repay always covers the debt.
57
+ *
58
+ * Note: the xoxno-ui mirror (`stellar-scaled-math.ts`) rounds the share product
59
+ * half-up before the ceil rescale; the contract ceils both steps. This module
60
+ * follows the contract.
61
+ */
62
+ export declare function unscaleBorrowCeil(scaledRay: string | bigint, indexRay: string | bigint, decimals: number): bigint;
63
+ /**
64
+ * Displayed balance in token base units — `scaling.rs::unscale_supply` /
65
+ * `unscale_borrow` (half-up at both steps). `resolve_withdrawal` treats a
66
+ * request at or above this value as a full withdrawal.
67
+ */
68
+ export declare function unscaleHalfUp(scaledRay: string | bigint, indexRay: string | bigint, decimals: number): bigint;
69
+ /** `Ray::from_asset` — token base units → RAY (exact for decimals ≤ 27). */
70
+ export declare function baseToRay(amountBase: bigint, decimals: number): bigint;
71
+ /**
72
+ * USD value (WAD) of a scaled position — `common/src/rates/value.rs`
73
+ * `position_value` (halfUp) / `position_value_floor` / `position_value_ceil`:
74
+ * `asset_ray = round(scaled * index / RAY)`, `asset_wad = round(asset_ray / 1e9)`,
75
+ * `usd_wad = round(asset_wad * price / WAD)` with one direction at all three steps.
76
+ */
77
+ export declare function positionValueWad(scaledRay: bigint, indexRay: bigint, priceWad: bigint, rounding: Rounding): bigint;
78
+ /**
79
+ * USD value (WAD) of a token amount in base units, using the same last two
80
+ * steps as {@link positionValueWad}. Used by projections, where the hypothetical
81
+ * amount is a token quantity rather than shares.
82
+ */
83
+ export declare function baseValueWad(amountBase: bigint, decimals: number, priceWad: bigint, rounding: Rounding): bigint;
84
+ /** WAD → JS number (display only; ~1e-16 relative precision). */
85
+ export declare const wadToNumber: (wad: bigint) => number;
86
+ /** RAY → JS number (display only). */
87
+ export declare const rayToNumber: (ray: bigint) => number;
88
+ /** Token base units → human-readable number (display only). */
89
+ export declare const baseToShort: (amountBase: bigint, decimals: number) => number;
90
+ /**
91
+ * Decimal JS number (a `usdPrice` / `*Short` API field) → integer at `decimals`.
92
+ * Uses `toFixed` so no float arithmetic runs after the parse; precision is that
93
+ * of the input double (~15–16 significant digits). Domain: `0 < value < 1e21`
94
+ * (`toFixed` switches to exponential notation at 1e21); anything outside,
95
+ * non-finite included, maps to 0 rather than throwing.
96
+ */
97
+ export declare function numberToScaled(value: number, decimals: number): bigint;
98
+ /** JS USD price → WAD, see {@link numberToScaled}. */
99
+ export declare const numberToWad: (usd: number) => bigint;