@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
package/README.md CHANGED
@@ -11,19 +11,21 @@ The perp and prediction lines expose builder functions with **colliding names**
11
11
  ```ts
12
12
  import { WaterXClient } from "@waterx/sdk";
13
13
 
14
- // waterxConfigUrl is REQUIRED — the SDK has no built-in default and never reads env.
14
+ // waterxConfigUrl and oracleSource are REQUIRED — the SDK has no built-in
15
+ // defaults and never reads env. oracleSource: see "Oracle sources" below.
15
16
  const client = await WaterXClient.create({
16
17
  network: "TESTNET",
17
18
  waterxConfigUrl:
18
19
  "https://raw.githubusercontent.com/WaterXProtocol/waterx-config/main/testnet.json",
20
+ oracleSource: "pyth_rule",
19
21
  });
20
22
  client.account.createAccount(tx, { alias }); // shared waterx_account + funding (credit/custody)
21
23
  client.perp.buildPlaceOrderTx(params); // perpetuals
22
24
  client.predict.placeOrder(tx, params); // prediction markets
23
25
  // client.perp / client.predict ARE the line clients — sign/execute on them directly:
24
26
  // await client.perp.signAndExecuteTransaction({ transaction: tx, signer })
25
- // each line can target a different network + URL:
26
- // WaterXClient.create({ perp: { network: "MAINNET", waterxConfigUrl: mainnetUrl }, predict: { network: "TESTNET", waterxConfigUrl: testnetUrl } })
27
+ // each line can target a different network + URL (oracleSource stays top-level):
28
+ // WaterXClient.create({ oracleSource: "pyth_rule", perp: { network: "MAINNET", waterxConfigUrl: mainnetUrl }, predict: { network: "TESTNET", waterxConfigUrl: testnetUrl } })
27
29
  ```
28
30
 
29
31
  > `WaterXClient` is the umbrella entry point. `Client` is kept as a **deprecated alias** for one major cycle.
@@ -56,6 +58,7 @@ import { Transaction } from "@mysten/sui/transactions";
56
58
  const client = await WaterXClient.create({
57
59
  network: "TESTNET",
58
60
  waterxConfigUrl: "https://raw.githubusercontent.com/WaterXProtocol/waterx-config/main/testnet.json",
61
+ oracleSource: "pyth_rule", // REQUIRED — single source or a list (the fed set); see "Oracle sources"
59
62
  });
60
63
  const signer = /* your Ed25519Keypair or wallet Signer */;
61
64
 
@@ -94,34 +97,36 @@ import { PredictClient } from "@waterx/sdk/prediction";
94
97
 
95
98
  const waterxConfigUrl =
96
99
  "https://raw.githubusercontent.com/WaterXProtocol/waterx-config/main/testnet.json";
