@xoxno/sdk-js 1.0.217 → 1.0.219

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.
Files changed (39) hide show
  1. package/dist/cjs/sdk/stellar/__tests__/auth-policy.test.d.ts +1 -0
  2. package/dist/cjs/sdk/stellar/__tests__/math/account.test.d.ts +10 -0
  3. package/dist/cjs/sdk/stellar/__tests__/math/parity.test.d.ts +14 -0
  4. package/dist/cjs/sdk/stellar/__tests__/math/scaled-rates.test.d.ts +6 -0
  5. package/dist/cjs/sdk/stellar/__tests__/route-verify.test.d.ts +1 -0
  6. package/dist/cjs/sdk/stellar/auth-policy.d.ts +106 -0
  7. package/dist/cjs/sdk/stellar/contracts.d.ts +6 -0
  8. package/dist/cjs/sdk/stellar/index.d.ts +3 -0
  9. package/dist/cjs/sdk/stellar/math/account.d.ts +183 -0
  10. package/dist/cjs/sdk/stellar/math/index.d.ts +12 -0
  11. package/dist/cjs/sdk/stellar/math/limits.d.ts +80 -0
  12. package/dist/cjs/sdk/stellar/math/projection.d.ts +53 -0
  13. package/dist/cjs/sdk/stellar/math/rates.d.ts +34 -0
  14. package/dist/cjs/sdk/stellar/math/scaled.d.ts +99 -0
  15. package/dist/cjs/sdk/stellar/route-verify.d.ts +100 -0
  16. package/dist/cjs/sdk/stellar/scval-encode.d.ts +10 -0
  17. package/dist/cjs/sdk/stellar/swap.d.ts +26 -6
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.esm.js +1 -1
  20. package/dist/sdk/stellar/__tests__/auth-policy.test.d.ts +1 -0
  21. package/dist/sdk/stellar/__tests__/math/account.test.d.ts +10 -0
  22. package/dist/sdk/stellar/__tests__/math/parity.test.d.ts +14 -0
  23. package/dist/sdk/stellar/__tests__/math/scaled-rates.test.d.ts +6 -0
  24. package/dist/sdk/stellar/__tests__/route-verify.test.d.ts +1 -0
  25. package/dist/sdk/stellar/auth-policy.d.ts +106 -0
  26. package/dist/sdk/stellar/contracts.d.ts +6 -0
  27. package/dist/sdk/stellar/index.cjs +1 -1
  28. package/dist/sdk/stellar/index.d.ts +3 -0
  29. package/dist/sdk/stellar/index.esm.js +1 -1
  30. package/dist/sdk/stellar/math/account.d.ts +183 -0
  31. package/dist/sdk/stellar/math/index.d.ts +12 -0
  32. package/dist/sdk/stellar/math/limits.d.ts +80 -0
  33. package/dist/sdk/stellar/math/projection.d.ts +53 -0
  34. package/dist/sdk/stellar/math/rates.d.ts +34 -0
  35. package/dist/sdk/stellar/math/scaled.d.ts +99 -0
  36. package/dist/sdk/stellar/route-verify.d.ts +100 -0
  37. package/dist/sdk/stellar/scval-encode.d.ts +10 -0
  38. package/dist/sdk/stellar/swap.d.ts +26 -6
  39. 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 {};
