@waterx/sdk 4.0.1 → 4.3.0

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 (130) hide show
  1. package/README.md +37 -17
  2. package/dist/cjs/src/account/account.js +2 -1
  3. package/dist/cjs/src/account/config.d.ts +3 -2
  4. package/dist/cjs/src/account/funding/balance.d.ts +25 -2
  5. package/dist/cjs/src/account/funding/balance.js +36 -24
  6. package/dist/cjs/src/account/funding/credit.js +6 -10
  7. package/dist/cjs/src/constants.d.ts +15 -1
  8. package/dist/cjs/src/constants.js +18 -4
  9. package/dist/cjs/src/generated/waterx_rule/deps/bucket_v2_framework/float.d.ts +8 -0
  10. package/dist/cjs/src/generated/waterx_rule/deps/bucket_v2_framework/float.js +13 -0
  11. package/dist/cjs/src/generated/waterx_rule/deps/sui/vec_map.d.ts +36 -0
  12. package/dist/cjs/src/generated/waterx_rule/deps/sui/vec_map.js +31 -0
  13. package/dist/cjs/src/generated/waterx_rule/waterx_rule.d.ts +813 -0
  14. package/dist/cjs/src/generated/waterx_rule/waterx_rule.js +946 -0
  15. package/dist/cjs/src/oracle/aggregate.d.ts +23 -21
  16. package/dist/cjs/src/oracle/aggregate.js +133 -59
  17. package/dist/cjs/src/oracle/config.d.ts +82 -41
  18. package/dist/cjs/src/oracle/config.js +0 -42
  19. package/dist/cjs/src/oracle/host.d.ts +26 -11
  20. package/dist/cjs/src/oracle/index.d.ts +6 -2
  21. package/dist/cjs/src/oracle/index.js +36 -8
  22. package/dist/cjs/src/oracle/price-update-rule.d.ts +16 -10
  23. package/dist/cjs/src/oracle/price-update-rule.js +3 -3
  24. package/dist/cjs/src/oracle/pyth.d.ts +44 -0
  25. package/dist/cjs/src/oracle/pyth.js +48 -6
  26. package/dist/cjs/src/oracle/read-plane.d.ts +70 -0
  27. package/dist/cjs/src/oracle/read-plane.js +78 -0
  28. package/dist/cjs/src/oracle/rule-registry.d.ts +14 -11
  29. package/dist/cjs/src/oracle/rule-registry.js +16 -11
  30. package/dist/cjs/src/oracle/rules/pyth-core-rule.js +4 -4
  31. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +21 -0
  32. package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +29 -4
  33. package/dist/cjs/src/oracle/rules/pyth-rule.js +5 -4
  34. package/dist/cjs/src/oracle/rules/waterx-rule.d.ts +126 -0
  35. package/dist/cjs/src/oracle/rules/waterx-rule.js +312 -0
  36. package/dist/cjs/src/perp/client.d.ts +71 -20
  37. package/dist/cjs/src/perp/client.js +35 -13
  38. package/dist/cjs/src/perp/config.d.ts +6 -7
  39. package/dist/cjs/src/perp/config.js +12 -11
  40. package/dist/cjs/src/perp/constants.d.ts +0 -6
  41. package/dist/cjs/src/perp/constants.js +11 -9
  42. package/dist/cjs/src/perp/fetch/account.js +3 -0
  43. package/dist/cjs/src/perp/fetch/bridge.js +2 -1
  44. package/dist/cjs/src/perp/fetch/market.js +2 -1
  45. package/dist/cjs/src/perp/fetch/positions.d.ts +16 -10
  46. package/dist/cjs/src/perp/fetch/positions.js +23 -20
  47. package/dist/cjs/src/perp/index.d.ts +9 -5
  48. package/dist/cjs/src/perp/index.js +16 -7
  49. package/dist/cjs/src/perp/liq-view.d.ts +64 -0
  50. package/dist/cjs/src/perp/liq-view.js +74 -0
  51. package/dist/cjs/src/perp/user/order.d.ts +13 -0
  52. package/dist/cjs/src/perp/user/order.js +30 -16
  53. package/dist/cjs/src/perp/user/staking.js +3 -2
  54. package/dist/cjs/src/perp/user/trading.js +25 -24
  55. package/dist/cjs/src/perp/user/wlp.js +6 -5
  56. package/dist/cjs/src/prediction/utils.d.ts +11 -2
  57. package/dist/cjs/src/prediction/utils.js +22 -22
  58. package/dist/cjs/src/unified-client.d.ts +41 -15
  59. package/dist/cjs/src/unified-client.js +4 -2
  60. package/dist/cjs/src/utils/format.d.ts +14 -0
  61. package/dist/cjs/src/utils/format.js +24 -0
  62. package/dist/cjs/src/utils/math.d.ts +304 -12
  63. package/dist/cjs/src/utils/math.js +397 -17
  64. package/dist/cjs/src/utils/validate.d.ts +69 -0
  65. package/dist/cjs/src/utils/validate.js +183 -0
  66. package/dist/src/account/account.js +2 -1
  67. package/dist/src/account/config.d.ts +3 -2
  68. package/dist/src/account/funding/balance.d.ts +25 -2
  69. package/dist/src/account/funding/balance.js +36 -24
  70. package/dist/src/account/funding/credit.js +6 -10
  71. package/dist/src/constants.d.ts +15 -1
  72. package/dist/src/constants.js +17 -3
  73. package/dist/src/generated/waterx_rule/deps/bucket_v2_framework/float.d.ts +8 -0
  74. package/dist/src/generated/waterx_rule/deps/bucket_v2_framework/float.js +10 -0
  75. package/dist/src/generated/waterx_rule/deps/sui/vec_map.d.ts +36 -0
  76. package/dist/src/generated/waterx_rule/deps/sui/vec_map.js +27 -0
  77. package/dist/src/generated/waterx_rule/waterx_rule.d.ts +813 -0
  78. package/dist/src/generated/waterx_rule/waterx_rule.js +855 -0
  79. package/dist/src/oracle/aggregate.d.ts +23 -21
  80. package/dist/src/oracle/aggregate.js +133 -59
  81. package/dist/src/oracle/config.d.ts +82 -41
  82. package/dist/src/oracle/config.js +1 -41
  83. package/dist/src/oracle/host.d.ts +26 -11
  84. package/dist/src/oracle/index.d.ts +6 -2
  85. package/dist/src/oracle/index.js +27 -9
  86. package/dist/src/oracle/price-update-rule.d.ts +16 -10
  87. package/dist/src/oracle/price-update-rule.js +3 -3
  88. package/dist/src/oracle/pyth.d.ts +44 -0
  89. package/dist/src/oracle/pyth.js +45 -5
  90. package/dist/src/oracle/read-plane.d.ts +70 -0
  91. package/dist/src/oracle/read-plane.js +74 -0
  92. package/dist/src/oracle/rule-registry.d.ts +14 -11
  93. package/dist/src/oracle/rule-registry.js +16 -11
  94. package/dist/src/oracle/rules/pyth-core-rule.js +5 -5
  95. package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +21 -0
  96. package/dist/src/oracle/rules/pyth-lazer-rule.js +28 -3
  97. package/dist/src/oracle/rules/pyth-rule.js +5 -4
  98. package/dist/src/oracle/rules/waterx-rule.d.ts +126 -0
  99. package/dist/src/oracle/rules/waterx-rule.js +305 -0
  100. package/dist/src/perp/client.d.ts +71 -20
  101. package/dist/src/perp/client.js +36 -14
  102. package/dist/src/perp/config.d.ts +6 -7
  103. package/dist/src/perp/config.js +11 -9
  104. package/dist/src/perp/constants.d.ts +0 -6
  105. package/dist/src/perp/constants.js +10 -8
  106. package/dist/src/perp/fetch/account.js +3 -0
  107. package/dist/src/perp/fetch/bridge.js +2 -1
  108. package/dist/src/perp/fetch/market.js +2 -1
  109. package/dist/src/perp/fetch/positions.d.ts +16 -10
  110. package/dist/src/perp/fetch/positions.js +28 -20
  111. package/dist/src/perp/index.d.ts +9 -5
  112. package/dist/src/perp/index.js +6 -4
  113. package/dist/src/perp/liq-view.d.ts +64 -0
  114. package/dist/src/perp/liq-view.js +71 -0
  115. package/dist/src/perp/user/order.d.ts +13 -0
  116. package/dist/src/perp/user/order.js +30 -16
  117. package/dist/src/perp/user/staking.js +3 -2
  118. package/dist/src/perp/user/trading.js +25 -24
  119. package/dist/src/perp/user/wlp.js +6 -5
  120. package/dist/src/prediction/utils.d.ts +11 -2
  121. package/dist/src/prediction/utils.js +22 -22
  122. package/dist/src/unified-client.d.ts +41 -15
  123. package/dist/src/unified-client.js +4 -2
  124. package/dist/src/utils/format.d.ts +14 -0
  125. package/dist/src/utils/format.js +21 -0
  126. package/dist/src/utils/math.d.ts +304 -12
  127. package/dist/src/utils/math.js +394 -17
  128. package/dist/src/utils/validate.d.ts +69 -0
  129. package/dist/src/utils/validate.js +167 -0
  130. package/package.json +4 -1