97
- const perp = await PerpClient.create("TESTNET", { waterxConfigUrl }); // or PerpClient.testnet({ waterxConfigUrl })
98
- const predict = await PredictClient.create("TESTNET", { waterxConfigUrl }); // or PredictClient.testnet({ waterxConfigUrl })
100
+ const perp = await PerpClient.create("TESTNET", { waterxConfigUrl, oracleSource: "pyth_rule" }); // or PerpClient.testnet({ ... })
101
+ const predict = await PredictClient.create("TESTNET", { waterxConfigUrl }); // predict line needs no oracle source
99
102
  ```
100
103
 
101
104
  Read-only queries use gRPC `simulateTransaction` (no signer) — the `getX` view helpers, e.g. `await perp.simulate(tx)` or `getMarketData(perp, …)`.
102
105
 
103
106
  ## Oracle sources
104
107
 
105
- ONE client create option, `oracleSource`, selects the price-update source. Each source is **self-contained** — it owns its own infra + config and does **not** back-stop any other source. The name is source-neutral on purpose: a future source need not be Pyth. The SDK **never reads `process.env`** — each consumer wires it from its own env var, so every environment runs the **same SDK version** and differs only by env:
108
+ ONE **required** client create option, `oracleSource`, names the price-update source(s) — a single value or a **list (the fed set)**. Each source is **self-contained** — it owns its own infra + config and does **not** back-stop any other source. There is **no default source**: a client that has not named its sources fails at creation. The name is source-neutral on purpose: a future source need not be Pyth. The SDK **never reads `process.env`** — each consumer wires it from its own env var, so every environment runs the **same SDK version** and differs only by env:
106
109
 
107
- | Option | Values | What it selects |
108
- | -------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
109
- | `oracleSource` | `'pyth_rule'` (default) \| `'pyth_lazer_rule'` | The price-update source. `'pyth_rule'` = Pyth Core updates (Hermes VAA + per-feed update fees, keyless `hermes.pyth.network`). `'pyth_lazer_rule'` = Pyth Lazer signed updates (ONE `leEcdsa` verify per PTB, no per-feed fees); needs `packages.pyth_lazer_rule` feeds + a `pythApiKey`. |
110
+ | Option | Values | What it selects |
111
+ | -------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
112
+ | `oracleSource` | `OracleSource \| OracleSource[]` of `'pyth_rule'` \| `'pyth_lazer_rule'` \| `'waterx_rule'` — REQUIRED, no default | The price-update source(s). `'pyth_rule'` = Pyth Core updates (Hermes VAA + per-feed update fees, keyless `hermes.pyth.network`). `'pyth_lazer_rule'` = Pyth Lazer signed updates (ONE `leEcdsa` verify per PTB, no per-feed fees); needs `packages.pyth_lazer_rule` feeds + a `pythApiKey`. `'waterx_rule'` = the first-party WaterX quote-center (Nautilus-TEE, ed25519-signed CEX prices): one signed batch envelope per build, no API key and no per-update fee; needs `packages.waterx_rule` feeds. |
110
113
 
111
- **No cross-source fallback, no init guard.** Selecting a source whose feed for a requested ticker is absent is **not** an error at client creation it fails at **tx-build** for exactly those tickers (constant-only tickers, which need no price update, are exempt). A present-but-wrong feed id is not validated by the SDK; it aborts on-chain at dry-run.
114
+ **Multi-source fed sets.** With a list, ONE build fetches and feeds EVERY listed source's data in the same PTB; the chain's per-ticker aggregator **weight tables** decide which contributions count — feeding an unweighted rule's price is dropped on-chain, while starving a weighted rule aborts. That asymmetry is what makes weight migrations (Core→Pro, Pyth↔waterx coexistence) safe: keep the list a **superset** of every ticker's weighted rule set and flip weights per ticker at any time — an env edit, never an SDK release. (One caveat: waterx's feed call burns a per-symbol signed-timestamp high-water mark regardless of weights see the replay note below.)
112
115
 
113
- The Pyth Core infra (`client.pyth`: state ids + Hermes endpoint) is **fixed per network** by `PYTH_DEFAULTS` and is **not** deployment-overridable — the canonical `waterx-config` JSON carries no `pyth` block. The `pyth_lazer_rule` source reads only the credential/policy (`api_key`/`fetch`) from `client.pyth`; its on-chain infra comes from `LAZER_DEFAULTS` + config. The credential and fetch policy are passed at client init (`pythApiKey` / `pythFetch`), never through the JSON.
116
+ **No cross-source fallback, no feeds guard at init.** Construction rejects an empty/nullish `oracleSource`, but a listed source whose feed for a requested ticker is absent is **not** an error at client creation — the build fails at **tx-build** only when **no** listed source serves the ticker (constant-only tickers, which need no price update, are exempt). A present-but-wrong feed id is not validated by the SDK; it aborts on-chain at dry-run.
117
+
118
+ Every source's external infra is a **rule-owned per-network table**, never deployment-overridable and never in the config JSON: `PYTH_CORE_INFRA` (`src/oracle/pyth.ts` — Pyth state ids + the keyless Core Hermes endpoint, read-plane accessor `pythCoreHermesEndpoint(network)`), `LAZER_INFRA` (`src/oracle/rules/pyth-lazer-rule.ts`), `WATERX_INFRA` (`src/oracle/rules/waterx-rule.ts` — testnet `quote-center-staging.waterx.app` / mainnet `quote-center.waterx.app`, accessor `waterxQuoteCenterEndpoint(network)`). For **price READS** under a fed set without `pyth_rule`, the documented Pyth Pro base (`pythProHermesEndpoint()` — identical for every subscriber, auth via the Bearer key) applies: resolve the read endpoint with `resolveHermesReadEndpoint(network, sources, override?)` instead of branching by hand, and pair it with `resolveOracleReadPlan` for the per-source served-sets/ids. `client.pyth` is the access-only `PythAccessConfig` — just the caller-supplied `pythApiKey` / `pythFetch` create options (a secret has no place in a public CDN JSON); `client.waterx` is likewise `WaterxAccessConfig` (`waterxEndpoint` / `waterxFetch` overrides only; fetch policy resolves **`waterxFetch` → built-in defaults** — deliberately no `pythFetch` fallback, sources never share config). See the browser/CORS note below.
114
119
 
115
120
  ```ts
116
121
  // Per-environment wiring — the consumer owns the env var, not the SDK:
117
122
  const perp = await PerpClient.create(network, {
118
123
  waterxConfigUrl,
119
- oracleSource: process.env.ORACLE_SOURCE as OracleSource | undefined, // e.g. staging: 'pyth_lazer_rule'
120
- pythApiKey: process.env.PYTH_API_KEY, // Lazer is auth-first; unused by 'pyth_rule'
124
+ oracleSource: process.env.ORACLE_SOURCE!.split(",") as OracleSource[], // REQUIRED; comma list = the fed set
125
+ pythApiKey: process.env.PYTH_API_KEY, // required iff 'pyth_lazer_rule' is listed (Lazer is auth-first)
121
126
  });