@@ -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 {};
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Pre-signing allowlist for the Soroban authorization tree of a PREPARED
3
+ * transaction.
4
+ *
5
+ * Simulation records every `require_auth` that code on the call stack makes
6
+ * for the caller — including one made by a rogue pool or token inside a swap
7
+ * hop, e.g. `any_token.transfer(caller, attacker, x)`. The wallet signature
8
+ * covers the whole tree, so whatever `prepareTransaction` returns is what the
9
+ * user authorizes. Each policy below is the set of sub-invocations the
10
+ * contracts legitimately request from the caller, with the source that proves
11
+ * it (paths are in the `rs-lending-xlm` repository):
12
+ *
13
+ * | Entrypoint | Caller sub-invocations | Source |
14
+ * |---|---|---|
15
+ * | controller `supply` | `asset.transfer(caller, pool, <= amount)` per asset | `contracts/controller/src/positions/supply.rs:118` |
16
+ * | controller `repay` | `asset.transfer(caller, pool, <= amount)` per asset | `contracts/controller/src/positions/debt.rs:144` |
17
+ * | controller `liquidate` | `asset.transfer(liquidator, pool, <= payment)` per asset | `contracts/controller/src/positions/liquidation/apply.rs:50` |
18
+ * | controller `multiply` | `asset.transfer(caller, controller, <= initial_payment)` when present | `contracts/controller/src/strategies/multiply.rs:169` |
19
+ * | controller `migrate_from_blend` | `blend_pool.submit(caller, controller, controller, [withdraw / withdraw_collateral / repay])`, at most twice | `contracts/controller/src/external/blend.rs:46-97` |
20
+ * | controller `borrow`, `withdraw`, `swap_debt`, `swap_collateral`, `repay_debt_with_collateral`, `flash_loan`, `flash_position`, any other | none | the controller funds these from its own custody (`strategies/legs.rs:50`, `strategies/flash_position.rs:285`) |
21
+ * | governance, any | none | proposer auth only |
22
+ * | router `execute_strategy` | `token_in.transfer(sender, router, <= total_in)` | `contracts/swap-aggregator/src/execute/mod.rs:81` |
23
+ */
24
+ import { xdr } from '@stellar/stellar-sdk';
25
+ import { type StellarNetwork } from './contracts.js';
26
+ export type StellarAuthPolicyErrorCode = 'MALFORMED_TRANSACTION' | 'INVOCATION_CHANGED' | 'UNSUPPORTED_CONTRACT' | 'CALLER_MISMATCH' | 'DUPLICATE_ENTRY' | 'UNEXPECTED_ROOT' | 'UNEXPECTED_CALL' | 'NESTED_INVOCATION' | 'UNEXPECTED_TOKEN' | 'UNEXPECTED_SENDER' | 'UNEXPECTED_RECIPIENT' | 'AMOUNT_EXCEEDED';
27
+ /** The invocation that broke the policy, in readable form. */
28
+ export interface StellarAuthOffender {
29
+ contract: string;
30
+ fn: string;
31
+ args: string[];
32
+ }
33
+ /** Thrown when the authorization tree holds anything the policy does not allow. */
34
+ export declare class StellarAuthPolicyError extends Error {
35
+ readonly code: StellarAuthPolicyErrorCode;
36
+ readonly offender?: StellarAuthOffender;
37
+ constructor(code: StellarAuthPolicyErrorCode, message: string, offender?: StellarAuthOffender);
38
+ }
39
+ /** Total the caller may send to `to` in `token` across all sub-invocations. */
40
+ export interface StellarAuthTransferAllowance {
41
+ token: string;
42
+ to: string;
43
+ maxAmount: bigint;
44
+ }
45
+ /** A non-transfer sub-invocation the caller legitimately authorizes. */
46
+ export interface StellarAuthCallAllowance {
47
+ contract: string;
48
+ fn: string;
49
+ maxCount: number;
50
+ /** Returns why `args` are not acceptable, or `undefined` when they are. */
51
+ reject: (args: xdr.ScVal[]) => string | undefined;
52
+ }
53
+ export interface StellarAuthPolicy {
54
+ caller: string;
55
+ root: {
56
+ contract: string;
57
+ fn: string;
58
+ };
59
+ transfers: StellarAuthTransferAllowance[];
60
+ calls?: StellarAuthCallAllowance[];
61
+ }
62
+ /**
63
+ * Enforce `policy` on the authorization entries of a prepared transaction.
64
+ * Entries that belong to other addresses are ignored. For the caller: at most
65
+ * one entry, the expected root, and only allowlisted leaf sub-invocations.
66
+ * @param sourceAccount - Account that source-account credentials stand for
67
+ * (the operation source, else the transaction source). When omitted, such
68
+ * entries are treated as the caller's.
69
+ * @throws StellarAuthPolicyError
70
+ */
71
+ export declare function assertStellarAuthEntries(entries: readonly xdr.SorobanAuthorizationEntry[], policy: StellarAuthPolicy, sourceAccount?: string): void;
72
+ /** Contract addresses a policy is derived against. */
73
+ export interface StellarAuthDeployment {
74
+ lendingController: string;
75
+ lendingPool: string;
76
+ aggregatorRouter: string;
77
+ governance: string;
78
+ }
79
+ /**
80
+ * Derive the policy for one contract invocation from its own arguments. The
81
+ * arguments are what the application built from the user's intent, so the
82
+ * allowed transfers are bounded by exactly what the user asked to pay.
83
+ * @throws StellarAuthPolicyError for a contract outside `deployment`.
84
+ */
85
+ export declare function stellarAuthPolicyForInvocation(caller: string, invocation: xdr.InvokeContractArgs, deployment: StellarAuthDeployment): StellarAuthPolicy;
86
+ export interface StellarPreparedTxAuthOptions {
87
+ network: StellarNetwork;
88
+ /** The wallet address that will sign. */
89
+ caller: string;
90
+ /**
91
+ * The unsigned builder output that was sent to `prepareTransaction`. When
92
+ * given, the prepared invocation must be byte-identical to it.
93
+ */
94
+ builtXdr?: string;
95
+ /** Replaces the derived policy (other contracts, custom flash receivers). */
96
+ policy?: StellarAuthPolicy;
97
+ /** Overrides manifest addresses for a non-standard deployment. */
98
+ deployment?: Partial<StellarAuthDeployment>;
99
+ }
100
+ /**
101
+ * Check a prepared transaction (base64 XDR, as `prepareTransaction` returns
102
+ * it) before it is passed to a wallet. Takes XDR strings so no live SDK object
103
+ * crosses the consumer's bundle boundary. Never sign when this throws.
104
+ * @throws StellarAuthPolicyError | StellarRouteVerificationError
105
+ */
106
+ export declare function assertStellarPreparedTxAuth(preparedXdr: string, opts: StellarPreparedTxAuthOptions): void;
@@ -20,6 +20,8 @@ export interface StellarDeployment {
20
20
  readonly aquariusApiUrl: string;
21
21
  readonly aquariusRouter: string;
22
22
  readonly lendingController: string;
23
+ /** The controller's single liquidity pool: the recipient of supply, repay and liquidation transfers. */
24
+ readonly lendingPool: string;
23
25
  readonly aggregatorRouter: string;
24
26
  readonly governance: string;
25
27
  readonly priceAggregator: string;
@@ -41,6 +43,7 @@ export declare const STELLAR_NETWORKS: {
41
43
  readonly aquariusApiUrl: "https://amm-api.aqua.network";
42
44
  readonly aquariusRouter: "CBQDHNBFBZYE4MKPWBSJOPIYLW4SFSXAXUTSXJN76GNKYVYPCKWC6QUK";
43
45
  readonly lendingController: "CAUCMIN5KSXEVZ7NMXR3LZATGD5EFIEUI5XWTFLYRO2R5OTXI22WE5JX";
46
+ readonly lendingPool: "CBXRNDQMAJFG4VUKMKFEMFS75UUXE2SPSNV4LEEFCSNBCN66PYRWBKXO";
44
47
  readonly aggregatorRouter: "CCVENFSVCBYDHVOACFZXMNNYVOZ3LKXPZYU5LUI4N7KTXOKRVYD7F3TR";
45
48
  readonly governance: "CC44PEQW7HSEPKAZ5ZRPH2JS5M5KVJXCUBLJ2ZX4E3WCDKMNFILHC2AD";
46
49
  readonly priceAggregator: "CBGUF2G2Q7HCVCWYISDXBHPVBGMYNXA7PG2VET66YBZX6IKOOV27NSMV";
@@ -56,6 +59,7 @@ export declare const STELLAR_NETWORKS: {
56
59
  readonly aquariusApiUrl: "https://amm-api-testnet.aqua.network";
57
60
  readonly aquariusRouter: "CBCFTQSPDBAIZ6R6PJQKSQWKNKWH2QIV3I4J72SHWBIK3ADRRAM5A6GD";
58
61
  readonly lendingController: "CCXRWJ6SIU2WPFEGLFGJVITPL57QAYIMIO6OAM2NBGNDQSSCK2FFV3F3";
62
+ readonly lendingPool: "CBSGF6QOQAMPFBEVSYPEQHSZRIHJ6RCGUPCRDMUX36DEKRWFAO2PZB5A";
59
63
  readonly aggregatorRouter: "CDNTWMWW2WGYTKIZTJYNGNVQQZI4KTC5BQRZ3275KESRX5T4O3AYECL5";
60
64
  readonly governance: "CDS33JDOYH3F3FL4QUQ6DV4WKHML2AKHF4LADTZ57FRUAEBTE7NMY5FQ";
61
65
  readonly priceAggregator: "CAALOOTIDXCX7D7FMQIBSSAJLPKOM3GMXS4UUSTDIG42JCRRJHOPUHOP";
@@ -74,6 +78,7 @@ export declare const stellarNetworks: {
74
78
  readonly aquariusApiUrl: "https://amm-api.aqua.network";
75
79
  readonly aquariusRouter: "CBQDHNBFBZYE4MKPWBSJOPIYLW4SFSXAXUTSXJN76GNKYVYPCKWC6QUK";
76
80
  readonly lendingController: "CAUCMIN5KSXEVZ7NMXR3LZATGD5EFIEUI5XWTFLYRO2R5OTXI22WE5JX";
81
+ readonly lendingPool: "CBXRNDQMAJFG4VUKMKFEMFS75UUXE2SPSNV4LEEFCSNBCN66PYRWBKXO";
77
82
  readonly aggregatorRouter: "CCVENFSVCBYDHVOACFZXMNNYVOZ3LKXPZYU5LUI4N7KTXOKRVYD7F3TR";
78
83
  readonly governance: "CC44PEQW7HSEPKAZ5ZRPH2JS5M5KVJXCUBLJ2ZX4E3WCDKMNFILHC2AD";
79
84
  readonly priceAggregator: "CBGUF2G2Q7HCVCWYISDXBHPVBGMYNXA7PG2VET66YBZX6IKOOV27NSMV";
@@ -89,6 +94,7 @@ export declare const stellarNetworks: {
89
94
  readonly aquariusApiUrl: "https://amm-api-testnet.aqua.network";
90
95
  readonly aquariusRouter: "CBCFTQSPDBAIZ6R6PJQKSQWKNKWH2QIV3I4J72SHWBIK3ADRRAM5A6GD";
91
96
  readonly lendingController: "CCXRWJ6SIU2WPFEGLFGJVITPL57QAYIMIO6OAM2NBGNDQSSCK2FFV3F3";
97
+ readonly lendingPool: "CBSGF6QOQAMPFBEVSYPEQHSZRIHJ6RCGUPCRDMUX36DEKRWFAO2PZB5A";
92
98
  readonly aggregatorRouter: "CDNTWMWW2WGYTKIZTJYNGNVQQZI4KTC5BQRZ3275KESRX5T4O3AYECL5";
93
99
  readonly governance: "CDS33JDOYH3F3FL4QUQ6DV4WKHML2AKHF4LADTZ57FRUAEBTE7NMY5FQ";
94
100
  readonly priceAggregator: "CAALOOTIDXCX7D7FMQIBSSAJLPKOM3GMXS4UUSTDIG42JCRRJHOPUHOP";
@@ -14,9 +14,12 @@ export * from './governance.js';
14
14
  export * from './events/index.js';
15
15
  export * from './quote.js';
16
16
  export * from './swap.js';
17
+ export * from './route-verify.js';
18
+ export * from './auth-policy.js';
17
19
  export * from './prepare.js';
18
20
  export * from './position-mode.js';
19
21
  export * from './repay-swap.js';
20
22
  export * from './cctp.js';
23
+ export * from './math/index.js';
21
24
  export type { StellarStrategySwapInput, StellarStrategySwapPathInput, StellarStrategySwapHopInput, } from './scval-encode.js';
22
25
  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;