@@ -1,11 +1,61 @@
1
1
  "use strict";
2
+ /**
3
+ * Perp / WLP number math: Move-mirroring formulas, the `Float` fixed-point
4
+ * mirrors the canonical raw estimates need, and the on-chain price encoding.
5
+ *
6
+ * ## Validation policy — two tiers, deliberate
7
+ *
8
+ * The per-function `@throws` blocks below are INSTANCES of this rule, not a
9
+ * dozen independent decisions — every tier-1 function carries one. Guards come
10
+ * from `utils/validate.ts` (`assertFinite` / `assertFiniteNonNegative` /
11
+ * `assertUnitFraction` / `assertTokenDecimal` / `assertUnsignedBigInt`) and
12
+ * every message names the offending parameter.
13
+ *
14
+ * 1. **MONEY PATH — throws `RangeError`.** The fee bundles
15
+ * (`calcRealLiqNetCostUsd`, `calcViewEstLiqFeesUsd`), the liquidation
16
+ * estimates (`calcEstLiqPrice`, `calcEstLiqPriceRaw`), collateral and
17
+ * withdrawable (`calcEffectiveCollateralUsd`,
18
+ * `calcMaxReducibleCollateralUsd`), and the WLP quote / APY surface
19
+ * (`calcWlpMintOut`, `calcWlpRedeemOut`, `calcDynamicFeeBps`,
20
+ * `annualizedApyFromRatio`, `calcWlpIncentiveApy`). These map garbage onto a
21
+ * PLAUSIBLE number — an `Infinity` fee returns 0, indistinguishable from
22
+ * "already liquidatable"; a negative fee ADDS to equity — so the input dies
23
+ * at the boundary instead of becoming a wrong price on a screen.
24
+ * 2. **BARE ARITHMETIC — no guards.** Mostly one-line helpers (`calcNotional`,
25
+ * `calcFee`, `calcUnrealizedPnl`, `calcLeverage`, `calcTotalTradingFeeRate`,
26
+ * `calcFundingRate`, `calcFundingFeeUsd`, `decodeFundingIndexDelta`,
27
+ * `calcBorrowRate`, `calcBorrowRateAccrual`, `calcPositionBorrowFee`,
28
+ * `calcTokenUtilizationBps`, `annualizeFundingRate`, `calcWlpPrice`), plus
29
+ * `calcImpactFeeRate` — NOT a one-liner (a branchy curve with eight early
30
+ * returns) but the same category: each of its early returns tests an exact
31
+ * zero or an ordering that a NaN never satisfies, so garbage falls straight
32
+ * through the branches instead of being absorbed by one. A NaN / Infinity
33
+ * input propagates VISIBLY to the output in all of them — there is no
34
+ * plausible-looking value for it to hide behind — so a guard would add noise
35
+ * and cost without buying safety.
36
+ *
37
+ * `rawPrice` sits in neither tier: it is a PARSE, and throws plain `Error` on
38
+ * malformed input.
39
+ *
40
+ * ### Documented domain zeros
41
+ * Distinct from garbage: an input that is legitimately zero and has exactly one
42
+ * honest answer returns it rather than throwing. `sizeInAsset === 0` /
43
+ * `entryNotional === 0` → `0` (nothing to liquidate); `totalSupply === 0` → `0`
44
+ * (no shares to price — `calcWlpMintOut` is the deliberate exception, where it
45
+ * means the bootstrap mint); `intervalMs === 0` / `days <= 0` → `0` (no window
46
+ * to annualize over); `liquidityAmount === 0` → `0` utilization;
47
+ * `collateralUsd === 0` → `Infinity` leverage. Each is noted at its function.
48
+ */
2
49
  Object.defineProperty(exports, "__esModule", { value: true });
3
50
  exports.rawPrice = rawPrice;
4
51
  exports.calcNotional = calcNotional;
5
52
  exports.calcFee = calcFee;
6
53
  exports.calcUnrealizedPnl = calcUnrealizedPnl;
7
54
  exports.calcLeverage = calcLeverage;
55
+ exports.calcRealLiqNetCostUsd = calcRealLiqNetCostUsd;
56
+ exports.calcViewEstLiqFeesUsd = calcViewEstLiqFeesUsd;
8
57
  exports.calcEstLiqPrice = calcEstLiqPrice;
58
+ exports.calcEstLiqPriceRaw = calcEstLiqPriceRaw;
9
59
  exports.calcEffectiveCollateralUsd = calcEffectiveCollateralUsd;