122
127
  ```
123
128
 
124
- This is the staging-Lazer / prod-Core rollout pattern: staging sets `ORACLE_SOURCE=pyth_lazer_rule` (+ `PYTH_API_KEY`) while production leaves it unset — flipping an environment is an env-var change, never an SDK release.
129
+ This is the coexistence rollout pattern: staging lists every source under migration (`ORACLE_SOURCE=pyth_rule,pyth_lazer_rule,waterx_rule` + `PYTH_API_KEY`) while production stays single-value (`ORACLE_SOURCE=pyth_rule`) until its weight tables move — flipping an environment is an env-var change, never an SDK release.
125
130
 
126
131
  ### Adding an oracle source (runbook)
127
132
 
@@ -130,10 +135,25 @@ Every source plugs in the same way — routing is driven **only** by the client'
130
135
  1. **Implement `PriceUpdateRule`** in `src/oracle/rules/<name>-rule.ts` — all port fields (`src/oracle/price-update-rule.ts`): `kind`, `requiresFeeSource` (`true` iff the on-chain verify draws a per-update fee — gates the fail-fast fee-source check), `supportedTickers`, `fetchUpdateData`, `narrowUpdateData` (subset a cached whole-universe payload to one build's tickers — a divisible payload returns a per-feed subset, an indivisible one returns itself whole iff fully covered; uncovered ticker → `null` miss), `buildUpdateCalls`.
131
136
  2. **Register it** in `src/oracle/rule-registry.ts` (`DEFAULT_RULES`) under a new `OracleSource` value (added to the union in `price-update-rule.ts`).
132
137
  3. **Publish the on-chain rule package** — its config entry (package ids, per-ticker `feeds`) arrives via the normal `waterx-config` deploy pipeline; type it in `OraclePackages` (`src/oracle/config.ts`).
133
- 4. **Add SDK infra constants** if the source needs external infra that is not part of the config JSON (API endpoints, verifier packages, state objects) — a per-network map in `src/oracle/config.ts`, mirroring `LAZER_DEFAULTS`.
138
+ 4. **Add SDK infra constants** if the source needs external infra that is not part of the config JSON (API endpoints, verifier packages, state objects) — a **rule-owned** per-network table inside the rule's own file, mirroring `LAZER_INFRA` / `WATERX_INFRA` (never on the shared client, never in `oracle/config.ts`). Wire its read-plane served-set/ids into `resolveOracleReadPlan` (`src/oracle/read-plane.ts`).
134
139
  5. **Consumers flip `oracleSource`** per environment — no consumer code change, no SDK re-release.
135
140
 
136
- The in-house `waterx_rule` (ed25519 enclave-signed CEX prices) follows exactly this path when it lands.
141
+ The in-house `waterx_rule` (ed25519 enclave-signed CEX prices, `src/oracle/rules/waterx-rule.ts`) took exactly this path: it pulls one enclave-signed batch envelope covering the requested tickers from the quote-center (`GET /v1/quotes/update?symbols=…`, public read — no auth), then verifies **and** feeds in a single `waterx_rule::collect_batch_latest` call per collector, so it emits no shared verify step. On-chain a **freshness** miss abstains (the other weighted rules cover); a config mismatch or bad signature aborts — and so does a **replayed** signed timestamp (`EReplayedSignature`, audit F-014: a signed tuple is single-use per symbol, weight-independent). Consequence: two PTBs carrying the same envelope for the same symbol cannot both land — never share one fetched envelope across concurrent builds for the same symbol.
142
+
143
+ > **Browser consumers:** this source fetches the quote-center directly from the page, so the quote-center deployment must return `Access-Control-Allow-Origin` for the app's origin. For an origin that is not on that allowlist, point the SDK at your own proxy instead of the default host — the endpoint and the transport are both overridable at client init:
144
+ >
145
+ > ```ts
146
+ > const perp = await PerpClient.create(network, {
147
+ > waterxConfigUrl,
148
+ > oracleSource: "waterx_rule",
149
+ > // absolute URL on your own origin; its base path is PRESERVED, so this
150
+ > // fetches https://app.example/api/quote-center/v1/quotes/update
151
+ > waterxEndpoint: "https://app.example/api/quote-center",
152
+ > waterxFetch: { fetchImpl: myFetch, timeoutMs: 8_000 }, // optional custom transport / policy
153
+ > });
154
+ > ```
155
+ >
156
+ > Unset, `waterxEndpoint` falls back to the rule-owned `WATERX_INFRA[network]` and `waterxFetch` to the built-in policy (15s timeout, 2 retries) — there is deliberately no `pythFetch` fallback. Both are inert under the Pyth sources. They are also top-level options on the umbrella `WaterXClient.create({ oracleSource, waterxEndpoint, waterxFetch, … })`, which forwards them to the perp line. Node/keeper consumers are unaffected by CORS either way.
137
157
 
138
158
  ## Recipes & full surface
139
159
 
@@ -143,7 +163,7 @@ To avoid doc drift, per-action usage lives in maintained, lint-checked code rath
143
163
  - **Prediction recipes:** [`test/prediction/e2e/`](./test/prediction/e2e) — the live reference for `client.predict.*` flows.
144
164
  - **Authoritative export list:** [`src/perp/index.ts`](./src/perp/index.ts) (perp) and [`src/prediction/index.ts`](./src/prediction/index.ts) — clients, builders, view helpers, BCS types, and `*Calls` generated namespaces. The package root (`.`) is [`src/sdk.ts`](./src/sdk.ts) (umbrella + flat-perp re-export); the shared base is published at `@waterx/sdk/account` and `@waterx/sdk/oracle`.
145
165
 
146
- Perp `build*Tx` helpers are Pyth-backed (`async`; they refresh feeds before the call). Oracle (Pyth/Hermes) helpers live in [`src/oracle/`](./src/oracle).
166
+ Perp `build*Tx` helpers are oracle-backed (`async`; they refresh prices before the call) — through whichever source `oracleSource` selects, not Pyth specifically. The oracle layer (sources, rules, refresh) lives in [`src/oracle/`](./src/oracle).
147
167
 
148
168
  ## Development
149
169
 
@@ -57,6 +57,7 @@ exports.requestWithdraw = requestWithdraw;
57
57
  exports.transferToAccount = transferToAccount;
58
58
  exports.receive = receive;
59
59
  const wxa = __importStar(require("../generated/waterx_account/account.js"));
60
+ const validate_ts_1 = require("../utils/validate.js");
60
61
  const account_request_ts_1 = require("./account-request.js");
61
62
  const constants_ts_1 = require("./constants.js");
62
63
  const waterx_account_ts_1 = require("./waterx-account.js");
@@ -195,7 +196,7 @@ function requestWithdraw(client, tx, params) {
195
196
  registry: tx.object(client.config.packages.waterx_account.account_registry),
196
197
  senderRequest: req,
197
198
  accountId: params.accountId,
198
- amount: params.amount,
199
+ amount: (0, validate_ts_1.toU64)(params.amount, "amount"),
199
200
  recipient: params.recipient,
200
201
  extraData: Array.from(params.extraData ?? new Uint8Array()),
201
202
  },
@@ -73,8 +73,9 @@ export interface WithdrawalQueuePackage {
73
73
  /**
74
74
  * Wormhole infra for the cross-chain credit bridge. `state_id` is the same
75
75
  * shared Sui Wormhole `State` object Pyth uses (kept in sync with
76
- * `PYTH_DEFAULTS[*].wormhole_state_id`). Override per-deployment via
77
- * `WaterXConfig.wormhole` if a deployment ever points elsewhere.
76
+ * `PYTH_CORE_INFRA[*].wormhole_state_id` in `oracle/pyth.ts`). Override
77
+ * per-deployment via `WaterXConfig.wormhole` if a deployment ever points
78
+ * elsewhere.
78
79
  */
79
80
  export interface WormholeInfraConfig {
80
81
  /** Shared Sui Wormhole `State` object. */
@@ -15,7 +15,15 @@ export interface ParkedBackingAssetBalance {
15
15
  /** Sum of `listCoins` object balances — TTO'd / owned `Coin<T>` path. */
16
16
  coinsRaw: bigint;
17
17
  }
18
- /** Rescale a u64 raw amount between token decimal precisions (truncates on downscale). */
18
+ /**
19
+ * Rescale a u64 raw amount between token decimal precisions (truncates on
20
+ * downscale). `toDecimals` defaults to {@link COLLATERAL_DECIMALS} — the
21
+ * CREDIT/wxUSD (collateral-typed) scale, 6 on every current deployment — for
22
+ * the common backing-asset → CREDIT direction. When rescaling a custody
23
+ * backing asset, always pass its per-asset config `decimal`
24
+ * (`NativeCustodyAsset.decimal`) as `fromDecimals`; never assume flat 6 — a
25
+ * 9-dec backing asset would otherwise mis-scale by 1000×.
26
+ */
19
27
  export declare function rescaleRawAmount(raw: bigint, fromDecimals: number, toDecimals?: number): bigint;
20
28
  /**
21
29
  * Probe every backing asset registered on `native_custody` for non-zero balances
@@ -26,7 +34,22 @@ export declare function probeParkedBackingAssets(client: AccountClientLike, acco
26
34
  /**
27
35
  * Probe non-zero CREDIT parked at `accountId`'s Sui address. Matches the
28
36
  * address-CREDIT legs inside {@link appendConsolidateAddressCredit}.
37
+ *
38
+ * RPC failures PROPAGATE (no swallow-to-zero): a gRPC error here used to be
39
+ * silently reported as an authoritative zero balance, which made
40
+ * `getSpendableCreditBalance` under-report spendable funds and let
41
+ * `appendConsolidateAddressCredit` build a tx that later aborted on-chain
42
+ * with a confusing insufficient-balance error. Callers that can tolerate a
43
+ * missing probe must catch explicitly.
29
44
  */
30
45
  export declare function probeAddressCreditBalance(client: AccountClientLike, accountId: string): Promise<AddressCreditBalance>;
31
- /** Sum parked backing assets into CREDIT base units at the 1:1 PSM peg. */
46
+ /**
47
+ * Sum parked backing assets into CREDIT base units at the 1:1 PSM peg.
48
+ * Each row rescales from its own per-asset `decimals` (sourced from config
49
+ * `NativeCustodyAsset.decimal` by {@link probeParkedBackingAssets}), so
50
+ * mixed-decimal backing sets (e.g. a 9-dec asset next to 6-dec USDC) sum
51
+ * correctly. `creditDecimals` is the CREDIT/wxUSD **target** scale —
52
+ * collateral-typed, 6 on all current deployments (the config JSON carries no
53
+ * credit decimal).
54
+ */
32
55
  export declare function sumParkedBackingAsCreditRaw(parked: readonly ParkedBackingAssetBalance[], creditDecimals?: number): bigint;
@@ -10,7 +10,15 @@ exports.sumParkedBackingAsCreditRaw = sumParkedBackingAsCreditRaw;
10
10
  * so display totals match what the next async tx-builder would sweep.
11
11
  */
12
12
  const constants_ts_1 = require("../../constants.js");
13
- /** Rescale a u64 raw amount between token decimal precisions (truncates on downscale). */
13
+ /**
14
+ * Rescale a u64 raw amount between token decimal precisions (truncates on
15
+ * downscale). `toDecimals` defaults to {@link COLLATERAL_DECIMALS} — the
16
+ * CREDIT/wxUSD (collateral-typed) scale, 6 on every current deployment — for
17
+ * the common backing-asset → CREDIT direction. When rescaling a custody
18
+ * backing asset, always pass its per-asset config `decimal`
19
+ * (`NativeCustodyAsset.decimal`) as `fromDecimals`; never assume flat 6 — a
20
+ * 9-dec backing asset would otherwise mis-scale by 1000×.
21
+ */
14
22
  function rescaleRawAmount(raw, fromDecimals, toDecimals = constants_ts_1.COLLATERAL_DECIMALS) {
15
23
  if (fromDecimals === toDecimals)
16
24
  return raw;
@@ -58,39 +66,43 @@ async function probeParkedBackingAssets(client, accountId) {
58
66
  /**
59
67
  * Probe non-zero CREDIT parked at `accountId`'s Sui address. Matches the
60
68
  * address-CREDIT legs inside {@link appendConsolidateAddressCredit}.
69
+ *
70
+ * RPC failures PROPAGATE (no swallow-to-zero): a gRPC error here used to be
71
+ * silently reported as an authoritative zero balance, which made
72
+ * `getSpendableCreditBalance` under-report spendable funds and let
73
+ * `appendConsolidateAddressCredit` build a tx that later aborted on-chain
74
+ * with a confusing insufficient-balance error. Callers that can tolerate a
75
+ * missing probe must catch explicitly.
61
76
  */
62
77
  async function probeAddressCreditBalance(client, accountId) {
63
78
  if (!client.config.packages.waterx_credit?.credit_type) {
64
79
  return { fundsRaw: 0n, coinsRaw: 0n };
65
80
  }
66
81
  const creditType = client.creditType();
67
- let fundsRaw = 0n;
82
+ const bal = (await client.getBalance({
83
+ owner: accountId,
84
+ coinType: creditType,
85
+ }));
86
+ const fundsRaw = BigInt(bal.balance?.addressBalance ?? "0");
87
+ const coins = (await client.listCoins({
88
+ owner: accountId,
89
+ coinType: creditType,
90
+ }));
68
91
  let coinsRaw = 0n;
69
- try {
70
- const bal = (await client.getBalance({
71
- owner: accountId,
72
- coinType: creditType,
73
- }));
74
- fundsRaw = BigInt(bal.balance?.addressBalance ?? "0");
75
- }
76
- catch {
77
- // ignore — treat as zero parked funds
78
- }
79
- try {
80
- const coins = (await client.listCoins({
81
- owner: accountId,
82
- coinType: creditType,
83
- }));
84
- for (const coin of coins.objects ?? []) {
85
- coinsRaw += BigInt(coin.balance ?? "0");
86
- }
87
- }
88
- catch {
89
- // ignore — treat as zero owned coins
92
+ for (const coin of coins.objects ?? []) {
93
+ coinsRaw += BigInt(coin.balance ?? "0");
90
94
  }
91
95
  return { fundsRaw, coinsRaw };
92
96
  }
93
- /** Sum parked backing assets into CREDIT base units at the 1:1 PSM peg. */
97
+ /**
98
+ * Sum parked backing assets into CREDIT base units at the 1:1 PSM peg.
99
+ * Each row rescales from its own per-asset `decimals` (sourced from config
100
+ * `NativeCustodyAsset.decimal` by {@link probeParkedBackingAssets}), so
101
+ * mixed-decimal backing sets (e.g. a 9-dec asset next to 6-dec USDC) sum
102
+ * correctly. `creditDecimals` is the CREDIT/wxUSD **target** scale —
103
+ * collateral-typed, 6 on all current deployments (the config JSON carries no
104
+ * credit decimal).
105
+ */
94
106
  function sumParkedBackingAsCreditRaw(parked, creditDecimals = constants_ts_1.COLLATERAL_DECIMALS) {
95
107
  let sum = 0n;
96
108
  for (const row of parked) {
@@ -35,6 +35,7 @@ const account_ts_1 = require("../../generated/waterx_account/account.js");
35
35
  const direct_rule_ts_1 = require("../../generated/waterx_account/direct_rule.js");
36
36
  const withdrawal_queue_ts_1 = require("../../generated/withdrawal_queue/withdrawal_queue.js");
37
37
  const wormhole_bridge_ts_1 = require("../../generated/wormhole_bridge/wormhole_bridge.js");
38
+ const validate_ts_1 = require("../../utils/validate.js");
38
39
  const account_request_ts_1 = require("../account-request.js");
39
40
  // ============================================================================
40
41
  // Byte helpers
@@ -137,15 +138,10 @@ function consumeCreditDeposit(client, tx, params) {
137
138
  }
138
139
  /** Build `withdrawal_queue::route_wormhole`. Returns the `extra_data` argument. */
139
140
  function routeWormhole(client, tx, params) {
140
- if (!Number.isInteger(params.evmDestinationChain) ||
141
- params.evmDestinationChain < 0 ||
142
- params.evmDestinationChain > 0xffff) {
143
- throw new Error(`evmDestinationChain must be a u16 (0..65535), got ${params.evmDestinationChain}`);
144
- }
145
141
  const out = (0, withdrawal_queue_ts_1.routeWormhole)({
146
142
  package: queuePkg(client),
147
143
  arguments: {
148
- evmDestinationChain: params.evmDestinationChain,
144
+ evmDestinationChain: (0, validate_ts_1.toU16)(params.evmDestinationChain, "evmDestinationChain"),
149
145
  evmRecipient: toEvmAddressBytes(params.evmRecipient, "evmRecipient"),
150
146
  evmToken: toEvmAddressBytes(params.evmToken, "evmToken"),
151
147
  },
@@ -156,7 +152,7 @@ function routeWormhole(client, tx, params) {
156
152
  function routeNative(client, tx, params) {
157
153
  const out = (0, withdrawal_queue_ts_1.routeNative)({
158
154
  package: queuePkg(client),
159
- arguments: { minOutput: BigInt(params.minOutput ?? 0n) },
155
+ arguments: { minOutput: (0, validate_ts_1.toU64)(params.minOutput ?? 0n, "minOutput") },
160
156
  typeArguments: [(0, utils_1.normalizeStructTag)(params.assetType)],
161
157
  })(tx);
162
158
  return out;
@@ -174,7 +170,7 @@ function requestCreditWithdraw(client, tx, params) {
174
170
  registry: tx.object(client.config.packages.waterx_account.account_registry),
175
171
  senderRequest: senderRequest,
176
172
  accountId: params.accountId,
177
- amount: params.amount,
173
+ amount: (0, validate_ts_1.toU64)(params.amount, "amount"),
178
174
  recipient: params.recipient,
179
175
  extraData: params.route,
180
176
  },
@@ -205,7 +201,7 @@ function executeWithdrawalWormhole(client, tx, params) {
205
201
  package: queuePkg(client),
206
202
  arguments: {
207
203
  queue: tx.object(queueId(client)),
208
- key: params.key,
204
+ key: (0, validate_ts_1.toU64)(params.key, "key"),
209
205
  request: request,
210
206
  bridge: tx.object(bridgeId(client)),
211
207
  creditRegistry: tx.object(creditRegistryId(client)),
@@ -222,7 +218,7 @@ function executeWithdrawalNative(client, tx, params) {
222
218
  package: queuePkg(client),
223
219
  arguments: {
224
220
  queue: tx.object(queueId(client)),
225
- key: params.key,
221
+ key: (0, validate_ts_1.toU64)(params.key, "key"),
226
222
  request: request,
227
223
  vault: tx.object(custodyVaultId(client)),
228
224
  creditRegistry: tx.object(creditRegistryId(client)),
@@ -9,7 +9,19 @@ export declare const DOUBLE_SCALE = 1000000000000000000n;
9
9
  export declare const SUI_DECIMALS = 9;
10
10
  /** WLP LP-token decimals. */
11
11
  export declare const WLP_DECIMALS = 6;
12
- /** Shared decimals for trading collateral / WLP backing assets (USDC, USDSUI). */
12
+ /**
13
+ * Decimals for **collateral-typed** values only: the CREDIT/wxUSD internal
14
+ * balance and the USD-pegged trading collateral (USDC, USDSUI). All current
15
+ * collateral and the CREDIT coin are 6-dec, and the config JSON carries no
16
+ * credit decimal, so this constant is the single source for the CREDIT scale.
17
+ *
18
+ * Do NOT use it for custody **backing assets** — those carry a per-asset
19
+ * `decimal` in config (`NativeCustodyAsset.decimal`), threaded through
20
+ * `probeParkedBackingAssets` → `sumParkedBackingAsCreditRaw`. If a 9-dec
21
+ * backing asset ever lists, a path that assumed flat 6 for it would mis-scale
22
+ * its parked balance by 1000×; only a change to the CREDIT coin itself (or a
23
+ * non-6-dec collateral listing) would require touching this constant.
24
+ */
13
25
  export declare const COLLATERAL_DECIMALS = 6;
14
26
  /**
15
27
  * @deprecated Import the individual `SUI_DECIMALS` / `WLP_DECIMALS` /
@@ -23,6 +35,8 @@ export declare const TOKEN_DECIMALS: {
23
35
  readonly USDSUI: 6;
24
36
  readonly WLP: 6;
25
37
  };
38
+ export declare const MS_PER_MINUTE: number;
39
+ export declare const MS_PER_HOUR: number;
26
40
  export declare const MS_PER_YEAR: number;
27
41
  /**
28
42
  * Zero-address placeholder used as the sender in dry-run / simulate calls.
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  // Shared, line-agnostic primitives (network id, scaling, decimals, time).
3
- // Perp-domain enums (permissions / order tags / action codes / fee rates) live
4
- // in `perp/constants.ts`; prediction has its own `prediction/constants.ts`.
3
+ // Perp-domain enums (permissions / order tags / action codes) live in
4
+ // `perp/constants.ts`; prediction has its own `prediction/constants.ts`.
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DRY_RUN_SENDER = exports.MS_PER_YEAR = exports.TOKEN_DECIMALS = exports.COLLATERAL_DECIMALS = exports.WLP_DECIMALS = exports.SUI_DECIMALS = exports.DOUBLE_SCALE = exports.FLOAT_SCALE = exports.BPS_SCALE = void 0;
6
+ exports.DRY_RUN_SENDER = exports.MS_PER_YEAR = exports.MS_PER_HOUR = exports.MS_PER_MINUTE = exports.TOKEN_DECIMALS = exports.COLLATERAL_DECIMALS = exports.WLP_DECIMALS = exports.SUI_DECIMALS = exports.DOUBLE_SCALE = exports.FLOAT_SCALE = exports.BPS_SCALE = void 0;
7
7
  // ======== Scaling ========
8
8
  /** Basis-point denominator (10_000). Matches BP_SCALE in the Move contracts. */
9
9
  exports.BPS_SCALE = 10000n;
@@ -16,7 +16,19 @@ exports.DOUBLE_SCALE = 1000000000000000000n;
16
16
  exports.SUI_DECIMALS = 9;
17
17
  /** WLP LP-token decimals. */
18
18
  exports.WLP_DECIMALS = 6;
19
- /** Shared decimals for trading collateral / WLP backing assets (USDC, USDSUI). */
19
+ /**
20
+ * Decimals for **collateral-typed** values only: the CREDIT/wxUSD internal
21
+ * balance and the USD-pegged trading collateral (USDC, USDSUI). All current
22
+ * collateral and the CREDIT coin are 6-dec, and the config JSON carries no
23
+ * credit decimal, so this constant is the single source for the CREDIT scale.
24
+ *
25
+ * Do NOT use it for custody **backing assets** — those carry a per-asset
26
+ * `decimal` in config (`NativeCustodyAsset.decimal`), threaded through
27
+ * `probeParkedBackingAssets` → `sumParkedBackingAsCreditRaw`. If a 9-dec
28
+ * backing asset ever lists, a path that assumed flat 6 for it would mis-scale
29
+ * its parked balance by 1000×; only a change to the CREDIT coin itself (or a
30
+ * non-6-dec collateral listing) would require touching this constant.
31
+ */
20
32
  exports.COLLATERAL_DECIMALS = 6;
21
33
  /**
22
34
  * @deprecated Import the individual `SUI_DECIMALS` / `WLP_DECIMALS` /
@@ -31,6 +43,8 @@ exports.TOKEN_DECIMALS = {
31
43
  WLP: exports.WLP_DECIMALS,
32
44
  };
33
45
  // ======== Time ========
46
+ exports.MS_PER_MINUTE = 60 * 1000;
47
+ exports.MS_PER_HOUR = 60 * 60 * 1000;
34
48
  exports.MS_PER_YEAR = 365 * 24 * 60 * 60 * 1000;
35
49
  // ======== Well-known addresses ========
36
50
  /**
@@ -0,0 +1,8 @@
1
+ /**************************************************************
2
+ * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
3
+ **************************************************************/
4
+ /** Module for floating points */
5
+ import { MoveStruct } from '../../../utils/index.ts';
6
+ export declare const Float: MoveStruct<{
7
+ value: import("@mysten/sui/bcs").BcsType<string, string | number | bigint, "u128">;
8
+ }, "bucket_v2_framework::float::Float">;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /**************************************************************
3
+ * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
4
+ **************************************************************/
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Float = void 0;
7
+ /** Module for floating points */
8
+ const index_ts_1 = require("../../../utils/index.js");
9
+ const bcs_1 = require("@mysten/sui/bcs");
10
+ const $moduleName = 'bucket_v2_framework::float';
11
+ exports.Float = new index_ts_1.MoveStruct({ name: `${$moduleName}::Float`, fields: {
12
+ value: bcs_1.bcs.u128()
13
+ } });
@@ -0,0 +1,36 @@
1
+ /**************************************************************
2
+ * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
3
+ **************************************************************/
4
+ import { type BcsType } from '@mysten/sui/bcs';
5
+ import { MoveStruct } from '../../../utils/index.ts';
6
+ /** An entry in the map */
7
+ export declare function Entry<K extends BcsType<any>, V extends BcsType<any>>(...typeParameters: [
8
+ K,
9
+ V
10
+ ]): MoveStruct<{
11
+ key: K;
12
+ value: V;
13
+ }, `0x2::vec_map::Entry<${K["name"]}, ${V["name"]}>`>;
14
+ /**
15
+ * A map data structure backed by a vector. The map is guaranteed not to contain
16
+ * duplicate keys, but entries are _not_ sorted by key--entries are included in
17
+ * insertion order. All operations are O(N) in the size of the map--the intention
18
+ * of this data structure is only to provide the convenience of programming against
19
+ * a map API. Large maps should use handwritten parent/child relationships instead.
20
+ * Maps that need sorted iteration rather than insertion order iteration should
21
+ * also be handwritten.
22
+ */
23
+ export declare function VecMap<K extends BcsType<any>, V extends BcsType<any>>(...typeParameters: [
24
+ K,
25
+ V
26
+ ]): MoveStruct<{
27
+ contents: BcsType<{
28
+ key: K extends BcsType<infer U, any, string> ? U : never;
29
+ value: V extends BcsType<infer U, any, string> ? U : never;
30
+ }[], Iterable<{
31
+ key: K extends BcsType<any, infer U_1, string> ? U_1 : never;
32
+ value: V extends BcsType<any, infer U_1, string> ? U_1 : never;
33
+ }> & {
34
+ length: number;
35
+ }, string>;
36
+ }, `0x2::vec_map::VecMap<${K["name"]}, ${V["name"]}>`>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Entry = Entry;
4
+ exports.VecMap = VecMap;
5
+ /**************************************************************
6
+ * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
7
+ **************************************************************/
8
+ const bcs_1 = require("@mysten/sui/bcs");
9
+ const index_ts_1 = require("../../../utils/index.js");
10
+ const $moduleName = '0x2::vec_map';
11
+ /** An entry in the map */
12
+ function Entry(...typeParameters) {
13
+ return new index_ts_1.MoveStruct({ name: `${$moduleName}::Entry<${typeParameters[0].name}, ${typeParameters[1].name}>`, fields: {
14
+ key: typeParameters[0],
15
+ value: typeParameters[1]
16
+ } });
17
+ }
18
+ /**
19
+ * A map data structure backed by a vector. The map is guaranteed not to contain
20
+ * duplicate keys, but entries are _not_ sorted by key--entries are included in
21
+ * insertion order. All operations are O(N) in the size of the map--the intention
22
+ * of this data structure is only to provide the convenience of programming against
23
+ * a map API. Large maps should use handwritten parent/child relationships instead.
24
+ * Maps that need sorted iteration rather than insertion order iteration should
25
+ * also be handwritten.
26
+ */
27
+ function VecMap(...typeParameters) {
28
+ return new index_ts_1.MoveStruct({ name: `${$moduleName}::VecMap<${typeParameters[0].name}, ${typeParameters[1].name}>`, fields: {
29
+ contents: bcs_1.bcs.vector(Entry(typeParameters[0], typeParameters[1]))
30
+ } });
31
+ }