@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.
- package/dist/cjs/sdk/stellar/__tests__/math/account.test.d.ts +10 -0
- package/dist/cjs/sdk/stellar/__tests__/math/parity.test.d.ts +14 -0
- package/dist/cjs/sdk/stellar/__tests__/math/scaled-rates.test.d.ts +6 -0
- package/dist/cjs/sdk/stellar/index.d.ts +1 -0
- package/dist/cjs/sdk/stellar/math/account.d.ts +183 -0
- package/dist/cjs/sdk/stellar/math/index.d.ts +12 -0
- package/dist/cjs/sdk/stellar/math/limits.d.ts +80 -0
- package/dist/cjs/sdk/stellar/math/projection.d.ts +53 -0
- package/dist/cjs/sdk/stellar/math/rates.d.ts +34 -0
- package/dist/cjs/sdk/stellar/math/scaled.d.ts +99 -0
- package/dist/index.cjs +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/sdk/stellar/__tests__/math/account.test.d.ts +10 -0
- package/dist/sdk/stellar/__tests__/math/parity.test.d.ts +14 -0
- package/dist/sdk/stellar/__tests__/math/scaled-rates.test.d.ts +6 -0
- package/dist/sdk/stellar/index.cjs +1 -1
- package/dist/sdk/stellar/index.d.ts +1 -0
- package/dist/sdk/stellar/index.esm.js +1 -1
- package/dist/sdk/stellar/math/account.d.ts +183 -0
- package/dist/sdk/stellar/math/index.d.ts +12 -0
- package/dist/sdk/stellar/math/limits.d.ts +80 -0
- package/dist/sdk/stellar/math/projection.d.ts +53 -0
- package/dist/sdk/stellar/math/rates.d.ts +34 -0
- package/dist/sdk/stellar/math/scaled.d.ts +99 -0
- package/package.json +1 -1
|
@@ -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 {};
|
|
@@ -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;
|