10
60
  exports.calcMaxReducibleCollateralUsd = calcMaxReducibleCollateralUsd;
11
61
  exports.calcImpactFeeRate = calcImpactFeeRate;
@@ -25,16 +75,40 @@ exports.calcWlpMintOut = calcWlpMintOut;
25
75
  exports.calcWlpRedeemOut = calcWlpRedeemOut;
26
76
  exports.calcDynamicFeeBps = calcDynamicFeeBps;
27
77
  const constants_ts_1 = require("../constants.js");
78
+ const validate_ts_1 = require("./validate.js");
28
79
  // ======== On-chain encoding ========
80
+ const DECIMAL_USD_RE = /^(\d+)(?:\.(\d+))?$/;
29
81
  /**
30
82
  * Convert a human-readable USD price to the raw 1e9-scaled `u128` value
31
83
  * that on-chain `Float`-typed parameters expect.
84
+ *
85
+ * Use for **tx-build** price args only (`acceptablePrice` / `triggerPrice` /
86
+ * size args). Do NOT use for the view-read params `basePriceUsd` /
87
+ * `collateralPriceUsd` on `perp/fetch` (`getPosition`, `getMarketPositions`,
88
+ * `getOrder`, …) — those take WHOLE-DOLLAR integer USD (the Move view applies
89
+ * `float::from` internally; a 1e9-scaled value inflates pnl/notional-derived
90
+ * fields by 1e9).
91
+ *
92
+ * Precision: see {@link RawPriceInput}. The `number` path rounds through f64
93
+ * and is exact only below ≈ $9,007,199; an {@link ExactDecimalUsd} string
94
+ * parses digits directly onto the 1e9 grid without touching f64. This bites
95
+ * hardest on `triggerPrice`, which is an EXACT order-book key: a raw value off
96
+ * by one unit silently fails the order lookup.
32
97
  */
33
98
  function rawPrice(usd) {
34
- const n = typeof usd === "string" ? Number(usd) : usd;
35
- if (!Number.isFinite(n))
99
+ if (typeof usd === "string") {
100
+ const match = DECIMAL_USD_RE.exec(usd.trim());
101
+ if (!match)
102
+ throw new Error(`Invalid USD price: ${usd}`);
103
+ const [, whole, frac = ""] = match;
104
+ if (frac.length > 9) {
105
+ throw new Error(`Invalid USD price: ${usd} — more than 9 decimal places cannot be represented on the 1e9 grid`);
106
+ }
107
+ return BigInt(whole) * constants_ts_1.FLOAT_SCALE + BigInt(frac.padEnd(9, "0") || "0");
108
+ }
109
+ if (!Number.isFinite(usd))
36
110
  throw new Error(`Invalid USD price: ${usd}`);
37
- return BigInt(Math.round(n * Number(constants_ts_1.FLOAT_SCALE)));
111
+ return BigInt(Math.round(usd * Number(constants_ts_1.FLOAT_SCALE)));
38
112
  }
39
113
  // ======== Basic position math ========
40
114
  /** Notional value in USD: sizeInAsset × price. */
@@ -56,9 +130,81 @@ function calcLeverage(sizeUsd, collateralUsd) {
56
130
  return sizeUsd / collateralUsd;
57
131
  }
58
132
  /**
59
- * Estimated liquidation price.
133
+ * Net fee cost (USD) of the REAL liquidation check — a plain SIGNED sum:
134
+ *
135
+ * netCostUsd = borrowFeeUsd + openFeeUsd + closingFeeUsd + fundingFeeUsd
136
+ *
137
+ * `fundingFeeUsd` is SIGNED cost-positive, and the result MAY BE NEGATIVE
138
+ * when funding income exceeds the other fees. That is deliberate:
139
+ * `position.move::is_liquidatable` credits funding income IN FULL — income
140
+ * first pays down any accumulated deficit, and the remainder is added back to
141
+ * remaining equity — so the linearized equity model is a plain signed sum
142
+ * with no floor. Flooring at 0 here would understate an income-rich
143
+ * position's equity and show a liq price closer to spot than the real check.
144
+ *
145
+ * Three-way Move-semantics relationship (verified against the Move sources
146
+ * 2026-07-30):
147
+ * - the REAL liquidation check, `position.move::is_liquidatable`
148
+ * (waterx_perp): deducts borrow + open + CLOSING fee and credits funding
149
+ * income in full (deficit first, remainder back to equity) — exactly this
150
+ * function's signed sum;
151
+ * - the view estimate, `view.move::calculate_est_liq_price`
152
+ * (waterx_perp_view): OMITS the closing fee AND floors its fee bundle at 0
153
+ * (`Float.saturating_sub` — Float is unsigned) — see `calcViewEstLiqFeesUsd`
154
+ * and the op-exact `calcEstLiqPriceRaw`;
155
+ * - SDK `calcEffectiveCollateralUsd` does NOT credit funding income at all:
156
+ * it mirrors the withdrawable-collateral checks
157
+ * (`calculate_effective_collateral_amount` in `trading.move`), not the
158
+ * liquidation inequality.
159
+ * Do not "fix" any of the three to match another.
160
+ *
161
+ * @throws RangeError when borrow/open/closing fees are not finite `>= 0`
162
+ * numbers, or `fundingFeeUsd` is not finite.
163
+ */
164
+ function calcRealLiqNetCostUsd(fees) {
165
+ (0, validate_ts_1.assertFiniteNonNegative)("fees.borrowFeeUsd", fees.borrowFeeUsd);
166
+ (0, validate_ts_1.assertFiniteNonNegative)("fees.openFeeUsd", fees.openFeeUsd);
167
+ (0, validate_ts_1.assertFiniteNonNegative)("fees.closingFeeUsd", fees.closingFeeUsd);
168
+ (0, validate_ts_1.assertFinite)("fees.fundingFeeUsd", fees.fundingFeeUsd);
169
+ return fees.borrowFeeUsd + fees.openFeeUsd + fees.closingFeeUsd + fees.fundingFeeUsd;
170
+ }
171
+ /**
172
+ * Fee bundle (USD) of the VIEW estimate `view.move::calculate_est_liq_price`:
173
+ *
174
+ * viewFeesUsd = max(0, borrowFeeUsd + openFeeUsd + fundingFeeUsd)
175
+ *
176
+ * Mirrors the view's unsigned Float arithmetic: funding income is credited
177
+ * via `Float.saturating_sub`, so the bundle FLOORS AT 0 — income beyond the
178
+ * other fees is discarded by the view. There is deliberately NO
179
+ * `closingFeeUsd` field: the view omits the close-fee term, and this shape
180
+ * makes it structurally impossible to include one. For the REAL liquidation
181
+ * check's semantics use `calcRealLiqNetCostUsd`; for chain-bit-identical
182
+ * output use `calcEstLiqPriceRaw`.
183
+ *
184
+ * Exported on purpose even though `calcEstLiqPriceRaw` covers the chain-exact
185
+ * path: this is the VIEW-model counterpart of `calcRealLiqNetCostUsd`, for
186
+ * consumers that need the view's fee bundle as a Number (mirroring what the
187
+ * view displays) rather than the full raw price. The two-API split is what makes
188
+ * the REAL and VIEW models impossible to confuse — do not fold it away.
189
+ *
190
+ * @throws RangeError when borrow/open fees are not finite `>= 0` numbers, or
191
+ * `fundingFeeUsd` is not finite.
192
+ */
193
+ function calcViewEstLiqFeesUsd(fees) {
194
+ (0, validate_ts_1.assertFiniteNonNegative)("fees.borrowFeeUsd", fees.borrowFeeUsd);
195
+ (0, validate_ts_1.assertFiniteNonNegative)("fees.openFeeUsd", fees.openFeeUsd);
196
+ (0, validate_ts_1.assertFinite)("fees.fundingFeeUsd", fees.fundingFeeUsd);
197
+ return Math.max(0, fees.borrowFeeUsd + fees.openFeeUsd + fees.fundingFeeUsd);
198
+ }
199
+ /**
200
+ * Estimated liquidation price — Number (f64) UI convenience.
60
201
  *
61
- * Matches `calculate_est_liq_price` in `waterx_perp_view/sources/view.move`:
202
+ * An approximation for display. The CANONICAL implementation is
203
+ * `calcEstLiqPriceRaw` (BigInt fixed-point, op-for-op mirror of
204
+ * `calculate_est_liq_price` in `waterx_perp_view/sources/view.move`); use that
205
+ * wherever exact parity with the on-chain `est_liq_price` matters.
206
+ *
207
+ * Same linear model as the view:
62
208
  * maintenance = maintenanceMarginRate × (size × spotPrice) ← uses current notional
63
209
  * ratio = (collateralUsd − totalFeesUsd − maintenance) / (size × avgPrice)
64
210
  * long: liq = avgPrice × (1 − ratio)
@@ -66,10 +212,40 @@ function calcLeverage(sizeUsd, collateralUsd) {
66
212
  *
67
213
  * Returns 0 when the position is already liquidatable or has no size.
68
214
  *
69
- * @param totalFeesUsd Sum of all accrued fees in USD: borrow + trading + net funding.
215
+ * Fees pass EITHER:
216
+ * - `fees`: the structured bundle, matching the REAL liquidation check
217
+ * (`position.move::is_liquidatable`). `totalFeesUsd` is derived via
218
+ * `calcRealLiqNetCostUsd` — a SIGNED sum including the closing fee, with
219
+ * funding income credited IN FULL, so a caller structurally cannot omit a
220
+ * term or mis-handle income. A negative net cost ADDS to the remaining
221
+ * margin, pushing the estimate FARTHER from spot. Takes precedence when
222
+ * both are given; or
223
+ * - `totalFeesUsd`: a pre-computed number (back-compat path) — the caller
224
+ * owns the fee model.
225
+ * NOTE the deliberate model difference vs the on-chain VIEW: the view both
226
+ * floors its fee bundle at 0 and omits the close fee (`calcViewEstLiqFeesUsd`
227
+ * / `calcEstLiqPriceRaw`); the `fees` path here matches the REAL check.
228
+ *
229
+ * @throws RangeError when sizeInAsset / avgPrice / spotPrice / collateralUsd
230
+ * are not finite `>= 0` numbers, when `maintenanceMarginRate` is not finite
231
+ * inside `[0, 1]`, or when `totalFeesUsd` is not finite.
232
+ * (`sizeInAsset === 0` stays a documented domain case returning 0.)
70
233
  */
