@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.
- package/README.md +37 -17
- package/dist/cjs/src/account/account.js +2 -1
- package/dist/cjs/src/account/config.d.ts +3 -2
- package/dist/cjs/src/account/funding/balance.d.ts +25 -2
- package/dist/cjs/src/account/funding/balance.js +36 -24
- package/dist/cjs/src/account/funding/credit.js +6 -10
- package/dist/cjs/src/constants.d.ts +15 -1
- package/dist/cjs/src/constants.js +18 -4
- package/dist/cjs/src/generated/waterx_rule/deps/bucket_v2_framework/float.d.ts +8 -0
- package/dist/cjs/src/generated/waterx_rule/deps/bucket_v2_framework/float.js +13 -0
- package/dist/cjs/src/generated/waterx_rule/deps/sui/vec_map.d.ts +36 -0
- package/dist/cjs/src/generated/waterx_rule/deps/sui/vec_map.js +31 -0
- package/dist/cjs/src/generated/waterx_rule/waterx_rule.d.ts +813 -0
- package/dist/cjs/src/generated/waterx_rule/waterx_rule.js +946 -0
- package/dist/cjs/src/oracle/aggregate.d.ts +23 -21
- package/dist/cjs/src/oracle/aggregate.js +133 -59
- package/dist/cjs/src/oracle/config.d.ts +82 -41
- package/dist/cjs/src/oracle/config.js +0 -42
- package/dist/cjs/src/oracle/host.d.ts +26 -11
- package/dist/cjs/src/oracle/index.d.ts +6 -2
- package/dist/cjs/src/oracle/index.js +36 -8
- package/dist/cjs/src/oracle/price-update-rule.d.ts +16 -10
- package/dist/cjs/src/oracle/price-update-rule.js +3 -3
- package/dist/cjs/src/oracle/pyth.d.ts +44 -0
- package/dist/cjs/src/oracle/pyth.js +48 -6
- package/dist/cjs/src/oracle/read-plane.d.ts +70 -0
- package/dist/cjs/src/oracle/read-plane.js +78 -0
- package/dist/cjs/src/oracle/rule-registry.d.ts +14 -11
- package/dist/cjs/src/oracle/rule-registry.js +16 -11
- package/dist/cjs/src/oracle/rules/pyth-core-rule.js +4 -4
- package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +21 -0
- package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +29 -4
- package/dist/cjs/src/oracle/rules/pyth-rule.js +5 -4
- package/dist/cjs/src/oracle/rules/waterx-rule.d.ts +126 -0
- package/dist/cjs/src/oracle/rules/waterx-rule.js +312 -0
- package/dist/cjs/src/perp/client.d.ts +71 -20
- package/dist/cjs/src/perp/client.js +35 -13
- package/dist/cjs/src/perp/config.d.ts +6 -7
- package/dist/cjs/src/perp/config.js +12 -11
- package/dist/cjs/src/perp/constants.d.ts +0 -6
- package/dist/cjs/src/perp/constants.js +11 -9
- package/dist/cjs/src/perp/fetch/account.js +3 -0
- package/dist/cjs/src/perp/fetch/bridge.js +2 -1
- package/dist/cjs/src/perp/fetch/market.js +2 -1
- package/dist/cjs/src/perp/fetch/positions.d.ts +16 -10
- package/dist/cjs/src/perp/fetch/positions.js +23 -20
- package/dist/cjs/src/perp/index.d.ts +9 -5
- package/dist/cjs/src/perp/index.js +16 -7
- package/dist/cjs/src/perp/liq-view.d.ts +64 -0
- package/dist/cjs/src/perp/liq-view.js +74 -0
- package/dist/cjs/src/perp/user/order.d.ts +13 -0
- package/dist/cjs/src/perp/user/order.js +30 -16
- package/dist/cjs/src/perp/user/staking.js +3 -2
- package/dist/cjs/src/perp/user/trading.js +25 -24
- package/dist/cjs/src/perp/user/wlp.js +6 -5
- package/dist/cjs/src/prediction/utils.d.ts +11 -2
- package/dist/cjs/src/prediction/utils.js +22 -22
- package/dist/cjs/src/unified-client.d.ts +41 -15
- package/dist/cjs/src/unified-client.js +4 -2
- package/dist/cjs/src/utils/format.d.ts +14 -0
- package/dist/cjs/src/utils/format.js +24 -0
- package/dist/cjs/src/utils/math.d.ts +304 -12
- package/dist/cjs/src/utils/math.js +397 -17
- package/dist/cjs/src/utils/validate.d.ts +69 -0
- package/dist/cjs/src/utils/validate.js +183 -0
- package/dist/src/account/account.js +2 -1
- package/dist/src/account/config.d.ts +3 -2
- package/dist/src/account/funding/balance.d.ts +25 -2
- package/dist/src/account/funding/balance.js +36 -24
- package/dist/src/account/funding/credit.js +6 -10
- package/dist/src/constants.d.ts +15 -1
- package/dist/src/constants.js +17 -3
- package/dist/src/generated/waterx_rule/deps/bucket_v2_framework/float.d.ts +8 -0
- package/dist/src/generated/waterx_rule/deps/bucket_v2_framework/float.js +10 -0
- package/dist/src/generated/waterx_rule/deps/sui/vec_map.d.ts +36 -0
- package/dist/src/generated/waterx_rule/deps/sui/vec_map.js +27 -0
- package/dist/src/generated/waterx_rule/waterx_rule.d.ts +813 -0
- package/dist/src/generated/waterx_rule/waterx_rule.js +855 -0
- package/dist/src/oracle/aggregate.d.ts +23 -21
- package/dist/src/oracle/aggregate.js +133 -59
- package/dist/src/oracle/config.d.ts +82 -41
- package/dist/src/oracle/config.js +1 -41
- package/dist/src/oracle/host.d.ts +26 -11
- package/dist/src/oracle/index.d.ts +6 -2
- package/dist/src/oracle/index.js +27 -9
- package/dist/src/oracle/price-update-rule.d.ts +16 -10
- package/dist/src/oracle/price-update-rule.js +3 -3
- package/dist/src/oracle/pyth.d.ts +44 -0
- package/dist/src/oracle/pyth.js +45 -5
- package/dist/src/oracle/read-plane.d.ts +70 -0
- package/dist/src/oracle/read-plane.js +74 -0
- package/dist/src/oracle/rule-registry.d.ts +14 -11
- package/dist/src/oracle/rule-registry.js +16 -11
- package/dist/src/oracle/rules/pyth-core-rule.js +5 -5
- package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +21 -0
- package/dist/src/oracle/rules/pyth-lazer-rule.js +28 -3
- package/dist/src/oracle/rules/pyth-rule.js +5 -4
- package/dist/src/oracle/rules/waterx-rule.d.ts +126 -0
- package/dist/src/oracle/rules/waterx-rule.js +305 -0
- package/dist/src/perp/client.d.ts +71 -20
- package/dist/src/perp/client.js +36 -14
- package/dist/src/perp/config.d.ts +6 -7
- package/dist/src/perp/config.js +11 -9
- package/dist/src/perp/constants.d.ts +0 -6
- package/dist/src/perp/constants.js +10 -8
- package/dist/src/perp/fetch/account.js +3 -0
- package/dist/src/perp/fetch/bridge.js +2 -1
- package/dist/src/perp/fetch/market.js +2 -1
- package/dist/src/perp/fetch/positions.d.ts +16 -10
- package/dist/src/perp/fetch/positions.js +28 -20
- package/dist/src/perp/index.d.ts +9 -5
- package/dist/src/perp/index.js +6 -4
- package/dist/src/perp/liq-view.d.ts +64 -0
- package/dist/src/perp/liq-view.js +71 -0
- package/dist/src/perp/user/order.d.ts +13 -0
- package/dist/src/perp/user/order.js +30 -16
- package/dist/src/perp/user/staking.js +3 -2
- package/dist/src/perp/user/trading.js +25 -24
- package/dist/src/perp/user/wlp.js +6 -5
- package/dist/src/prediction/utils.d.ts +11 -2
- package/dist/src/prediction/utils.js +22 -22
- package/dist/src/unified-client.d.ts +41 -15
- package/dist/src/unified-client.js +4 -2
- package/dist/src/utils/format.d.ts +14 -0
- package/dist/src/utils/format.js +21 -0
- package/dist/src/utils/math.d.ts +304 -12
- package/dist/src/utils/math.js +394 -17
- package/dist/src/utils/validate.d.ts +69 -0
- package/dist/src/utils/validate.js +167 -0
- package/package.json +4 -1
|
@@ -9,12 +9,37 @@
|
|
|
9
9
|
* `Update` PTB value back through a `RuleUpdateHandle` for the feed calls.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.PythLazerRule = exports.LazerApiKeyMissingError = void 0;
|
|
12
|
+
exports.PythLazerRule = exports.LazerApiKeyMissingError = exports.LAZER_INFRA = void 0;
|
|
13
13
|
exports.feedLazerRule = feedLazerRule;
|
|
14
14
|
const bcs_1 = require("@mysten/bcs");
|
|
15
|
-
const config_ts_1 = require("../config.js");
|
|
16
15
|
const price_update_rule_ts_1 = require("../price-update-rule.js");
|
|
17
16
|
const update_fetch_ts_1 = require("../update-fetch.js");
|
|
17
|
+
/**
|
|
18
|
+
* Pyth Lazer (Pyth Pro) external infra — owned by THIS source, by network.
|
|
19
|
+
* Per-network constants for infrastructure Pyth operates (not part of the
|
|
20
|
+
* `waterx-config` JSON), co-located with the only rule that reads them —
|
|
21
|
+
* no other oracle source ever touches a Lazer endpoint or verifier.
|
|
22
|
+
*
|
|
23
|
+
* - `endpoint` — Lazer HTTP API base; signed updates come from
|
|
24
|
+
* `POST /v1/latest_price` (Bearer-authenticated). The service is
|
|
25
|
+
* network-agnostic (one signed payload verifies on any chain that trusts the
|
|
26
|
+
* Lazer signers), so both networks share the production host.
|
|
27
|
+
* - `verifier_package` — the Sui package carrying
|
|
28
|
+
* `pyth_lazer::parse_and_verify_le_ecdsa_update`. Per-network: testnet is
|
|
29
|
+
* still the original v1 publish; mainnet is the v2-upgraded package (which
|
|
30
|
+
* still exposes the v1 entry `pyth_lazer_rule` binds). Values mirror the
|
|
31
|
+
* contract repo's `pyth_lazer_rule/Move.toml` published-at pins.
|
|
32
|
+
*/
|
|
33
|
+
exports.LAZER_INFRA = {
|
|
34
|
+
MAINNET: {
|
|
35
|
+
endpoint: "https://pyth-lazer.dourolabs.app",
|
|
36
|
+
verifier_package: "0xefbfd064480777699fd9c557a5804d72ace7bc82661fdc8d1f1a44ea6d92ee10",
|
|
37
|
+
},
|
|
38
|
+
TESTNET: {
|
|
39
|
+
endpoint: "https://pyth-lazer.dourolabs.app",
|
|
40
|
+
verifier_package: "0xf5bd2141967507050a91b58de3d95e77c432cd90d1799ee46effc27430a68c21",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
18
43
|
/**
|
|
19
44
|
* Signed-update request pins, mirroring what the on-chain rule consumes:
|
|
20
45
|
* - `properties` — `price` + `exponent` are REQUIRED by
|
|
@@ -152,7 +177,7 @@ exports.PythLazerRule = {
|
|
|
152
177
|
if (!apiKey) {
|
|
153
178
|
throw new LazerApiKeyMissingError();
|
|
154
179
|
}
|
|
155
|
-
const update = await fetchLazerSignedUpdate(
|
|
180
|
+
const update = await fetchLazerSignedUpdate(exports.LAZER_INFRA[host.network].endpoint, apiKey, feedIds, host.pyth.fetch);
|
|
156
181
|
return { kind: "pyth_lazer_rule", payload: { update, feedIds } };
|
|
157
182
|
},
|
|
158
183
|
/**
|
|
@@ -189,7 +214,7 @@ exports.PythLazerRule = {
|
|
|
189
214
|
return undefined;
|
|
190
215
|
const lazer = requireLazerPackage(host);
|
|
191
216
|
const [update] = tx.moveCall({
|
|
192
|
-
target: `${
|
|
217
|
+
target: `${exports.LAZER_INFRA[host.network].verifier_package}::pyth_lazer::parse_and_verify_le_ecdsa_update`,
|
|
193
218
|
arguments: [tx.object(lazer.state), tx.object.clock(), tx.pure.vector("u8", payload.update)],
|
|
194
219
|
});
|
|
195
220
|
return { kind: "pyth_lazer_rule", update };
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.feedPythRule = feedPythRule;
|
|
12
12
|
const pyth_rule_ts_1 = require("../../generated/waterx_pyth_rule/pyth_rule.js");
|
|
13
|
+
const pyth_ts_1 = require("../pyth.js");
|
|
13
14
|
function feedPythRule(tx, host, collector, priceInfoObjectId) {
|
|
14
15
|
(0, pyth_rule_ts_1.feed)({
|
|
15
16
|
package: host.config.packages.pyth_rule.published_at,
|
|
@@ -18,10 +19,10 @@ function feedPythRule(tx, host, collector, priceInfoObjectId) {
|
|
|
18
19
|
config: tx.object(host.config.packages.pyth_rule.config),
|
|
19
20
|
// The deployed pyth_rule package is compiled against the Core pyth
|
|
20
21
|
// dependency, so its `&PythState` parameter is the Core-package-qualified
|
|
21
|
-
// type and
|
|
22
|
-
// right state to pass. The config's price_info_object
|
|
23
|
-
// objects to match.
|
|
24
|
-
pythState: tx.object(host.
|
|
22
|
+
// type and the Core source's own per-network state (`PYTH_CORE_INFRA`)
|
|
23
|
+
// is always the right state to pass. The config's price_info_object
|
|
24
|
+
// entries are Core objects to match.
|
|
25
|
+
pythState: tx.object(pyth_ts_1.PYTH_CORE_INFRA[host.network].state_id),
|
|
25
26
|
pythPriceInfo: tx.object(priceInfoObjectId),
|
|
26
27
|
},
|
|
27
28
|
})(tx);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `WaterxRule` — `PriceUpdateRule` for the first-party WaterX quote-center
|
|
3
|
+
* (Nautilus-TEE, ed25519), plus `feedWaterxRule`, the collector-feed leg
|
|
4
|
+
* `aggregateTicker` appends per waterx-routed ticker. Pulls one enclave-signed
|
|
5
|
+
* batch envelope covering every requested ticker from the quote-center
|
|
6
|
+
* (`GET /v1/quotes/update?symbols=…`, endpoint from `host.waterx` — the
|
|
7
|
+
* `waterxEndpoint`/`waterxFetch` create options — else its own `WATERX_INFRA`), then —
|
|
8
|
+
* unlike Pyth Lazer, whose verify is a single shared PTB step — verifies AND
|
|
9
|
+
* feeds in ONE `waterx_rule::collect_batch_latest` call per collector (the Move
|
|
10
|
+
* API bundles the two). So `buildUpdateCalls` emits nothing and the signed
|
|
11
|
+
* envelope is handed straight to the per-ticker feed leg.
|
|
12
|
+
*
|
|
13
|
+
* `collect_batch_latest` is the dual-rule path: it feeds the item matching
|
|
14
|
+
* `collector.symbol()` WITHOUT aggregating, so a waterx-routed ticker composes
|
|
15
|
+
* onto the same collector as Pyth/Supra (compose-then-aggregate). On-chain a
|
|
16
|
+
* FRESHNESS miss ABSTAINS (the other weighted rules cover); a config/
|
|
17
|
+
* integrity mismatch, bad signature, future timestamp — or a REPLAYED signed
|
|
18
|
+
* timestamp — ABORTS (`EReplayedSignature`, audit F-014: a signed tuple is
|
|
19
|
+
* single-use per symbol, enforced by a per-symbol high-water mark BEFORE any
|
|
20
|
+
* weight arbitration). Consequence for concurrent builds: two PTBs carrying
|
|
21
|
+
* the same envelope for the same symbol cannot both land — the second aborts
|
|
22
|
+
* even if the rule is unweighted for that ticker. Never share one fetched
|
|
23
|
+
* envelope across builds that may execute concurrently for the same symbol.
|
|
24
|
+
*/
|
|
25
|
+
import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
|
|
26
|
+
import type { Network } from "../../constants.ts";
|
|
27
|
+
import type { OracleHost } from "../host.ts";
|
|
28
|
+
import { type PriceUpdateRule, type RuleUpdateData } from "../price-update-rule.ts";
|
|
29
|
+
/** The single signing intent the quote-center emits — exported so read-plane
|
|
30
|
+
* consumers can mirror the rule's own envelope intent check (a mispointed
|
|
31
|
+
* endpoint must be rejected by reads exactly as tx-builds reject it). */
|
|
32
|
+
export declare const BATCH_PRICE_INTENT = 1;
|
|
33
|
+
/**
|
|
34
|
+
* WaterX quote-center external infra — owned by THIS source, by network.
|
|
35
|
+
* Mirrors `PYTH_CORE_INFRA` (oracle/pyth.ts) and `LAZER_INFRA`
|
|
36
|
+
* (rules/pyth-lazer-rule.ts): per-network constants for infrastructure the
|
|
37
|
+
* source's operator runs, co-located with the only rule that reads them — no
|
|
38
|
+
* other oracle source ever touches a quote-center endpoint. Public read (no
|
|
39
|
+
* auth), so there is no api_key. `endpoint` has no trailing slash — the rule
|
|
40
|
+
* appends the path.
|
|
41
|
+
*
|
|
42
|
+
* These are the DEFAULTS behind the caller's `client.waterx` access slice
|
|
43
|
+
* (`waterxEndpoint` / `waterxFetch` create options) — the browser-CORS proxy
|
|
44
|
+
* hook, since this is the one source fetched from the page.
|
|
45
|
+
*/
|
|
46
|
+
export declare const WATERX_INFRA: Record<Network, {
|
|
47
|
+
endpoint: string;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* The waterx source's quote-center base for `network` — the ONE accessor
|
|
51
|
+
* consumers (BE/FE read planes) use when, and only when, their own
|
|
52
|
+
* `ORACLE_SOURCE` resolves to `'waterx_rule'`. Mirrors
|
|
53
|
+
* `pythCoreHermesEndpoint`. Under any other source the read endpoint is that
|
|
54
|
+
* source's own configuration — never this one.
|
|
55
|
+
*/
|
|
56
|
+
export declare function waterxQuoteCenterEndpoint(network: Network): string;
|
|
57
|
+
/**
|
|
58
|
+
* One item inside a signed batch payload, mirroring the quote-center
|
|
59
|
+
* `/v1/quotes/update` JSON 1:1 (snake_case). The u64 integer fields are the
|
|
60
|
+
* EXACT values the enclave signed over BCS — `collect_batch_latest` rebuilds
|
|
61
|
+
* the payload on-chain and re-verifies, so they must round-trip byte-for-byte.
|
|
62
|
+
* They are `bigint` (not `number`): {@link parseSignedEnvelope} decodes them
|
|
63
|
+
* from the raw JSON integer literals so a value above `Number.MAX_SAFE_INTEGER`
|
|
64
|
+
* (2^53) can never lose precision and silently abort the on-chain signature
|
|
65
|
+
* check. `num_sources` is a `u8` (≤ 255) and stays a `number`.
|
|
66
|
+
*/
|
|
67
|
+
export interface WaterxBatchItem {
|
|
68
|
+
symbol: string;
|
|
69
|
+
ticker: string;
|
|
70
|
+
sources: bigint[];
|
|
71
|
+
method: string;
|
|
72
|
+
price_timestamp_ms: bigint;
|
|
73
|
+
price_n: bigint;
|
|
74
|
+
price_scale: bigint;
|
|
75
|
+
confidence_n: bigint;
|
|
76
|
+
confidence_scale: bigint;
|
|
77
|
+
max_source_deviation_bps: bigint;
|
|
78
|
+
num_sources: number;
|
|
79
|
+
}
|
|
80
|
+
/** The enclave-signed batch envelope from `GET /v1/quotes/update`. */
|
|
81
|
+
export interface WaterxSignedEnvelope {
|
|
82
|
+
intent: number;
|
|
83
|
+
/** Enclave signing timestamp (ms) — the on-chain `timestamp_ms` argument. */
|
|
84
|
+
timestamp_ms: bigint;
|
|
85
|
+
payload: {
|
|
86
|
+
items: WaterxBatchItem[];
|
|
87
|
+
};
|
|
88
|
+
/** ed25519 signature over `BCS(IntentMessage<BatchPricePayload>)`, hex (± `0x`). */
|
|
89
|
+
signature: string;
|
|
90
|
+
}
|
|
91
|
+
/** `waterx_rule`'s narrowed `RuleUpdateData.payload` shape. */
|
|
92
|
+
export interface WaterxUpdatePayload {
|
|
93
|
+
readonly envelope: WaterxSignedEnvelope;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Parse a quote-center `/v1/quotes/update` response body into a
|
|
97
|
+
* {@link WaterxSignedEnvelope} with the u64 fields decoded as `bigint`, exact.
|
|
98
|
+
*
|
|
99
|
+
* The signature is over `BCS(IntentMessage<BatchPricePayload>)`, so every u64
|
|
100
|
+
* the SDK rebuilds in-PTB must equal the enclave's byte-for-byte or
|
|
101
|
+
* `collect_batch_latest` aborts the whole trade PTB (bad signature — not an
|
|
102
|
+
* abstain). A plain `JSON.parse` yields IEEE-754 doubles that lose precision
|
|
103
|
+
* above 2^53, so instead we recover each integer's exact source literal via the
|
|
104
|
+
* ES2023 reviver `context.source` (Node 21+ / modern browsers) and `BigInt()`
|
|
105
|
+
* it. On an older runtime that passes no `context`, a value within 2^53 is
|
|
106
|
+
* still exact (`BigInt(number)`); a value ABOVE it throws loudly here rather
|
|
107
|
+
* than silently corrupting the payload into an on-chain abort. `num_sources`
|
|
108
|
+
* (u8) and `intent` are coerced back to `number` — both are tiny.
|
|
109
|
+
*/
|
|
110
|
+
export declare function parseSignedEnvelope(text: string): WaterxSignedEnvelope;
|
|
111
|
+
/** Narrow a `RuleUpdateData` to its `WaterxSignedEnvelope`, or `null`. */
|
|
112
|
+
export declare function waterxEnvelopeOf(data: RuleUpdateData): WaterxSignedEnvelope | null;
|
|
113
|
+
/**
|
|
114
|
+
* `waterx_rule::collect_batch_latest(collector, config, clock, enclave_config,
|
|
115
|
+
* enclave, timestamp_ms, payload, sig)` — rebuild the enclave-signed batch
|
|
116
|
+
* payload in-PTB (`new_batch_payload` + one `new_batch_item`/`push_batch_item`
|
|
117
|
+
* per item, the exact shape the enclave signed) and contribute the price for
|
|
118
|
+
* `collector.symbol()` to the collector. One collect call re-verifies the batch
|
|
119
|
+
* signature and picks this collector's symbol out of the batch; on-chain it
|
|
120
|
+
* abstains (records `none`) when the symbol is stale or absent from the batch,
|
|
121
|
+
* but ABORTS `EReplayedSignature` when the symbol's signed timestamp was
|
|
122
|
+
* already accepted (per-symbol high-water mark, audit F-014) — see the module
|
|
123
|
+
* header for the concurrent-build consequence.
|
|
124
|
+
*/
|
|
125
|
+
export declare function feedWaterxRule(tx: Transaction, host: OracleHost, collector: TransactionArgument, envelope: WaterxSignedEnvelope): void;
|
|
126
|
+
export declare const WaterxRule: PriceUpdateRule;
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `WaterxRule` — `PriceUpdateRule` for the first-party WaterX quote-center
|
|
4
|
+
* (Nautilus-TEE, ed25519), plus `feedWaterxRule`, the collector-feed leg
|
|
5
|
+
* `aggregateTicker` appends per waterx-routed ticker. Pulls one enclave-signed
|
|
6
|
+
* batch envelope covering every requested ticker from the quote-center
|
|
7
|
+
* (`GET /v1/quotes/update?symbols=…`, endpoint from `host.waterx` — the
|
|
8
|
+
* `waterxEndpoint`/`waterxFetch` create options — else its own `WATERX_INFRA`), then —
|
|
9
|
+
* unlike Pyth Lazer, whose verify is a single shared PTB step — verifies AND
|
|
10
|
+
* feeds in ONE `waterx_rule::collect_batch_latest` call per collector (the Move
|
|
11
|
+
* API bundles the two). So `buildUpdateCalls` emits nothing and the signed
|
|
12
|
+
* envelope is handed straight to the per-ticker feed leg.
|
|
13
|
+
*
|
|
14
|
+
* `collect_batch_latest` is the dual-rule path: it feeds the item matching
|
|
15
|
+
* `collector.symbol()` WITHOUT aggregating, so a waterx-routed ticker composes
|
|
16
|
+
* onto the same collector as Pyth/Supra (compose-then-aggregate). On-chain a
|
|
17
|
+
* FRESHNESS miss ABSTAINS (the other weighted rules cover); a config/
|
|
18
|
+
* integrity mismatch, bad signature, future timestamp — or a REPLAYED signed
|
|
19
|
+
* timestamp — ABORTS (`EReplayedSignature`, audit F-014: a signed tuple is
|
|
20
|
+
* single-use per symbol, enforced by a per-symbol high-water mark BEFORE any
|
|
21
|
+
* weight arbitration). Consequence for concurrent builds: two PTBs carrying
|
|
22
|
+
* the same envelope for the same symbol cannot both land — the second aborts
|
|
23
|
+
* even if the rule is unweighted for that ticker. Never share one fetched
|
|
24
|
+
* envelope across builds that may execute concurrently for the same symbol.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.WaterxRule = exports.WATERX_INFRA = exports.BATCH_PRICE_INTENT = void 0;
|
|
28
|
+
exports.waterxQuoteCenterEndpoint = waterxQuoteCenterEndpoint;
|
|
29
|
+
exports.parseSignedEnvelope = parseSignedEnvelope;
|
|
30
|
+
exports.waterxEnvelopeOf = waterxEnvelopeOf;
|
|
31
|
+
exports.feedWaterxRule = feedWaterxRule;
|
|
32
|
+
const bcs_1 = require("@mysten/bcs");
|
|
33
|
+
const waterx_rule_ts_1 = require("../../generated/waterx_rule/waterx_rule.js");
|
|
34
|
+
const price_update_rule_ts_1 = require("../price-update-rule.js");
|
|
35
|
+
const update_fetch_ts_1 = require("../update-fetch.js");
|
|
36
|
+
/** The single signing intent the quote-center emits — exported so read-plane
|
|
37
|
+
* consumers can mirror the rule's own envelope intent check (a mispointed
|
|
38
|
+
* endpoint must be rejected by reads exactly as tx-builds reject it). */
|
|
39
|
+
exports.BATCH_PRICE_INTENT = 1;
|
|
40
|
+
/**
|
|
41
|
+
* WaterX quote-center external infra — owned by THIS source, by network.
|
|
42
|
+
* Mirrors `PYTH_CORE_INFRA` (oracle/pyth.ts) and `LAZER_INFRA`
|
|
43
|
+
* (rules/pyth-lazer-rule.ts): per-network constants for infrastructure the
|
|
44
|
+
* source's operator runs, co-located with the only rule that reads them — no
|
|
45
|
+
* other oracle source ever touches a quote-center endpoint. Public read (no
|
|
46
|
+
* auth), so there is no api_key. `endpoint` has no trailing slash — the rule
|
|
47
|
+
* appends the path.
|
|
48
|
+
*
|
|
49
|
+
* These are the DEFAULTS behind the caller's `client.waterx` access slice
|
|
50
|
+
* (`waterxEndpoint` / `waterxFetch` create options) — the browser-CORS proxy
|
|
51
|
+
* hook, since this is the one source fetched from the page.
|
|
52
|
+
*/
|
|
53
|
+
exports.WATERX_INFRA = {
|
|
54
|
+
MAINNET: { endpoint: "https://quote-center.waterx.app" },
|
|
55
|
+
TESTNET: { endpoint: "https://quote-center-staging.waterx.app" },
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The waterx source's quote-center base for `network` — the ONE accessor
|
|
59
|
+
* consumers (BE/FE read planes) use when, and only when, their own
|
|
60
|
+
* `ORACLE_SOURCE` resolves to `'waterx_rule'`. Mirrors
|
|
61
|
+
* `pythCoreHermesEndpoint`. Under any other source the read endpoint is that
|
|
62
|
+
* source's own configuration — never this one.
|
|
63
|
+
*/
|
|
64
|
+
function waterxQuoteCenterEndpoint(network) {
|
|
65
|
+
return exports.WATERX_INFRA[network].endpoint;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Shape check ONLY — the `kind` discriminant is checked separately by the
|
|
69
|
+
* caller before this runs (mirrors the other rules' guard split), so a
|
|
70
|
+
* same-shaped payload from a different rule can never silently pass.
|
|
71
|
+
*/
|
|
72
|
+
function isWaterxUpdatePayloadShape(payload) {
|
|
73
|
+
const env = payload?.envelope;
|
|
74
|
+
return (typeof env === "object" &&
|
|
75
|
+
env !== null &&
|
|
76
|
+
typeof env.signature === "string" &&
|
|
77
|
+
typeof env.timestamp_ms === "bigint" &&
|
|
78
|
+
Array.isArray(env.payload?.items));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parse a quote-center `/v1/quotes/update` response body into a
|
|
82
|
+
* {@link WaterxSignedEnvelope} with the u64 fields decoded as `bigint`, exact.
|
|
83
|
+
*
|
|
84
|
+
* The signature is over `BCS(IntentMessage<BatchPricePayload>)`, so every u64
|
|
85
|
+
* the SDK rebuilds in-PTB must equal the enclave's byte-for-byte or
|
|
86
|
+
* `collect_batch_latest` aborts the whole trade PTB (bad signature — not an
|
|
87
|
+
* abstain). A plain `JSON.parse` yields IEEE-754 doubles that lose precision
|
|
88
|
+
* above 2^53, so instead we recover each integer's exact source literal via the
|
|
89
|
+
* ES2023 reviver `context.source` (Node 21+ / modern browsers) and `BigInt()`
|
|
90
|
+
* it. On an older runtime that passes no `context`, a value within 2^53 is
|
|
91
|
+
* still exact (`BigInt(number)`); a value ABOVE it throws loudly here rather
|
|
92
|
+
* than silently corrupting the payload into an on-chain abort. `num_sources`
|
|
93
|
+
* (u8) and `intent` are coerced back to `number` — both are tiny.
|
|
94
|
+
*/
|
|
95
|
+
function parseSignedEnvelope(text) {
|
|
96
|
+
const raw = JSON.parse(text, (_key, value, context) => {
|
|
97
|
+
if (typeof value !== "number" || !Number.isInteger(value))
|
|
98
|
+
return value;
|
|
99
|
+
if (context?.source !== undefined)
|
|
100
|
+
return BigInt(context.source);
|
|
101
|
+
if (!Number.isSafeInteger(value)) {
|
|
102
|
+
throw new Error("waterx envelope carries an integer above 2^53 and this runtime lacks JSON " +
|
|
103
|
+
"source access — cannot preserve u64 precision for the signed payload");
|
|
104
|
+
}
|
|
105
|
+
return BigInt(value);
|
|
106
|
+
});
|
|
107
|
+
if (typeof raw.signature !== "string" || !Array.isArray(raw.payload?.items)) {
|
|
108
|
+
throw new Error("WaterX quote-center returned a malformed signed envelope");
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
intent: Number(raw.intent),
|
|
112
|
+
timestamp_ms: (raw.timestamp_ms ?? 0n),
|
|
113
|
+
signature: raw.signature,
|
|
114
|
+
payload: {
|
|
115
|
+
items: raw.payload.items.map((i) => ({ ...i, num_sources: Number(i.num_sources) })),
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** The `waterx_rule` deployment entry; throws when the config carries none. */
|
|
120
|
+
function requireWaterxPackage(host) {
|
|
121
|
+
const entry = host.config.packages.waterx_rule;
|
|
122
|
+
if (!entry) {
|
|
123
|
+
throw new Error("waterx_rule package is not deployed in this config");
|
|
124
|
+
}
|
|
125
|
+
return entry;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Resolve the quote-center infra for this host: each field independently from
|
|
129
|
+
* the caller's `client.waterx` access slice (`waterxEndpoint` / `waterxFetch`
|
|
130
|
+
* create options) when set, else this source's own `WATERX_INFRA` default /
|
|
131
|
+
* `fetchWithPolicy`'s built-ins. Deliberately NO fallback onto `pyth.fetch`
|
|
132
|
+
* or any other source's policy — sources stay fully independent.
|
|
133
|
+
*
|
|
134
|
+
* This is the seam a browser consumer needs: the envelope is fetched FROM THE
|
|
135
|
+
* PAGE, so a front end whose origin the quote-center does not allow (CORS)
|
|
136
|
+
* points `endpoint` at a same-origin proxy, or supplies its own `fetchImpl`.
|
|
137
|
+
*/
|
|
138
|
+
function resolveWaterxInfra(host) {
|
|
139
|
+
return {
|
|
140
|
+
endpoint: host.waterx?.endpoint ?? exports.WATERX_INFRA[host.network].endpoint,
|
|
141
|
+
fetch: host.waterx?.fetch,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Pull one enclave-signed batch envelope covering `symbols` from the
|
|
146
|
+
* quote-center. Goes through the shared `fetchWithPolicy` (`../update-fetch.ts`)
|
|
147
|
+
* — same retry/timeout policy as the Pyth/Lazer fetches. No auth: the
|
|
148
|
+
* quote-center read surface is public.
|
|
149
|
+
*
|
|
150
|
+
* The URL is built with `joinEndpointPath`, not `new URL(path, endpoint)`: a
|
|
151
|
+
* leading-slash path is ABSOLUTE and silently drops the endpoint's own base
|
|
152
|
+
* path, which is exactly what a `waterxEndpoint` proxy route is (a
|
|
153
|
+
* `https://app.example/api/quote-center` override would have been rewritten to
|
|
154
|
+
* `https://app.example/v1/quotes/update`, bypassing the proxy). Same footgun
|
|
155
|
+
* that 404'd every Pyth Pro feed by dropping its `/hermes` prefix.
|
|
156
|
+
*/
|
|
157
|
+
async function fetchWaterxSignedUpdate(endpoint, symbols, fetchOpts) {
|
|
158
|
+
const url = (0, update_fetch_ts_1.joinEndpointPath)(endpoint, "v1/quotes/update");
|
|
159
|
+
url.searchParams.set("symbols", symbols.join(","));
|
|
160
|
+
let res;
|
|
161
|
+
try {
|
|
162
|
+
res = await (0, update_fetch_ts_1.fetchWithPolicy)(url.toString(), { method: "GET" }, { ...fetchOpts });
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
if (err instanceof update_fetch_ts_1.FetchPolicyError && err.status !== undefined) {
|
|
166
|
+
const body = err.bodySnippet ? ` ${err.bodySnippet}` : "";
|
|
167
|
+
throw new Error(`WaterX quote-center fetch failed: ${err.status}${body} (retries exhausted after ${err.attempts} attempts)`, { cause: err });
|
|
168
|
+
}
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
if (!res.ok) {
|
|
172
|
+
throw new Error(`WaterX quote-center fetch failed: ${res.status} ${await res.text()}`);
|
|
173
|
+
}
|
|
174
|
+
// Parse from raw text (not res.json()) so the u64 fields are decoded exact as
|
|
175
|
+
// bigint — see parseSignedEnvelope. Malformed-shape check lives there.
|
|
176
|
+
const envelope = parseSignedEnvelope(await res.text());
|
|
177
|
+
if (envelope.intent !== exports.BATCH_PRICE_INTENT) {
|
|
178
|
+
throw new Error(`WaterX quote-center returned intent ${envelope.intent}, expected BATCH_PRICE_INTENT ${exports.BATCH_PRICE_INTENT}`);
|
|
179
|
+
}
|
|
180
|
+
return envelope;
|
|
181
|
+
}
|
|
182
|
+
/** Narrow a `RuleUpdateData` to its `WaterxSignedEnvelope`, or `null`. */
|
|
183
|
+
function waterxEnvelopeOf(data) {
|
|
184
|
+
const payload = (0, price_update_rule_ts_1.assertRuleUpdateData)(data, "waterx_rule", isWaterxUpdatePayloadShape, "{ envelope: { intent, timestamp_ms, payload: { items }, signature } }");
|
|
185
|
+
return payload?.envelope ?? null;
|
|
186
|
+
}
|
|
187
|
+
/** Strip an optional `0x` prefix, then decode hex → bytes. */
|
|
188
|
+
function decodeSig(hex) {
|
|
189
|
+
return (0, bcs_1.fromHex)(hex.startsWith("0x") ? hex.slice(2) : hex);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* `waterx_rule::collect_batch_latest(collector, config, clock, enclave_config,
|
|
193
|
+
* enclave, timestamp_ms, payload, sig)` — rebuild the enclave-signed batch
|
|
194
|
+
* payload in-PTB (`new_batch_payload` + one `new_batch_item`/`push_batch_item`
|
|
195
|
+
* per item, the exact shape the enclave signed) and contribute the price for
|
|
196
|
+
* `collector.symbol()` to the collector. One collect call re-verifies the batch
|
|
197
|
+
* signature and picks this collector's symbol out of the batch; on-chain it
|
|
198
|
+
* abstains (records `none`) when the symbol is stale or absent from the batch,
|
|
199
|
+
* but ABORTS `EReplayedSignature` when the symbol's signed timestamp was
|
|
200
|
+
* already accepted (per-symbol high-water mark, audit F-014) — see the module
|
|
201
|
+
* header for the concurrent-build consequence.
|
|
202
|
+
*/
|
|
203
|
+
function feedWaterxRule(tx, host, collector, envelope) {
|
|
204
|
+
const wr = requireWaterxPackage(host);
|
|
205
|
+
const pkg = wr.published_at;
|
|
206
|
+
const payload = (0, waterx_rule_ts_1.newBatchPayload)({ package: pkg })(tx);
|
|
207
|
+
for (const item of envelope.payload.items) {
|
|
208
|
+
// u64 fields are already exact bigints (see parseSignedEnvelope) — passed
|
|
209
|
+
// through verbatim so the rebuilt BCS matches the enclave's signed bytes.
|
|
210
|
+
const itemArg = (0, waterx_rule_ts_1.newBatchItem)({
|
|
211
|
+
package: pkg,
|
|
212
|
+
arguments: {
|
|
213
|
+
symbol: item.symbol,
|
|
214
|
+
ticker: item.ticker,
|
|
215
|
+
sources: item.sources,
|
|
216
|
+
method: item.method,
|
|
217
|
+
priceTimestampMs: item.price_timestamp_ms,
|
|
218
|
+
priceN: item.price_n,
|
|
219
|
+
priceScale: item.price_scale,
|
|
220
|
+
confidenceN: item.confidence_n,
|
|
221
|
+
confidenceScale: item.confidence_scale,
|
|
222
|
+
maxSourceDeviationBps: item.max_source_deviation_bps,
|
|
223
|
+
numSources: item.num_sources,
|
|
224
|
+
},
|
|
225
|
+
})(tx);
|
|
226
|
+
(0, waterx_rule_ts_1.pushBatchItem)({ package: pkg, arguments: { payload, item: itemArg } })(tx);
|
|
227
|
+
}
|
|
228
|
+
(0, waterx_rule_ts_1.collectBatchLatest)({
|
|
229
|
+
package: pkg,
|
|
230
|
+
arguments: {
|
|
231
|
+
collector,
|
|
232
|
+
config: tx.object(wr.config),
|
|
233
|
+
enclaveConfig: tx.object(wr.enclave_config),
|
|
234
|
+
enclave: tx.object(wr.enclave),
|
|
235
|
+
timestampMs: envelope.timestamp_ms,
|
|
236
|
+
payload,
|
|
237
|
+
sig: Array.from(decodeSig(envelope.signature)),
|
|
238
|
+
},
|
|
239
|
+
})(tx);
|
|
240
|
+
}
|
|
241
|
+
exports.WaterxRule = {
|
|
242
|
+
kind: "waterx_rule",
|
|
243
|
+
// Verification is an in-Move ed25519 check with no Coin argument — no
|
|
244
|
+
// update fee — see `PriceUpdateRule.requiresFeeSource`.
|
|
245
|
+
requiresFeeSource: false,
|
|
246
|
+
/** Tickers with a `waterx_rule.feeds` entry (keyed by oracle ticker). */
|
|
247
|
+
supportedTickers(host) {
|
|
248
|
+
return Object.keys(host.config.packages.waterx_rule?.feeds ?? {});
|
|
249
|
+
},
|
|
250
|
+
/**
|
|
251
|
+
* Pulls one enclave-signed batch envelope covering `tickers` from the
|
|
252
|
+
* quote-center, and only returns it when it actually covers ALL of them.
|
|
253
|
+
*
|
|
254
|
+
* A 200 whose `items` omit a requested symbol is a valid, well-signed
|
|
255
|
+
* envelope — nothing downstream would reject it, and the build would emit a
|
|
256
|
+
* `collect_batch_latest` that abstains for the missing symbol, surfacing as
|
|
257
|
+
* an on-chain `EMissingPriceSource` (or a silently thinner weighted set) much
|
|
258
|
+
* later. Same coverage rule the cached path enforces in
|
|
259
|
+
* {@link WaterxRule.narrowUpdateData}; the difference is disposition — a
|
|
260
|
+
* cache miss falls back to this live fetch, whereas the live source itself
|
|
261
|
+
* coming up short has no fallback left, so it throws deterministically here.
|
|
262
|
+
*/
|
|
263
|
+
async fetchUpdateData(host, tickers) {
|
|
264
|
+
if (tickers.length === 0)
|
|
265
|
+
return null;
|
|
266
|
+
// Package-level check first: a config without the deployment must say so,
|
|
267
|
+
// not fail per ticker as if only that feed were missing.
|
|
268
|
+
const { feeds } = requireWaterxPackage(host);
|
|
269
|
+
for (const ticker of tickers) {
|
|
270
|
+
if (feeds[ticker] === undefined) {
|
|
271
|
+
throw new Error(`No waterx_rule feed listed for ticker: ${ticker}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const { endpoint, fetch: fetchOpts } = resolveWaterxInfra(host);
|
|
275
|
+
const envelope = await fetchWaterxSignedUpdate(endpoint, tickers, fetchOpts);
|
|
276
|
+
const covered = new Set(envelope.payload.items.map((i) => i.symbol));
|
|
277
|
+
const missing = tickers.filter((t) => !covered.has(t));
|
|
278
|
+
if (missing.length > 0) {
|
|
279
|
+
throw new Error(`WaterX quote-center envelope does not cover ticker(s): ${missing.join(", ")} ` +
|
|
280
|
+
`(requested ${tickers.join(", ")}; served ${[...covered].join(", ") || "none"})`);
|
|
281
|
+
}
|
|
282
|
+
return { kind: "waterx_rule", payload: { envelope } };
|
|
283
|
+
},
|
|
284
|
+
/**
|
|
285
|
+
* One signed batch envelope carries a single ed25519 signature over its whole
|
|
286
|
+
* `payload` — it is indivisible: it can only be served whole (re-verified from
|
|
287
|
+
* the full item set). Returns the whole payload iff every requested ticker's
|
|
288
|
+
* item is present in THIS envelope; any coverage gap → `null` (miss), never a
|
|
289
|
+
* silent partial.
|
|
290
|
+
*/
|
|
291
|
+
narrowUpdateData(_host, data, tickers) {
|
|
292
|
+
const envelope = waterxEnvelopeOf(data);
|
|
293
|
+
if (!envelope || tickers.length === 0)
|
|
294
|
+
return null;
|
|
295
|
+
const covered = new Set(envelope.payload.items.map((i) => i.symbol));
|
|
296
|
+
for (const ticker of tickers) {
|
|
297
|
+
if (!covered.has(ticker))
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
return { kind: "waterx_rule", payload: { envelope } };
|
|
301
|
+
},
|
|
302
|
+
/**
|
|
303
|
+
* No shared verify step: `waterx_rule::collect_batch_latest` bundles verify
|
|
304
|
+
* AND feed into one per-collector call, appended by {@link feedWaterxRule} in
|
|
305
|
+
* the per-ticker aggregate leg. So this emits nothing and returns `void` — the
|
|
306
|
+
* signed envelope reaches the feed leg via `aggregate.ts`'s per-ticker map
|
|
307
|
+
* (built from the group's fetched data), not a `RuleUpdateHandle`.
|
|
308
|
+
*/
|
|
309
|
+
buildUpdateCalls(_tx, _host, _data, _opts) {
|
|
310
|
+
return;
|
|
311
|
+
},
|
|
312
|
+
};
|
|
@@ -12,27 +12,49 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { BaseLineClient } from "../base-client.ts";
|
|
14
14
|
import type { OracleSource } from "../oracle/price-update-rule.ts";
|
|
15
|
-
import
|
|
15
|
+
import type { FetchPolicy } from "../oracle/update-fetch.ts";
|
|
16
|
+
import { type LoadConfigOptions, type PythAccessConfig, type PythFetchPolicy, type WaterxAccessConfig, type WaterXConfig, type WormholeInfraConfig } from "./config.ts";
|
|
16
17
|
import type { Network } from "./constants.ts";
|
|
17
18
|
export interface CreateClientOptions extends LoadConfigOptions {
|
|
18
19
|
grpcUrl?: string;
|
|
19
20
|
/**
|
|
20
|
-
* Which oracle price-update source drives `refreshOraclePrices`.
|
|
21
|
-
* is
|
|
21
|
+
* Which oracle price-update source drives `refreshOraclePrices`. REQUIRED —
|
|
22
|
+
* there is NO default source: every deployment names its source explicitly
|
|
23
|
+
* (wire it from your own env var, e.g. `ORACLE_SOURCE`). Each source is
|
|
24
|
+
* self-contained (own infra, own endpoints, own config) with NO cross-source
|
|
25
|
+
* fallback:
|
|
22
26
|
*
|
|
23
|
-
* - `'pyth_rule'`
|
|
24
|
-
*
|
|
27
|
+
* - `'pyth_rule'` — Pyth Core `pyth_rule` updates (Hermes VAA + per-feed
|
|
28
|
+
* update fees); Core state + Hermes endpoint live in the source's own
|
|
29
|
+
* `PYTH_CORE_INFRA` table.
|
|
25
30
|
* - `'pyth_lazer_rule'` — Pyth Lazer signed updates (ONE `leEcdsa` verify
|
|
26
31
|
* per PTB, no per-feed fees); needs `packages.pyth_lazer_rule` with feeds
|
|
27
|
-
* and a `pythApiKey` (Lazer is auth-first)
|
|
32
|
+
* and a `pythApiKey` (Lazer is auth-first); Lazer infra lives in the
|
|
33
|
+
* source's own `LAZER_INFRA` table.
|
|
34
|
+
* - `'waterx_rule'` — the first-party WaterX quote-center (Nautilus-TEE,
|
|
35
|
+
* ed25519-signed batches): ONE envelope covering the build's tickers,
|
|
36
|
+
* verified AND fed by a single `collect_batch_latest` per collector. No
|
|
37
|
+
* credential and no per-update fee; needs `packages.waterx_rule` with
|
|
38
|
+
* feeds. Quote-center infra lives in the source's own `WATERX_INFRA`
|
|
39
|
+
* table; endpoint/transport overridable via
|
|
40
|
+
* {@link CreateClientOptions.waterxEndpoint} /
|
|
41
|
+
* {@link CreateClientOptions.waterxFetch} — the browser-CORS proxy hook,
|
|
42
|
+
* since this is the one source fetched from the page.
|
|
28
43
|
*
|
|
29
|
-
*
|
|
30
|
-
* Selecting a source whose feed for a requested
|
|
31
|
-
* error at client creation: it fails at tx-build
|
|
32
|
-
* tickers (see `refreshOraclePrices`).
|
|
33
|
-
*
|
|
44
|
+
* The name is source-neutral on purpose — a source need not be Pyth (as
|
|
45
|
+
* `'waterx_rule'` shows). Selecting a source whose feed for a requested
|
|
46
|
+
* ticker is absent is NOT an error at client creation: it fails at tx-build
|
|
47
|
+
* time for exactly those tickers (see `refreshOraclePrices`).
|
|
48
|
+
*
|
|
49
|
+
* Accepts a SINGLE source or a LIST. A list means every listed source's
|
|
50
|
+
* data is fetched and fed in one build — required whenever the on-chain
|
|
51
|
+
* weight tables have more than one rule weighted (e.g. a Core→Pro or
|
|
52
|
+
* Pro+Waterx coexistence window): the chain drops unweighted contributions
|
|
53
|
+
* (harmless) but aborts on a starved weighted rule, so the list must stay a
|
|
54
|
+
* SUPERSET of every ticker's weighted set. Order is meaningful to consumers
|
|
55
|
+
* (read-plane priority), not to the on-chain build.
|
|
34
56
|
*/
|
|
35
|
-
oracleSource
|
|
57
|
+
oracleSource: OracleSource | OracleSource[];
|
|
36
58
|
/**
|
|
37
59
|
* Pyth Lazer access token (`Authorization: Bearer …`). Required under
|
|
38
60
|
* `oracleSource: 'pyth_lazer_rule'` (Lazer is auth-first) and unused by
|
|
@@ -46,17 +68,46 @@ export interface CreateClientOptions extends LoadConfigOptions {
|
|
|
46
68
|
* `fetchWithPolicy`). Optional — defaults to 15s timeout, 2 retries.
|
|
47
69
|
*/
|
|
48
70
|
pythFetch?: PythFetchPolicy;
|
|
71
|
+
/**
|
|
72
|
+
* Quote-center base URL for `oracleSource: 'waterx_rule'` — overrides the
|
|
73
|
+
* source's own per-network `WATERX_INFRA` default.
|
|
74
|
+
*
|
|
75
|
+
* This is the one source a BROWSER fetches itself (the signed envelope is
|
|
76
|
+
* pulled from the page), so it is bound by the quote-center deployment's CORS
|
|
77
|
+
* allowlist. A front end whose origin is not allowed — or one that must route
|
|
78
|
+
* egress through its own backend — points this at a same-origin proxy that
|
|
79
|
+
* forwards `GET /v1/quotes/update`. Unused by the Pyth sources.
|
|
80
|
+
*
|
|
81
|
+
* An absolute URL. Any base PATH is preserved (`joinEndpointPath`), so
|
|
82
|
+
* `https://app.example/api/quote-center` fetches
|
|
83
|
+
* `https://app.example/api/quote-center/v1/quotes/update` — a proxy route
|
|
84
|
+
* survives instead of being rewritten to the origin root.
|
|
85
|
+
*/
|
|
86
|
+
waterxEndpoint?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Retry/timeout policy — and `fetchImpl` — for the quote-center fetch (see
|
|
89
|
+
* `fetchWithPolicy`). Optional: falls back to the built-in defaults — never
|
|
90
|
+
* to `pythFetch` (sources stay independent). Supply `fetchImpl` to route the request through your own
|
|
91
|
+
* transport (a proxying `fetch` wrapper, a non-global `fetch`, a test double).
|
|
92
|
+
*/
|
|
93
|
+
waterxFetch?: FetchPolicy;
|
|
49
94
|
}
|
|
50
95
|
export declare class PerpClient extends BaseLineClient<WaterXConfig> {
|
|
51
|
-
/** Pyth
|
|
52
|
-
pyth:
|
|
96
|
+
/** Caller-supplied Pyth credential + fetch policy — NO infra; each source owns its own tables. */
|
|
97
|
+
pyth: PythAccessConfig;
|
|
98
|
+
/**
|
|
99
|
+
* Caller-supplied quote-center overrides for `oracleSource: 'waterx_rule'`
|
|
100
|
+
* (`waterxEndpoint` / `waterxFetch` create options) — access-only, mirroring
|
|
101
|
+
* `pyth` above; unset fields resolve against the rule's own `WATERX_INFRA`.
|
|
102
|
+
*/
|
|
103
|
+
waterx: WaterxAccessConfig;
|
|
53
104
|
/** Wormhole infra for the credit bridge (network defaults unless overridden). */
|
|
54
105
|
wormhole: WormholeInfraConfig;
|
|
55
|
-
/**
|
|
56
|
-
readonly
|
|
106
|
+
/** The fed set: `oracleSource` create option normalized to a non-empty, deduped list. */
|
|
107
|
+
readonly oracleSources: readonly OracleSource[];
|
|
57
108
|
/** Canonical-schema lookups (delegated to below); no transport. */
|
|
58
109
|
private readonly view;
|
|
59
|
-
constructor(network: Network, config: WaterXConfig, opts
|
|
110
|
+
constructor(network: Network, config: WaterXConfig, opts: CreateClientOptions);
|
|
60
111
|
/**
|
|
61
112
|
* Async factory: fetches the deployment config for `network` and returns
|
|
62
113
|
* a ready-to-use client. Pass `opts.cache=true` to memoize the JSON.
|
|
@@ -65,9 +116,9 @@ export declare class PerpClient extends BaseLineClient<WaterXConfig> {
|
|
|
65
116
|
* not an error at init — it surfaces at tx-build time for the specific
|
|
66
117
|
* tickers that source can't serve (see `refreshOraclePrices`).
|
|
67
118
|
*/
|
|
68
|
-
static create(network: Network, opts
|
|
69
|
-
static mainnet(opts
|
|
70
|
-
static testnet(opts
|
|
119
|
+
static create(network: Network, opts: CreateClientOptions): Promise<PerpClient>;
|
|
120
|
+
static mainnet(opts: CreateClientOptions): Promise<PerpClient>;
|
|
121
|
+
static testnet(opts: CreateClientOptions): Promise<PerpClient>;
|
|
71
122
|
/** @see PerpConfigView.getMarket */
|
|
72
123
|
getMarket(ticker: string): import("./config.ts").WaterxPerpMarketEntry;
|
|
73
124
|
/** @see PerpConfigView.getAggregator */
|