71
234
  function calcEstLiqPrice(params) {
72
- const { isLong, avgPrice, sizeInAsset, collateralUsd, maintenanceMarginRate, spotPrice, totalFeesUsd, } = params;
235
+ const { isLong, avgPrice, sizeInAsset, collateralUsd, maintenanceMarginRate, spotPrice } = params;
236
+ (0, validate_ts_1.assertFiniteNonNegative)("sizeInAsset", sizeInAsset);
237
+ (0, validate_ts_1.assertFiniteNonNegative)("avgPrice", avgPrice);
238
+ (0, validate_ts_1.assertFiniteNonNegative)("spotPrice", spotPrice);
239
+ (0, validate_ts_1.assertFiniteNonNegative)("collateralUsd", collateralUsd);
240
+ (0, validate_ts_1.assertUnitFraction)("maintenanceMarginRate", maintenanceMarginRate);
241
+ let totalFeesUsd;
242
+ if (params.fees !== undefined) {
243
+ totalFeesUsd = calcRealLiqNetCostUsd(params.fees);
244
+ }
245
+ else {
246
+ totalFeesUsd = params.totalFeesUsd ?? 0;
247
+ (0, validate_ts_1.assertFinite)("totalFeesUsd", totalFeesUsd);
248
+ }
73
249
  if (sizeInAsset === 0)
74
250
  return 0;
75
251
  const entryNotional = sizeInAsset * avgPrice;
@@ -85,6 +261,118 @@ function calcEstLiqPrice(params) {
85
261
  }
86
262
  return avgPrice * (1 + ratio);
87
263
  }
264
+ // ======== Move Float mirrors (internal) ========
265
+ // `bucket_v2_framework::float` — UNSIGNED u128 fixed-point at 1e9 where every
266
+ // operation truncates via integer division. These mirror exactly the ops
267
+ // `calcEstLiqPriceRaw` needs. Move's overflow aborts are not mirrored: JS
268
+ // BigInt cannot overflow, and an input that would abort on chain has no
269
+ // chain-produced value to be compared against.
270
+ /** `float::from_fraction(n, m)` — `(n × 1e9) / m`, truncating. */
271
+ const floatFromFraction = (n, m) => (n * constants_ts_1.FLOAT_SCALE) / m;
272
+ /** `float::mul(a, b)` — `(a × b) / 1e9`, truncating. */
273
+ const floatMul = (a, b) => (a * b) / constants_ts_1.FLOAT_SCALE;
274
+ /** `float::div(a, b)` — `(a × 1e9) / b`, truncating. */
275
+ const floatDiv = (a, b) => (a * constants_ts_1.FLOAT_SCALE) / b;
276
+ /** `float::saturating_sub(a, b)` — `a − b` floored at 0 (Float is unsigned). */
277
+ const floatSaturatingSub = (a, b) => (a < b ? 0n : a - b);
278
+ /**
279
+ * `waterx_perp::math::amount_to_usd(amount, decimal, price)` — the exact
280
+ * two-step composition `from_fraction(amount, 10^decimal).mul(price)`, each
281
+ * step truncating independently (NOT algebraically merged).
282
+ */
283
+ const amountToUsdRaw = (amountRaw, pow10, priceRaw) => floatMul(floatFromFraction(amountRaw, pow10), priceRaw);
284
+ /**
285
+ * `10u64.pow(decimal)` for the whole u64 decimal domain — exhaustive, since the
286
+ * `collateralDecimal` guard pins it to `[0, 19]` (10^19 < 2^64 ≤ 10^20). Built
287
+ * once at module load instead of exponentiating per call.
288
+ */
289
+ const POW10 = Object.freeze(Array.from({ length: 20 }, (_, decimal) => 10n ** BigInt(decimal)));
290
+ /**
291
+ * Estimated liquidation price — CANONICAL raw fixed-point implementation.
292
+ *
293
+ * Op-for-op mirror of `calculate_est_liq_price` in
294
+ * `waterx_perp_view/sources/view.move` under `bucket_v2_framework::float`
295
+ * semantics: unsigned 1e9 fixed-point, every `mul` is `(a×b)/1e9` and every
296
+ * `div` is `(a×1e9)/b` with truncating BigInt division at EACH step,
297
+ * including `math::amount_to_usd`'s exact composition, the u64 addition of
298
+ * borrow + open fee BEFORE the USD conversion, and the `saturating_sub`
299
+ * funding credit (the VIEW model: fee bundle floors at 0, close fee omitted).
300
+ * Given the same raw inputs the view receives, the result is bit-identical to
301
+ * the chain's `PositionData.est_liq_price`.
302
+ *
303
+ * The Number `calcEstLiqPrice` is a UI convenience approximation of this
304
+ * canonical form (and its `fees` path models the REAL liquidation check
305
+ * instead of the view — see `calcRealLiqNetCostUsd`).
306
+ *
307
+ * Inputs are the raw on-chain values exactly as the view takes them.
308
+ * Returns the raw 1e9-scaled u128 price; `0n` = already liquidatable /
309
+ * zero size (the view's N/A signal).
310
+ *
311
+ * Holding a fetched `PositionDataView` row? Use `calcEstLiqPriceRawFromView`
312
+ * (`perp/liq-view.ts`) instead of hand-mapping its nine raw fields — the adapter
313
+ * owns that mapping and carries the invariant below on its own signature.
314
+ *
315
+ * INVARIANT the signature cannot enforce: `basePriceUsd` / `collateralPriceUsd`
316
+ * MUST be the same whole-dollar values passed to the `perp/fetch` read that
317
+ * produced the row whose fields you are feeding in. `PositionDataView` does not
318
+ * carry the probe prices, so nothing here can check it — feed different prices
319
+ * and the fee/notional-derived fields were computed against one price while the
320
+ * estimate is computed against another, and parity with
321
+ * `PositionData.est_liq_price` silently breaks.
322
+ *
323
+ * @throws RangeError when any bigint input is negative or
324
+ * `collateralDecimal` is not an integer in `[0, 19]` (u64 `10^decimal`).
325
+ */
326
+ function calcEstLiqPriceRaw(params) {
327
+ const { isLong, sizeRaw, avgPriceRaw, collateralAmountRaw, collateralDecimal, basePriceUsd, collateralPriceUsd, maintenanceMarginRaw, borrowFeeRaw, fundingSign, fundingFeeRaw, tradingFeeRaw, } = params;
328
+ // One loop so the guard labels cannot drift from the field names.
329
+ for (const [label, value] of Object.entries({
330
+ sizeRaw,
331
+ avgPriceRaw,
332
+ collateralAmountRaw,
333
+ basePriceUsd,
334
+ collateralPriceUsd,
335
+ maintenanceMarginRaw,
336
+ borrowFeeRaw,
337
+ fundingFeeRaw,
338
+ tradingFeeRaw,
339
+ })) {
340
+ (0, validate_ts_1.assertUnsignedBigInt)(label, value);
341
+ }
342
+ (0, validate_ts_1.assertTokenDecimal)("collateralDecimal", collateralDecimal);
343
+ // float::from(u64) — whole dollars onto the 1e9 grid (exact, no truncation).
344
+ const basePrice = basePriceUsd * constants_ts_1.FLOAT_SCALE;
345
+ const collPrice = collateralPriceUsd * constants_ts_1.FLOAT_SCALE;
346
+ const pow10 = POW10[collateralDecimal];
347
+ const entryNotional = floatMul(sizeRaw, avgPriceRaw);
348
+ // math::amount_to_usd — the exact two-step from_fraction(amount, 10^dec).mul(price),
349
+ // each step truncating independently (see amountToUsdRaw).
350
+ const collateralUsd = amountToUsdRaw(collateralAmountRaw, pow10, collPrice);
351
+ const currentNotional = floatMul(sizeRaw, basePrice);
352
+ const maintenance = floatMul(maintenanceMarginRaw, currentNotional);
353
+ // u64 addition BEFORE the conversion — ONE from_fraction over the summed fees,
354
+ // not two conversions added (the truncation points differ).
355
+ const accruedFeesUsd = amountToUsdRaw(borrowFeeRaw + tradingFeeRaw, pow10, collPrice);
356
+ const fundingFeeUsd = amountToUsdRaw(fundingFeeRaw, pow10, collPrice);
357
+ // owed → add; income → Float.saturating_sub (unsigned: the bundle floors at 0,
358
+ // so the view DISCARDS income beyond the other fees).
359
+ const totalFeesUsd = fundingSign
360
+ ? accruedFeesUsd + fundingFeeUsd
361
+ : floatSaturatingSub(accruedFeesUsd, fundingFeeUsd);
362
+ const deductions = totalFeesUsd + maintenance;
363
+ // The view's early return is `lte` / `eq` — INCLUSIVE: collateral exactly at
364
+ // deductions is already liquidatable, hence `<=` and `=== 0n` here.
365
+ if (collateralUsd <= deductions || entryNotional === 0n)
366
+ return 0n;
367
+ const marginRemaining = collateralUsd - deductions;
368
+ const ratio = floatDiv(marginRemaining, entryNotional);
369
+ if (isLong) {
370
+ if (ratio >= constants_ts_1.FLOAT_SCALE)
371
+ return 0n;
372
+ return floatMul(avgPriceRaw, constants_ts_1.FLOAT_SCALE - ratio);
373
+ }
374
+ return floatMul(avgPriceRaw, constants_ts_1.FLOAT_SCALE + ratio);
375
+ }
88
376
  /**
89
377
  * Effective (fee-adjusted) collateral in USD.
90
378
  *
@@ -105,12 +393,19 @@ function calcEstLiqPrice(params) {
105
393
  * @param grossCollateralUsd Position collateral in USD (`collateral_amount` → USD).
106
394
  * @param borrowFeeUsd `unrealized_borrow_fee` in USD.
107
395
  * @param fundingSign `unrealized_funding_sign` — true when the position owes funding.
108
- * @param fundingFeeUsd `unrealized_funding_fee` magnitude in USD.
396
+ * @param fundingFeeUsd `unrealized_funding_fee` magnitude in USD (unsigned; sign travels separately).
109
397
  * @param tradingFeeUsd `unrealized_trading_fee` in USD.
110
398
  * @param projectedTradingFeeUsd Closing fee to reserve (0 for a bare collateral withdrawal).
399
+ * @throws RangeError when any USD input is not a finite `>= 0` number — a
400
+ * negative fee would silently ADD to effective collateral.
111
401
  */
112
402
  function calcEffectiveCollateralUsd(params) {
113
403
  const { grossCollateralUsd, borrowFeeUsd, fundingSign, fundingFeeUsd, tradingFeeUsd, projectedTradingFeeUsd = 0, } = params;
404
+ (0, validate_ts_1.assertFiniteNonNegative)("grossCollateralUsd", grossCollateralUsd);
405
+ (0, validate_ts_1.assertFiniteNonNegative)("borrowFeeUsd", borrowFeeUsd);
406
+ (0, validate_ts_1.assertFiniteNonNegative)("fundingFeeUsd", fundingFeeUsd);
407
+ (0, validate_ts_1.assertFiniteNonNegative)("tradingFeeUsd", tradingFeeUsd);
408
+ (0, validate_ts_1.assertFiniteNonNegative)("projectedTradingFeeUsd", projectedTradingFeeUsd);
114
409
  const eff = grossCollateralUsd -
115
410
  borrowFeeUsd -
116
411
  tradingFeeUsd -
@@ -154,9 +449,42 @@ function calcEffectiveCollateralUsd(params) {
154
449
  * @param closingFeeUsd Full closing fee in USD (`close_fee` → USD).
155
450
  * @param collateralPriceUsd Oracle price of the collateral token (USD per token).
156
451
  * @param collateralDecimal Collateral token decimals — sets the smallest withdraw step.
452
+ * @throws RangeError when any USD / size / price input is not a finite `>= 0`
453
+ * number, when `maintenanceMarginRate` is not finite inside `[0, 1]`, or when
454
+ * `collateralDecimal` is not an integer in `[0, 19]` — the same domains
455
+ * `calcEstLiqPrice` / `calcEstLiqPriceRaw` enforce. Two of these are the
456
+ * reason the whole set is here: a negative fee silently INFLATES the
457
+ * withdrawable amount, and a negative `collateralPriceUsd` drops the
458
+ * one-raw-unit liquidation back-off to 0 — removing the abort-safety margin
459
+ * while still returning a plausible-looking dollar figure.
460
+ * (`maxLeverage === 0` → no leverage cap and `collateralPriceUsd === 0` → no
461
+ * back-off stay documented domain zeros, not garbage.)
157
462
  */
158
463
  function calcMaxReducibleCollateralUsd(params) {
159
464
  const { grossCollateralUsd, sizeInAsset, spotPrice, isLong, entryPrice, maxLeverage, maintenanceMarginRate, minCollValueUsd, borrowFeeUsd, tradingFeeUsd, closingFeeUsd, fundingSign, fundingFeeUsd, collateralPriceUsd, collateralDecimal, } = params;
465
+ // MONEY PATH (see the validation policy at the top of this file): every
466
+ // numeric input is pinned to the same domains calcEstLiqPrice uses. Fees are
467
+ // UNSIGNED here — funding's sign travels separately in `fundingSign` — so a
468
+ // negative one would silently INFLATE the withdrawable amount. One loop so
469
+ // the guard labels cannot drift from the field names; the grossCollateral /
470
+ // borrow / trading legs are re-checked inside calcEffectiveCollateralUsd.
471
+ for (const [label, value] of Object.entries({
472
+ grossCollateralUsd,
473
+ sizeInAsset,
474
+ spotPrice,
475
+ entryPrice,
476
+ maxLeverage,
477
+ minCollValueUsd,
478
+ borrowFeeUsd,
479
+ tradingFeeUsd,
480
+ closingFeeUsd,
481
+ fundingFeeUsd,
482
+ collateralPriceUsd,
483
+ })) {
484
+ (0, validate_ts_1.assertFiniteNonNegative)(label, value);
485
+ }
486
+ (0, validate_ts_1.assertUnitFraction)("maintenanceMarginRate", maintenanceMarginRate);
487
+ (0, validate_ts_1.assertTokenDecimal)("collateralDecimal", collateralDecimal);
160
488
  const notional = sizeInAsset * spotPrice;
161
489
  // effLeverage: matches calculate_effective_collateral_amount(..., projectedTradingFee = 0).
162
490
  const effLeverage = calcEffectiveCollateralUsd({
@@ -312,8 +640,11 @@ function calcBorrowRate(utilizationBps, rate0, rate1, rate2, threshold0Bps, thre
312
640
  /**
313
641
  * Time-weighted borrow rate accrual for a given elapsed period.
314
642
  *
315
- * Matches `calculate_borrow_rate_accrual` in `lp_pool.move`.
316
- * `elapsedMs / intervalMs` gives the number of completed intervals.
643
+ * Continuous proration: `rate × elapsedMs / intervalMs`, matching the formula
644
+ * of `lp_pool.move::calculate_borrow_rate_accrual`
645
+ * (`borrow_rate.mul_u64(elapsed_ms).div_u64(interval_ms)`) — the contract does
646
+ * NOT floor to completed intervals; a partial interval accrues pro rata
647
+ * (verified against the Move source 2026-07-29).
317
648
  */
318
649
  function calcBorrowRateAccrual(borrowRate, elapsedMs, intervalMs) {
319
650
  if (borrowRate === 0 || elapsedMs === 0 || intervalMs === 0)
@@ -358,13 +689,17 @@ function annualizeFundingRate(rate, intervalMs) {
358
689
  * Annualized APY from a NAV ratio over a given number of days.
359
690
  *
360
691
  * Compounds `ratio` (WLP price now / WLP price past) to a 365-day return.
361
- * Returns 0 when the result is not finite (e.g. ratio 0 or days = 0).
692
+ * Documented domain cases returning 0: `ratio <= 0` or `days <= 0` (no valid
693
+ * sample window), and an overflowing compound result.
362
694
  *
363
695
  * @param ratio Current NAV divided by past NAV (e.g. 1.05 for 5% growth).
364
696
  * @param days Number of days elapsed between the two NAV samples.
697
+ * @throws RangeError when `ratio` or `days` is NaN / ±Infinity.
365
698
  */
366
699
  function annualizedApyFromRatio(ratio, days) {
367
- if (days === 0 || ratio <= 0)
700
+ (0, validate_ts_1.assertFinite)("ratio", ratio);
701
+ (0, validate_ts_1.assertFinite)("days", days);
702
+ if (days <= 0 || ratio <= 0)
368
703
  return 0;
369
704
  const apy = Math.pow(ratio, 365 / days) - 1;
370
705
  return Number.isFinite(apy) ? apy : 0;
@@ -373,11 +708,13 @@ function annualizedApyFromRatio(ratio, days) {
373
708
  * Convert a continuously-compounded incentive APR to APY.
374
709
  *
375
710
  * Rewards stream via `flow_rate` (continuous compounding), so APY = e^APR − 1.
376
- * Returns 0 when the result is not finite.
711
+ * Returns 0 when the compound result overflows to Infinity.
377
712
  *
378
713
  * @param apr Time-weighted incentive APR as a decimal fraction (e.g. 0.12 for 12%).
714
+ * @throws RangeError when `apr` is NaN / ±Infinity.
379
715
  */
380
716
  function calcWlpIncentiveApy(apr) {
717
+ (0, validate_ts_1.assertFinite)("apr", apr);
381
718
  const apy = Math.expm1(apr);
382
719
  return Number.isFinite(apy) ? apy : 0;
383
720
  }
@@ -399,17 +736,32 @@ function calcWlpPrice(tvlUsd, totalSupply, lpDecimals) {
399
736
  *
400
737
  * Matches the LP-amount formula in `mint_wlp_with_pricing_tvl` in `lp_pool.move`.
401
738
  * Pass `netDepositUsd` (after the dynamic mint fee is deducted).
402
- * Bootstrap path (totalSupply === 0): lpAmount = netDepositUsd × 10^lpDecimals.
739
+ *
740
+ * Bootstrap ($1/share par, `lpAmount = netDepositUsd × 10^lpDecimals`) applies
741
+ * ONLY to the genuine first mint (`totalSupply === 0`) — exactly like the
742
+ * chain. When supply is outstanding but the priced TVL has floored to 0 (e.g.
743
+ * trader unrealized profit ≥ TVL drove the equity `saturating_sub` to 0) the
744
+ * chain ABORTS `EInvalidBootstrap` rather than par-minting cheap shares that
745
+ * dilute existing LPs (re-audit F-023); this helper throws `RangeError` in
746
+ * that state instead of silently par-quoting.
403
747
  *
404
748
  * @param netDepositUsd Deposit value in USD after dynamic mint fee.
405
749
  * @param tvlUsd Pool TVL in USD at pricing time.
406
750
  * @param totalSupply Current total WLP supply in raw units.
407
751
  * @param lpDecimals WLP token decimals (6).
752
+ * @throws RangeError when inputs are not finite `>= 0` numbers, or on the
753
+ * `totalSupply > 0 && tvlUsd === 0` state (chain aborts `EInvalidBootstrap`).
408
754
  */
409
755
  function calcWlpMintOut(netDepositUsd, tvlUsd, totalSupply, lpDecimals) {
756
+ (0, validate_ts_1.assertFiniteNonNegative)("netDepositUsd", netDepositUsd);
757
+ (0, validate_ts_1.assertFiniteNonNegative)("tvlUsd", tvlUsd);
758
+ (0, validate_ts_1.assertFiniteNonNegative)("totalSupply", totalSupply);
410
759
  const scale = Math.pow(10, lpDecimals);
411
- if (totalSupply === 0 || tvlUsd === 0)
760
+ if (totalSupply === 0)
412
761
  return Math.floor(netDepositUsd * scale);
762
+ if (tvlUsd === 0) {
763
+ throw new RangeError("calcWlpMintOut: totalSupply > 0 with tvlUsd === 0 — the chain aborts EInvalidBootstrap here (par-minting against zero priced TVL dilutes existing LPs; F-023). Retry once the equity snapshot recovers.");
764
+ }
413
765
  return Math.floor((netDepositUsd * totalSupply) / tvlUsd);
414
766
  }
415
767
  /**
@@ -418,13 +770,27 @@ function calcWlpMintOut(netDepositUsd, tvlUsd, totalSupply, lpDecimals) {
418
770
  * Matches the settlement formula in `settle_redeem_with_pricing_tvl` in `lp_pool.move`.
419
771
  * Apply `calcDynamicFeeBps` separately to get the net output.
420
772
  *
773
+ * Chain-divergence notes (display-convenience helper, not settlement-exact):
774
+ * - `tokenPriceUsd === 0` returns 0 here, but the chain ABORTS (`EZeroPrice`,
775
+ * lp_pool.move) — a zero from this helper on that input is a display
776
+ * placeholder, not a real quote.
777
+ * - The burn fee is applied on-chain as
778
+ * `float::from_bps(fee_bps).mul_u64(raw_amount).ceil()` — composing this
779
+ * helper with `calcDynamicFeeBps` in f64 (which cannot `.ceil()` on the raw
780
+ * grid) can drift ±1 raw unit from the settled output.
781
+ *
421
782
  * @param lpAmount LP tokens being redeemed (raw units).
422
783
  * @param tvlUsd Pool TVL in USD at pricing time.
423
784
  * @param totalSupply Current total WLP supply in raw units.
424
785
  * @param tokenPriceUsd Oracle price of the output token.
425
786
  * @param tokenDecimals Output token decimals.
787
+ * @throws RangeError when any numeric input is not a finite `>= 0` number.
426
788
  */
427
789
  function calcWlpRedeemOut(lpAmount, tvlUsd, totalSupply, tokenPriceUsd, tokenDecimals) {
790
+ (0, validate_ts_1.assertFiniteNonNegative)("lpAmount", lpAmount);
791
+ (0, validate_ts_1.assertFiniteNonNegative)("tvlUsd", tvlUsd);
792
+ (0, validate_ts_1.assertFiniteNonNegative)("totalSupply", totalSupply);
793
+ (0, validate_ts_1.assertFiniteNonNegative)("tokenPriceUsd", tokenPriceUsd);
428
794
  if (totalSupply === 0 || tokenPriceUsd === 0)
429
795
  return 0;
430
796
  const burnValueUsd = (tvlUsd * lpAmount) / totalSupply;
@@ -436,7 +802,8 @@ function calcWlpRedeemOut(lpAmount, tvlUsd, totalSupply, tokenPriceUsd, tokenDec
436
802
  * Matches `calculate_dynamic_fee` in `lp_pool.move`. Returns `baseFeeBps` when
437
803
  * the operation moves the token closer to (or does not worsen) its target weight.
438
804
  * Adds an additional fee proportional to the average deviation when it moves
439
- * further away.
805
+ * further away. Clamped to 100% (`bp_scale`) on both the additional term and
806
+ * the total, mirroring the on-chain F-039 clamp.
440
807
  *
441
808
  * @param tokenValueUsd Current USD value of this token in the pool.
442
809
  * @param tvlUsd Total pool TVL in USD.
@@ -444,8 +811,16 @@ function calcWlpRedeemOut(lpAmount, tvlUsd, totalSupply, tokenPriceUsd, tokenDec
444
811
  * @param targetWeightBps Target allocation weight for this token (bps).
445
812
  * @param baseFeeBps Base mint/burn fee (bps).
446
813
  * @param isDeposit True for mint, false for redeem.
814
+ * @throws RangeError when any USD / bps input is not a finite `>= 0` number.
815
+ * (`tvlUsd` / `operationValueUsd` / `targetWeightBps` of exactly 0 stay
816
+ * documented domain cases returning `baseFeeBps` — nothing to deviate from.)
447
817
  */
448
818
  function calcDynamicFeeBps(tokenValueUsd, tvlUsd, operationValueUsd, targetWeightBps, baseFeeBps, isDeposit) {
819
+ (0, validate_ts_1.assertFiniteNonNegative)("tokenValueUsd", tokenValueUsd);
820
+ (0, validate_ts_1.assertFiniteNonNegative)("tvlUsd", tvlUsd);
821
+ (0, validate_ts_1.assertFiniteNonNegative)("operationValueUsd", operationValueUsd);
822
+ (0, validate_ts_1.assertFiniteNonNegative)("targetWeightBps", targetWeightBps);
823
+ (0, validate_ts_1.assertFiniteNonNegative)("baseFeeBps", baseFeeBps);
449
824
  if (tvlUsd === 0 || operationValueUsd === 0 || targetWeightBps === 0)
450
825
  return baseFeeBps;
451
826
  const targetValue = (tvlUsd * targetWeightBps) / Number(constants_ts_1.BPS_SCALE);
@@ -465,5 +840,10 @@ function calcDynamicFeeBps(tokenValueUsd, tvlUsd, operationValueUsd, targetWeigh
465
840
  if (avgTargetValue === 0)
466
841
  return baseFeeBps;
467
842
  const additional = Math.floor((avgDiff / avgTargetValue) * baseFeeBps);
468
- return baseFeeBps + additional;
843
+ // F-039 (lp_pool.move::calculate_dynamic_fee): clamp the dynamic fee to 100%.
844
+ // Both the additional term and the total are capped at bp_scale, mirroring
845
+ // `(base_fee_bps + additional.min(bp_scale)).min(bp_scale)` — without it the
846
+ // fee could exceed the operation amount and the on-chain `amount - fee`
847
+ // subtraction would abort.
848
+ return Math.min(baseFeeBps + Math.min(additional, Number(constants_ts_1.BPS_SCALE)), Number(constants_ts_1.BPS_SCALE));
469
849
  }
@@ -0,0 +1,69 @@
1
+ import type { TransactionArgument } from "@mysten/sui/transactions";
2
+ export declare const U64_MAX = 18446744073709551615n;
3
+ /** Validate a u64 param (bigint passthrough with range check; number must be a safe integer >= 0). */
4
+ export declare function toU64(value: bigint | number, label: string): bigint;
5
+ /** Validate a u128 param (bigint passthrough with range check; number must be a safe integer >= 0). */
6
+ export declare function toU128(value: bigint | number, label: string): bigint;
7
+ /**
8
+ * Validate a u8 param. Returns a `number` (not a bigint) because that is the
9
+ * shape the generated BCS layer takes at this width.
10
+ *
11
+ * Separate from `toU64` on purpose: at u8/u16 the BCS writer already throws on
12
+ * an out-of-range value, but SILENTLY TRUNCATES a fractional one — so the
13
+ * fractional case is what this guard exists to catch (see the header note).
14
+ */
15
+ export declare function toU8(value: number, label: string): number;
16
+ /** Validate a u16 param. Same width caveat as {@link toU8}. */
17
+ export declare function toU16(value: number, label: string): number;
18
+ /** `Option<u64>` param: `null` / `undefined` pass through as `null`, anything else is validated. */
19
+ export declare function toU64OrNull(value: bigint | number | null | undefined, label: string): bigint | null;
20
+ /** `Option<u128>` param: `null` / `undefined` pass through as `null`, anything else is validated. */
21
+ export declare function toU128OrNull(value: bigint | number | null | undefined, label: string): bigint | null;
22
+ /**
23
+ * u64 param that may instead be a PTB result chained from an earlier command
24
+ * (e.g. the `lp_amount` returned by `mintWlp`). A `TransactionArgument` passes
25
+ * through untouched — its value only exists on chain, where Move types it.
26
+ */
27
+ export declare function toU64Arg(value: bigint | number | TransactionArgument, label: string): bigint | TransactionArgument;
28
+ /** Reject NaN / ±Infinity for a `number` money-path input. */
29
+ export declare function assertFinite(label: string, value: number): void;
30
+ /** Reject NaN / ±Infinity / negative for a `number` money-path input. */
31
+ export declare function assertFiniteNonNegative(label: string, value: number): void;
32
+ /**
33
+ * Reject anything outside `[0, 1]` for a `number` input that is a FRACTION of a
34
+ * whole — a maintenance-margin rate above 1 means "maintenance exceeds the
35
+ * entire notional", which is not a rate the caller can have meant.
36
+ */
37
+ export declare function assertUnitFraction(label: string, value: number): void;
38
+ /**
39
+ * Reject a token-decimal outside `[0, 19]` — the domain of the contract's
40
+ * `10u64.pow(decimal)` (`10^19 < 2^64 <= 10^20`), and the same bound the raw
41
+ * `POW10` table in `utils/math.ts` is built over.
42
+ */
43
+ export declare function assertTokenDecimal(label: string, value: number): void;
44
+ /** Reject a negative `bigint` where the on-chain type is unsigned. */
45
+ export declare function assertUnsignedBigInt(label: string, value: bigint): void;
46
+ /**
47
+ * WHOLE-DOLLAR integer USD price for the `waterx_perp_view` read params
48
+ * (`80000n` for BTC at $80k). The Move view applies `float::from(...)`
49
+ * internally — do NOT pass a 1e9-scaled `rawPrice()` value or every
50
+ * pnl/notional-derived field inflates by 1e9 (`rawPrice()` is for tx-build
51
+ * args only; the u128 order-book `triggerPrice` key on `getOrder` is the one
52
+ * view param that still takes that scale). Sub-$1 prices cannot be
53
+ * represented (u64 whole dollars). The price only bases the
54
+ * pnl / close-fee / notional-derived fields — `0n` zero-bases them (still
55
+ * computed with price 0, not skipped).
56
+ */
57
+ export type WholeDollarUsdPrice = bigint | number;
58
+ /**
59
+ * Parse a user/env-supplied value into a whole-dollar u64 price
60
+ * (`WholeDollarUsdPrice`) with NO silent rounding: throws `RangeError` on
61
+ * fractional, negative, non-finite, non-numeric, or `> u64::MAX` input. If
62
+ * rounding is ever wanted it must be explicit at the call site
63
+ * (e.g. `parseWholeDollarU64(Math.round(x))`) — never baked in here.
64
+ *
65
+ * The numeric domain (finite → safe integer → non-negative → `<= u64::MAX`) is
66
+ * `toU64`'s and is NOT restated here; this function only adds the string form,
67
+ * whose digits parse exactly past the 2^53 f64 cliff.
68
+ */
69
+ export declare function parseWholeDollarU64(value: string | number): bigint;