@waterx/sdk 4.3.0 → 4.3.2
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 +9 -4
- package/dist/cjs/src/oracle/aggregate.d.ts +4 -3
- package/dist/cjs/src/oracle/aggregate.js +7 -5
- package/dist/cjs/src/oracle/index.d.ts +3 -1
- package/dist/cjs/src/oracle/index.js +11 -1
- package/dist/cjs/src/oracle/price-update-rule.d.ts +31 -9
- package/dist/cjs/src/oracle/price-update-rule.js +20 -0
- package/dist/cjs/src/oracle/read-plane.js +11 -4
- package/dist/cjs/src/oracle/rules/pyth-core-rule.js +2 -1
- package/dist/cjs/src/oracle/rules/pyth-lazer-rule.d.ts +25 -7
- package/dist/cjs/src/oracle/rules/pyth-lazer-rule.js +46 -19
- package/dist/cjs/src/oracle/rules/waterx-rule.js +4 -1
- package/dist/cjs/src/oracle/source-list.d.ts +36 -0
- package/dist/cjs/src/oracle/source-list.js +57 -0
- package/dist/cjs/src/perp/client.d.ts +1 -1
- package/dist/cjs/src/perp/client.js +11 -7
- package/dist/cjs/src/perp/config-view.js +7 -6
- package/dist/cjs/src/perp/index.d.ts +1 -1
- package/dist/cjs/src/perp/index.js +8 -4
- package/dist/cjs/src/perp/user/staking.js +2 -1
- package/dist/cjs/src/utils/config.js +2 -1
- package/dist/cjs/src/utils/record.d.ts +12 -0
- package/dist/cjs/src/utils/record.js +22 -0
- package/dist/src/oracle/aggregate.d.ts +4 -3
- package/dist/src/oracle/aggregate.js +7 -5
- package/dist/src/oracle/index.d.ts +3 -1
- package/dist/src/oracle/index.js +8 -1
- package/dist/src/oracle/price-update-rule.d.ts +31 -9
- package/dist/src/oracle/price-update-rule.js +19 -0
- package/dist/src/oracle/read-plane.js +11 -4
- package/dist/src/oracle/rules/pyth-core-rule.js +2 -1
- package/dist/src/oracle/rules/pyth-lazer-rule.d.ts +25 -7
- package/dist/src/oracle/rules/pyth-lazer-rule.js +46 -19
- package/dist/src/oracle/rules/waterx-rule.js +4 -1
- package/dist/src/oracle/source-list.d.ts +36 -0
- package/dist/src/oracle/source-list.js +53 -0
- package/dist/src/perp/client.d.ts +1 -1
- package/dist/src/perp/client.js +11 -7
- package/dist/src/perp/config-view.js +7 -6
- package/dist/src/perp/index.d.ts +1 -1
- package/dist/src/perp/index.js +1 -1
- package/dist/src/perp/user/staking.js +2 -1
- package/dist/src/utils/config.js +2 -1
- package/dist/src/utils/record.d.ts +12 -0
- package/dist/src/utils/record.js +19 -0
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `source-list.ts` — THE parser for a consumer's `ORACLE_SOURCE` env string
|
|
4
|
+
* (comma list of `OracleSource` values → the fed set). The FE and BE
|
|
5
|
+
* previously carried twin hand-written parsers whose semantics drifted once
|
|
6
|
+
* in review (a trailing comma booted one deployment green and 500'd the
|
|
7
|
+
* other); this canonical behavior is what both fold onto:
|
|
8
|
+
*
|
|
9
|
+
* - split on `,`, trim entries, DROP empties (trailing/doubled commas are
|
|
10
|
+
* the most common env typo, never a boot failure)
|
|
11
|
+
* - validate every entry against {@link ORACLE_SOURCES}
|
|
12
|
+
* - dedupe, order-preserving (list order is consumer read-plane policy —
|
|
13
|
+
* the SDK's own fed-set build treats the list as a set)
|
|
14
|
+
* - throw an operator-actionable error on empty/unset/invalid input —
|
|
15
|
+
* there is NO default oracle source
|
|
16
|
+
*
|
|
17
|
+
* The SDK still never reads `process.env` — callers pass the raw string.
|
|
18
|
+
*
|
|
19
|
+
* STRICTER than the consumers' previous `in`-operator checks: a value named
|
|
20
|
+
* like an `Object.prototype` key (`toString`, `constructor`, …) passed those
|
|
21
|
+
* and died deep in the stack; `Set.has` rejects it here at parse.
|
|
22
|
+
*
|
|
23
|
+
* Zod adopters: this THROWS a plain Error. Inside a zod `.transform()` a
|
|
24
|
+
* throw escapes `schema.parse()` un-aggregated and masks sibling issues —
|
|
25
|
+
* wrap it: `try { return parseOracleSourceList(raw); } catch (e) {
|
|
26
|
+
* ctx.addIssue({ code: "custom", message: (e as Error).message }); return
|
|
27
|
+
* z.NEVER; }`.
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.isOracleSource = isOracleSource;
|
|
31
|
+
exports.parseOracleSourceList = parseOracleSourceList;
|
|
32
|
+
const price_update_rule_ts_1 = require("./price-update-rule.js");
|
|
33
|
+
// Widened-annotation Set (not an assertion) so the type predicate below
|
|
34
|
+
// narrows by CONSTRUCTION rather than by cast.
|
|
35
|
+
const ORACLE_SOURCE_SET = new Set(price_update_rule_ts_1.ORACLE_SOURCES);
|
|
36
|
+
/**
|
|
37
|
+
* THE runtime membership check for {@link ORACLE_SOURCES} — the parser below
|
|
38
|
+
* and `PerpClient`'s ctor validation both use this one predicate, so the env
|
|
39
|
+
* parser and the create-option front door can never disagree. `Set.has`,
|
|
40
|
+
* never `in`/bracket reads (prototype-chain safe by construction).
|
|
41
|
+
*/
|
|
42
|
+
function isOracleSource(value) {
|
|
43
|
+
return ORACLE_SOURCE_SET.has(value);
|
|
44
|
+
}
|
|
45
|
+
function parseOracleSourceList(raw) {
|
|
46
|
+
const parts = (raw ?? "")
|
|
47
|
+
.split(",")
|
|
48
|
+
.map((part) => part.trim())
|
|
49
|
+
.filter((part) => part !== "");
|
|
50
|
+
const sources = parts.filter(isOracleSource);
|
|
51
|
+
if (parts.length === 0 || sources.length !== parts.length) {
|
|
52
|
+
const got = raw == null || raw.trim() === "" ? "unset" : `'${raw}'`;
|
|
53
|
+
throw new Error(`ORACLE_SOURCE must be a comma-separated list of ${price_update_rule_ts_1.ORACLE_SOURCES.join(" | ")} ` +
|
|
54
|
+
`(got ${got}) — there is NO default oracle source; set it in the deployment's env.`);
|
|
55
|
+
}
|
|
56
|
+
return [...new Set(sources)];
|
|
57
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* {@link PerpConfigView}. This class is just the wiring + factory between them.
|
|
12
12
|
*/
|
|
13
13
|
import { BaseLineClient } from "../base-client.ts";
|
|
14
|
-
import type
|
|
14
|
+
import { type OracleSource } from "../oracle/price-update-rule.ts";
|
|
15
15
|
import type { FetchPolicy } from "../oracle/update-fetch.ts";
|
|
16
16
|
import { type LoadConfigOptions, type PythAccessConfig, type PythFetchPolicy, type WaterxAccessConfig, type WaterXConfig, type WormholeInfraConfig } from "./config.ts";
|
|
17
17
|
import type { Network } from "./constants.ts";
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.PerpClient = void 0;
|
|
16
16
|
const base_client_ts_1 = require("../base-client.js");
|
|
17
|
+
const price_update_rule_ts_1 = require("../oracle/price-update-rule.js");
|
|
18
|
+
const source_list_ts_1 = require("../oracle/source-list.js");
|
|
17
19
|
const config_view_ts_1 = require("./config-view.js");
|
|
18
20
|
const config_ts_1 = require("./config.js");
|
|
19
21
|
class PerpClient extends base_client_ts_1.BaseLineClient {
|
|
@@ -49,16 +51,18 @@ class PerpClient extends base_client_ts_1.BaseLineClient {
|
|
|
49
51
|
...(opts.waterxEndpoint !== undefined ? { endpoint: opts.waterxEndpoint } : {}),
|
|
50
52
|
...(opts.waterxFetch !== undefined ? { fetch: opts.waterxFetch } : {}),
|
|
51
53
|
};
|
|
52
|
-
// Normalize single-or-list to a deduped, order-preserving list
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
54
|
+
// Normalize single-or-list to a deduped, order-preserving list, gated by
|
|
55
|
+
// `isOracleSource` — the SAME predicate `parseOracleSourceList` uses. An
|
|
56
|
+
// empty list, a nullish entry (an untyped caller omitting the REQUIRED
|
|
57
|
+
// option), or an unregistered value (`'core'`, `'pyth'`, …) fails
|
|
58
|
+
// construction loudly instead of booting green and surfacing as
|
|
59
|
+
// `OracleSourceNotImplemented` at the first tx-build.
|
|
57
60
|
const sources = Array.isArray(opts.oracleSource) ? opts.oracleSource : [opts.oracleSource];
|
|
58
61
|
this.oracleSources = [...new Set(sources)];
|
|
59
62
|
if (this.oracleSources.length === 0 ||
|
|
60
|
-
this.oracleSources.some((source) =>
|
|
61
|
-
throw new Error(`oracleSource is REQUIRED and must name at least one
|
|
63
|
+
this.oracleSources.some((source) => !(0, source_list_ts_1.isOracleSource)(source))) {
|
|
64
|
+
throw new Error(`oracleSource is REQUIRED and must name at least one of ${price_update_rule_ts_1.ORACLE_SOURCES.join(" | ")} ` +
|
|
65
|
+
`(got ${JSON.stringify(sources)})`);
|
|
62
66
|
}
|
|
63
67
|
this.view = new config_view_ts_1.PerpConfigView(() => this.config, () => this.wormhole);
|
|
64
68
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.PerpConfigView = void 0;
|
|
14
|
+
const record_ts_1 = require("../utils/record.js");
|
|
14
15
|
class PerpConfigView {
|
|
15
16
|
getConfig;
|
|
16
17
|
getWormhole;
|
|
@@ -29,21 +30,21 @@ class PerpConfigView {
|
|
|
29
30
|
}
|
|
30
31
|
/** `waterx_perp.markets[ticker]`, throws if unknown. */
|
|
31
32
|
getMarket(ticker) {
|
|
32
|
-
const m = this.config.packages.waterx_perp?.markets
|
|
33
|
+
const m = (0, record_ts_1.ownEntry)(this.config.packages.waterx_perp?.markets, ticker);
|
|
33
34
|
if (!m)
|
|
34
35
|
throw new Error(`Unknown market ticker: ${ticker}`);
|
|
35
36
|
return m;
|
|
36
37
|
}
|
|
37
38
|
/** `waterx_oracle.aggregators[ticker]`, throws if unknown. */
|
|
38
39
|
getAggregator(ticker) {
|
|
39
|
-
const a = this.config.packages.waterx_oracle?.aggregators
|
|
40
|
+
const a = (0, record_ts_1.ownEntry)(this.config.packages.waterx_oracle?.aggregators, ticker);
|
|
40
41
|
if (!a)
|
|
41
42
|
throw new Error(`No aggregator listed for ticker: ${ticker}`);
|
|
42
43
|
return a;
|
|
43
44
|
}
|
|
44
45
|
/** `pyth_rule.feeds[ticker]`, throws if unknown. */
|
|
45
46
|
getPythFeed(ticker) {
|
|
46
|
-
const f = this.config.packages.pyth_rule?.feeds
|
|
47
|
+
const f = (0, record_ts_1.ownEntry)(this.config.packages.pyth_rule?.feeds, ticker);
|
|
47
48
|
if (!f)
|
|
48
49
|
throw new Error(`No pyth feed listed for ticker: ${ticker}`);
|
|
49
50
|
return f;
|
|
@@ -69,7 +70,7 @@ class PerpConfigView {
|
|
|
69
70
|
const c = this.config.packages.constant_rule;
|
|
70
71
|
if (!c?.published_at || !c.config)
|
|
71
72
|
return false;
|
|
72
|
-
return c.feeds
|
|
73
|
+
return (0, record_ts_1.ownEntry)(c.feeds, ticker) !== undefined;
|
|
73
74
|
}
|
|
74
75
|
/**
|
|
75
76
|
* The `supra_rule` config when it is deployed, enabled, and fully wired
|
|
@@ -95,7 +96,7 @@ class PerpConfigView {
|
|
|
95
96
|
*/
|
|
96
97
|
getPoolTokenType(tickerOrName) {
|
|
97
98
|
const poolTokens = this.config.packages.wlp?.pool_tokens ?? {};
|
|
98
|
-
const exact = poolTokens
|
|
99
|
+
const exact = (0, record_ts_1.ownEntry)(poolTokens, tickerOrName);
|
|
99
100
|
if (exact)
|
|
100
101
|
return exact;
|
|
101
102
|
for (const t of Object.values(poolTokens)) {
|
|
@@ -118,7 +119,7 @@ class PerpConfigView {
|
|
|
118
119
|
* reward coin type, so callers don't need a separate alias-to-type lookup.
|
|
119
120
|
*/
|
|
120
121
|
getRewarders(stakeAlias) {
|
|
121
|
-
const map = this.config.packages.waterx_staking?.rewarders
|
|
122
|
+
const map = (0, record_ts_1.ownEntry)(this.config.packages.waterx_staking?.rewarders, stakeAlias);
|
|
122
123
|
if (!map)
|
|
123
124
|
return [];
|
|
124
125
|
return Object.entries(map).map(([alias, entry]) => ({ alias, ...entry }));
|
|
@@ -13,7 +13,7 @@ export type { EstLiqPriceViewOpts } from "./liq-view.ts";
|
|
|
13
13
|
export * from "./user/index.ts";
|
|
14
14
|
export * from "./tx-builders.ts";
|
|
15
15
|
export * from "./fetch.ts";
|
|
16
|
-
export { FetchPolicyError, LazerApiKeyMissingError, OracleFeeSourceUnavailableError, OracleSourceNotImplementedError, PythCache, aggregateTicker, aggregateTickerWithConstant, aggregateTickerWithPyth, buildPythPriceUpdateCalls, fetchPriceFeedsUpdateData, pythCoreHermesEndpoint, pythProHermesEndpoint, resolveHermesReadEndpoint,
|
|
16
|
+
export { FetchPolicyError, LazerApiKeyMissingError, OracleFeeSourceUnavailableError, OracleSourceNotImplementedError, PythCache, aggregateTicker, aggregateTickerWithConstant, aggregateTickerWithPyth, ORACLE_SOURCES, buildPythPriceUpdateCalls, fetchPriceFeedsUpdateData, isOracleSource, parseOracleSourceList, pythCoreHermesEndpoint, pythProHermesEndpoint, refreshOraclePrices, resolveHermesReadEndpoint, updatePythPrices, waterxEnvelopeOf, waterxQuoteCenterEndpoint, } from "../oracle/index.ts";
|
|
17
17
|
export type { FetchPolicy, OracleFeeSource, OracleSource, UpdateDataProvider, } from "../oracle/index.ts";
|
|
18
18
|
export { fetchDepositVaa, fetchVaa, listBridgeWithdrawalVaas, listVaasByEmitter, padEvmEmitter, toWormholescanEmitter, vaaBase64ToBytes, vaaBase64ToHex, vaaBytesToBase64, waitForVaa, } from "../account/funding/wormhole.ts";
|
|
19
19
|
export type { VaaListItem, VaaResponse, WormholescanOptions } from "../account/funding/wormhole.ts";
|
|
@@ -37,8 +37,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
37
37
|
})();
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.getCollateralAssets = exports.getMarketTickers = exports.TOKEN_DECIMALS = exports.COLLATERAL_DECIMALS = exports.WLP_DECIMALS = exports.SUI_DECIMALS = exports.MS_PER_YEAR = exports.MS_PER_HOUR = exports.MS_PER_MINUTE = exports.STAKING_PERM_ALL = exports.STAKING_PERM_CLAIM_REWARD = exports.STAKING_PERM_REDEEM_STAKE = exports.STAKING_PERM_DEPOSIT_STAKE = exports.PERM_WITHDRAW_COLLATERAL = exports.PERM_REDEEM_WLP = exports.PERM_PLACE_ORDER = exports.PERM_OPEN_POSITION = exports.PERM_MINT_WLP = exports.PERM_INCREASE_POSITION = exports.PERM_DEPOSIT_COLLATERAL = exports.PERM_DECREASE_POSITION = exports.PERM_CLOSE_POSITION = exports.PERM_CANCEL_ORDER = exports.PERM_ALL_TRADING = exports.PERM_ALL = exports.ORDER_TAG_WILDCARD = exports.ORDER_STOP_SELL = exports.ORDER_STOP_BUY = exports.ORDER_LIMIT_SELL = exports.ORDER_LIMIT_BUY = exports.FLOAT_SCALE = exports.DRY_RUN_SENDER = exports.DOUBLE_SCALE = exports.BPS_SCALE = exports.ACTION_WITHDRAW_COLLATERAL = exports.ACTION_UPDATE_ORDER = exports.ACTION_PLACE_ORDER = exports.ACTION_OPEN_POSITION = exports.ACTION_LIQUIDATE = exports.ACTION_INCREASE_POSITION = exports.ACTION_DEPOSIT_COLLATERAL = exports.ACTION_DECREASE_POSITION = exports.ACTION_CLOSE_POSITION = exports.ACTION_CANCEL_PRE_ORDER = exports.ACTION_CANCEL_ORDER = exports.ACTION_ADD_PRE_ORDER = exports.loadConfig = exports.clearConfigCache = exports.WORMHOLE_DEFAULTS = exports.PerpClient = void 0;
|
|
40
|
-
exports.
|
|
41
|
-
exports.withdrawalQueueCalls = exports.nativeCustodyCalls = exports.referralCalls = exports.pythSponsorRuleCalls = exports.pythRuleCalls = exports.oracleCalls = exports.stakingCalls = exports.wxaAccountCalls = exports.viewCalls = exports.lpPoolCalls = exports.tradingCalls = exports.MarketConfigBcs = exports.MarketBcs = exports.OrderBcs = exports.PositionBcs = exports.TokenPoolDataBcs = exports.RedeemRequestDataBcs = exports.PositionDataBcs = exports.PoolDataBcs = exports.OrderDataBcs = exports.MarketDataBcs = exports.GlobalConfigDataBcs = exports.AccountDataBcs = exports.waitForVaa = exports.vaaBytesToBase64 = exports.vaaBase64ToHex = exports.vaaBase64ToBytes = exports.toWormholescanEmitter = void 0;
|
|
40
|
+
exports.fetchDepositVaa = exports.waterxQuoteCenterEndpoint = exports.waterxEnvelopeOf = exports.updatePythPrices = exports.resolveHermesReadEndpoint = exports.refreshOraclePrices = exports.pythProHermesEndpoint = exports.pythCoreHermesEndpoint = exports.parseOracleSourceList = exports.isOracleSource = exports.fetchPriceFeedsUpdateData = exports.buildPythPriceUpdateCalls = exports.ORACLE_SOURCES = exports.aggregateTickerWithPyth = exports.aggregateTickerWithConstant = exports.aggregateTicker = exports.PythCache = exports.OracleSourceNotImplementedError = exports.OracleFeeSourceUnavailableError = exports.LazerApiKeyMissingError = exports.FetchPolicyError = exports.calcEstLiqPriceRawFromView = exports.formatFundingInterval = exports.rawPrice = exports.decodeFundingIndexDelta = exports.calcWlpRedeemOut = exports.calcWlpPrice = exports.calcWlpMintOut = exports.calcWlpIncentiveApy = exports.calcViewEstLiqFeesUsd = exports.calcUnrealizedPnl = exports.calcTotalTradingFeeRate = exports.calcTokenUtilizationBps = exports.calcRealLiqNetCostUsd = exports.calcPositionBorrowFee = exports.calcNotional = exports.calcMaxReducibleCollateralUsd = exports.calcLeverage = exports.calcImpactFeeRate = exports.calcFundingRate = exports.calcFundingFeeUsd = exports.calcFee = exports.calcEstLiqPriceRaw = exports.calcEstLiqPrice = exports.calcEffectiveCollateralUsd = exports.calcDynamicFeeBps = exports.calcBorrowRateAccrual = exports.calcBorrowRate = exports.annualizeFundingRate = exports.annualizedApyFromRatio = void 0;
|
|
41
|
+
exports.withdrawalQueueCalls = exports.nativeCustodyCalls = exports.referralCalls = exports.pythSponsorRuleCalls = exports.pythRuleCalls = exports.oracleCalls = exports.stakingCalls = exports.wxaAccountCalls = exports.viewCalls = exports.lpPoolCalls = exports.tradingCalls = exports.MarketConfigBcs = exports.MarketBcs = exports.OrderBcs = exports.PositionBcs = exports.TokenPoolDataBcs = exports.RedeemRequestDataBcs = exports.PositionDataBcs = exports.PoolDataBcs = exports.OrderDataBcs = exports.MarketDataBcs = exports.GlobalConfigDataBcs = exports.AccountDataBcs = exports.waitForVaa = exports.vaaBytesToBase64 = exports.vaaBase64ToHex = exports.vaaBase64ToBytes = exports.toWormholescanEmitter = exports.padEvmEmitter = exports.listVaasByEmitter = exports.listBridgeWithdrawalVaas = exports.fetchVaa = void 0;
|
|
42
42
|
// ======== Core ========
|
|
43
43
|
var client_ts_1 = require("./client.js");
|
|
44
44
|
Object.defineProperty(exports, "PerpClient", { enumerable: true, get: function () { return client_ts_1.PerpClient; } });
|
|
@@ -144,14 +144,18 @@ Object.defineProperty(exports, "PythCache", { enumerable: true, get: function ()
|
|
|
144
144
|
Object.defineProperty(exports, "aggregateTicker", { enumerable: true, get: function () { return index_ts_1.aggregateTicker; } });
|
|
145
145
|
Object.defineProperty(exports, "aggregateTickerWithConstant", { enumerable: true, get: function () { return index_ts_1.aggregateTickerWithConstant; } });
|
|
146
146
|
Object.defineProperty(exports, "aggregateTickerWithPyth", { enumerable: true, get: function () { return index_ts_1.aggregateTickerWithPyth; } });
|
|
147
|
+
Object.defineProperty(exports, "ORACLE_SOURCES", { enumerable: true, get: function () { return index_ts_1.ORACLE_SOURCES; } });
|
|
147
148
|
Object.defineProperty(exports, "buildPythPriceUpdateCalls", { enumerable: true, get: function () { return index_ts_1.buildPythPriceUpdateCalls; } });
|
|
148
149
|
Object.defineProperty(exports, "fetchPriceFeedsUpdateData", { enumerable: true, get: function () { return index_ts_1.fetchPriceFeedsUpdateData; } });
|
|
150
|
+
Object.defineProperty(exports, "isOracleSource", { enumerable: true, get: function () { return index_ts_1.isOracleSource; } });
|
|
151
|
+
Object.defineProperty(exports, "parseOracleSourceList", { enumerable: true, get: function () { return index_ts_1.parseOracleSourceList; } });
|
|
149
152
|
Object.defineProperty(exports, "pythCoreHermesEndpoint", { enumerable: true, get: function () { return index_ts_1.pythCoreHermesEndpoint; } });
|
|
150
153
|
Object.defineProperty(exports, "pythProHermesEndpoint", { enumerable: true, get: function () { return index_ts_1.pythProHermesEndpoint; } });
|
|
151
|
-
Object.defineProperty(exports, "resolveHermesReadEndpoint", { enumerable: true, get: function () { return index_ts_1.resolveHermesReadEndpoint; } });
|
|
152
|
-
Object.defineProperty(exports, "waterxQuoteCenterEndpoint", { enumerable: true, get: function () { return index_ts_1.waterxQuoteCenterEndpoint; } });
|
|
153
154
|
Object.defineProperty(exports, "refreshOraclePrices", { enumerable: true, get: function () { return index_ts_1.refreshOraclePrices; } });
|
|
155
|
+
Object.defineProperty(exports, "resolveHermesReadEndpoint", { enumerable: true, get: function () { return index_ts_1.resolveHermesReadEndpoint; } });
|
|
154
156
|
Object.defineProperty(exports, "updatePythPrices", { enumerable: true, get: function () { return index_ts_1.updatePythPrices; } });
|
|
157
|
+
Object.defineProperty(exports, "waterxEnvelopeOf", { enumerable: true, get: function () { return index_ts_1.waterxEnvelopeOf; } });
|
|
158
|
+
Object.defineProperty(exports, "waterxQuoteCenterEndpoint", { enumerable: true, get: function () { return index_ts_1.waterxQuoteCenterEndpoint; } });
|
|
155
159
|
// ======== Wormhole / Wormholescan utilities (credit bridge) ========
|
|
156
160
|
var wormhole_ts_1 = require("../account/funding/wormhole.js");
|
|
157
161
|
Object.defineProperty(exports, "fetchDepositVaa", { enumerable: true, get: function () { return wormhole_ts_1.fetchDepositVaa; } });
|
|
@@ -55,9 +55,10 @@ exports.unstake = unstake;
|
|
|
55
55
|
exports.claimReward = claimReward;
|
|
56
56
|
const account_request_ts_1 = require("../../account/account-request.js");
|
|
57
57
|
const staking = __importStar(require("../../generated/waterx_staking/waterx_staking.js"));
|
|
58
|
+
const record_ts_1 = require("../../utils/record.js");
|
|
58
59
|
const validate_ts_1 = require("../../utils/validate.js");
|
|
59
60
|
function pool(client, stakeAlias) {
|
|
60
|
-
const id = client.config.packages.waterx_staking?.pools
|
|
61
|
+
const id = (0, record_ts_1.ownEntry)(client.config.packages.waterx_staking?.pools, stakeAlias);
|
|
61
62
|
if (!id) {
|
|
62
63
|
throw new Error(`config.packages.waterx_staking.pools[${stakeAlias}] is not set — staking is not deployed for this stake type`);
|
|
63
64
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getMarketTickers = getMarketTickers;
|
|
4
4
|
exports.getCollateralAssets = getCollateralAssets;
|
|
5
|
+
const record_ts_1 = require("./record.js");
|
|
5
6
|
/** Returns all registered market tickers (e.g. "BTCUSD") from waterx-config. */
|
|
6
7
|
function getMarketTickers(config) {
|
|
7
8
|
return Object.keys(config.packages.waterx_perp.markets);
|
|
@@ -15,5 +16,5 @@ function getMarketTickers(config) {
|
|
|
15
16
|
*/
|
|
16
17
|
function getCollateralAssets(config) {
|
|
17
18
|
const feeds = config.packages.pyth_rule?.feeds ?? {};
|
|
18
|
-
return Object.keys(config.packages.wlp.pool_tokens).filter((t) => feeds
|
|
19
|
+
return Object.keys(config.packages.wlp.pool_tokens).filter((t) => (0, record_ts_1.ownEntry)(feeds, t) !== undefined);
|
|
19
20
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Own-key record lookup. Ticker-keyed config records (`feeds`, `markets`,
|
|
3
|
+
* `aggregators`) are indexed with caller-supplied strings; a bare bracket
|
|
4
|
+
* read — like the `in` operator — walks the prototype chain, so a ticker
|
|
5
|
+
* named like an `Object.prototype` key ("toString", "constructor", …) reads
|
|
6
|
+
* as an inherited Function instead of "absent" and leaks into batches sent
|
|
7
|
+
* to the network. Every such lookup funnels through here so the answer is
|
|
8
|
+
* own-keys-only, everywhere, instead of per-site `Object.hasOwn` guards
|
|
9
|
+
* that drift.
|
|
10
|
+
*/
|
|
11
|
+
/** `record[key]` iff `key` is an OWN key — `undefined` for an absent record, an absent key, or a prototype-chain hit. */
|
|
12
|
+
export declare function ownEntry<T extends Record<string, unknown>>(record: T | undefined, key: string): T[string] | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Own-key record lookup. Ticker-keyed config records (`feeds`, `markets`,
|
|
4
|
+
* `aggregators`) are indexed with caller-supplied strings; a bare bracket
|
|
5
|
+
* read — like the `in` operator — walks the prototype chain, so a ticker
|
|
6
|
+
* named like an `Object.prototype` key ("toString", "constructor", …) reads
|
|
7
|
+
* as an inherited Function instead of "absent" and leaks into batches sent
|
|
8
|
+
* to the network. Every such lookup funnels through here so the answer is
|
|
9
|
+
* own-keys-only, everywhere, instead of per-site `Object.hasOwn` guards
|
|
10
|
+
* that drift.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ownEntry = ownEntry;
|
|
14
|
+
/** `record[key]` iff `key` is an OWN key — `undefined` for an absent record, an absent key, or a prototype-chain hit. */
|
|
15
|
+
function ownEntry(record, key) {
|
|
16
|
+
// The `T extends Record<...>` form (rather than `Record<string, V>`) keeps
|
|
17
|
+
// union-typed records inferable; TS then resolves `record[key]` only to the
|
|
18
|
+
// constraint's `unknown`, so restate the definitionally-true index type.
|
|
19
|
+
return record !== undefined && Object.hasOwn(record, key)
|
|
20
|
+
? record[key]
|
|
21
|
+
: undefined;
|
|
22
|
+
}
|
|
@@ -38,9 +38,10 @@ import { type WaterxSignedEnvelope } from "./rules/waterx-rule.ts";
|
|
|
38
38
|
* if it is stale — it never aborts — so the call stays mandatory while
|
|
39
39
|
* `pyth_rule` remains in the ticker's on-chain weighted set
|
|
40
40
|
* (`EMissingPriceSource` requires every weighted rule to appear).
|
|
41
|
-
* - **Lazer** — fed when `lazerUpdate` is supplied: the verified
|
|
42
|
-
*
|
|
43
|
-
* (
|
|
41
|
+
* - **Lazer** — fed when `lazerUpdate` is supplied: the verified update this
|
|
42
|
+
* PTB's lazer update leg produced with the network's verify entry
|
|
43
|
+
* (`update_v2::Update` on mainnet, `update::Update` on testnet — see
|
|
44
|
+
* `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
|
|
44
45
|
* not (yet) weight `PythLazerRule`, the contribution is silently dropped
|
|
45
46
|
* on-chain — feeding ahead of the weight migration is harmless.
|
|
46
47
|
* - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* `rule-registry.ts`.
|
|
24
24
|
*/
|
|
25
25
|
import { aggregate as aggregateCall, newCollector } from "../generated/waterx_oracle/oracle.js";
|
|
26
|
+
import { ownEntry } from "../utils/record.js";
|
|
26
27
|
import { OracleFeeSourceUnavailableError } from "./pyth.js";
|
|
27
28
|
import { resolveOracleRule } from "./rule-registry.js";
|
|
28
29
|
import { feedConstantRule } from "./rules/constant-rule.js";
|
|
@@ -83,9 +84,10 @@ async function resolveGroupUpdateData(host, group, provider) {
|
|
|
83
84
|
* if it is stale — it never aborts — so the call stays mandatory while
|
|
84
85
|
* `pyth_rule` remains in the ticker's on-chain weighted set
|
|
85
86
|
* (`EMissingPriceSource` requires every weighted rule to appear).
|
|
86
|
-
* - **Lazer** — fed when `lazerUpdate` is supplied: the verified
|
|
87
|
-
*
|
|
88
|
-
* (
|
|
87
|
+
* - **Lazer** — fed when `lazerUpdate` is supplied: the verified update this
|
|
88
|
+
* PTB's lazer update leg produced with the network's verify entry
|
|
89
|
+
* (`update_v2::Update` on mainnet, `update::Update` on testnet — see
|
|
90
|
+
* `PythLazerRule.buildUpdateCalls`). If the ticker's aggregator does
|
|
89
91
|
* not (yet) weight `PythLazerRule`, the contribution is silently dropped
|
|
90
92
|
* on-chain — feeding ahead of the weight migration is harmless.
|
|
91
93
|
* - **Supra** — fed alongside Pyth/Lazer when supra is enabled + wired
|
|
@@ -163,7 +165,7 @@ export function aggregateTickerWithPyth(tx, host, args) {
|
|
|
163
165
|
* {@link refreshOraclePrices}), which feeds both.
|
|
164
166
|
*/
|
|
165
167
|
export function aggregateTickerWithConstant(tx, host, args) {
|
|
166
|
-
if (host.config.packages.pyth_rule?.feeds
|
|
168
|
+
if (ownEntry(host.config.packages.pyth_rule?.feeds, args.ticker) !== undefined) {
|
|
167
169
|
throw new Error(`'${args.ticker}' is in pyth_rule.feeds (dual-feed) — feed both via aggregateTicker({ priceInfoObjectId }) / refreshOraclePrices, not aggregateTickerWithConstant`);
|
|
168
170
|
}
|
|
169
171
|
aggregateTicker(tx, host, { ticker: args.ticker });
|
|
@@ -224,7 +226,7 @@ export async function refreshOraclePrices(tx, host, tickers, opts = {}) {
|
|
|
224
226
|
// price_info_object lookup for every ticker with a pyth_rule.feeds entry —
|
|
225
227
|
// needed by aggregateTicker's (unchanged) Pyth feed step below regardless of
|
|
226
228
|
// which rule performed the on-chain update for that ticker.
|
|
227
|
-
const pythTickers = tickers.filter((t) => host.config.packages.pyth_rule?.feeds
|
|
229
|
+
const pythTickers = tickers.filter((t) => ownEntry(host.config.packages.pyth_rule?.feeds, t) !== undefined);
|
|
228
230
|
const priceInfoByTicker = new Map();
|
|
229
231
|
pythTickers.forEach((t) => priceInfoByTicker.set(t, host.getPythFeed(t).price_info_object));
|
|
230
232
|
// The fed set is a LIST (`host.oracleSources`, normalized + deduped at
|
|
@@ -21,13 +21,15 @@ export type { FetchPolicy } from "./update-fetch.ts";
|
|
|
21
21
|
export { PythCache, fetchPriceFeedsUpdateData, endpointSupportedFeedIds, probeMissingFeeds, buildPythPriceUpdateCalls, pythCoreHermesEndpoint, pythProHermesEndpoint, PYTH_PRO_HERMES_ENDPOINT, updatePythPrices, HermesEndpointRejectedAllFeedsError, MISSING_FEED_MEMO_TTL_MS, OracleFeeSourceUnavailableError, } from "./pyth.ts";
|
|
22
22
|
export type { OracleFeeSource } from "./pyth.ts";
|
|
23
23
|
export type { PriceUpdateRule, PriceUpdateRuleKind, RuleUpdateData, RuleUpdateHandle, BuildUpdateOpts, OracleSource, UpdateDataProvider, } from "./price-update-rule.ts";
|
|
24
|
+
export { ORACLE_SOURCES } from "./price-update-rule.ts";
|
|
25
|
+
export { isOracleSource, parseOracleSourceList } from "./source-list.ts";
|
|
24
26
|
export { resolveOracleReadPlan, resolveHermesReadEndpoint } from "./read-plane.ts";
|
|
25
27
|
export type { OracleReadPlan } from "./read-plane.ts";
|
|
26
28
|
export { PythCoreRule } from "./rules/pyth-core-rule.ts";
|
|
27
29
|
export type { PythCoreUpdatePayload } from "./rules/pyth-core-rule.ts";
|
|
28
30
|
export { PythLazerRule, LazerApiKeyMissingError } from "./rules/pyth-lazer-rule.ts";
|
|
29
31
|
export type { PythLazerUpdatePayload } from "./rules/pyth-lazer-rule.ts";
|
|
30
|
-
export { WaterxRule, parseSignedEnvelope, BATCH_PRICE_INTENT, WATERX_INFRA, waterxQuoteCenterEndpoint, } from "./rules/waterx-rule.ts";
|
|
32
|
+
export { WaterxRule, parseSignedEnvelope, BATCH_PRICE_INTENT, WATERX_INFRA, waterxQuoteCenterEndpoint, waterxEnvelopeOf, } from "./rules/waterx-rule.ts";
|
|
31
33
|
export type { WaterxUpdatePayload, WaterxSignedEnvelope, WaterxBatchItem, } from "./rules/waterx-rule.ts";
|
|
32
34
|
export { OracleSourceNotImplementedError, resolveOracleRule } from "./rule-registry.ts";
|
|
33
35
|
export { aggregateTicker, aggregateTickerWithPyth, aggregateTickerWithConstant, refreshOraclePrices, } from "./aggregate.ts";
|
package/dist/src/oracle/index.js
CHANGED
|
@@ -40,6 +40,10 @@ export { PythCache, fetchPriceFeedsUpdateData, endpointSupportedFeedIds, probeMi
|
|
|
40
40
|
// `resolveHermesReadEndpoint` (pyth_rule listed → Core, else override ??
|
|
41
41
|
// Pro) — never a hand-rolled branch, never a cross-source fallback.
|
|
42
42
|
pythCoreHermesEndpoint, pythProHermesEndpoint, PYTH_PRO_HERMES_ENDPOINT, updatePythPrices, HermesEndpointRejectedAllFeedsError, MISSING_FEED_MEMO_TTL_MS, OracleFeeSourceUnavailableError, } from "./pyth.js";
|
|
43
|
+
// Canonical OracleSource value list + THE env-string parser consumers fold
|
|
44
|
+
// onto — semantics and rationale in `source-list.ts`'s header.
|
|
45
|
+
export { ORACLE_SOURCES } from "./price-update-rule.js";
|
|
46
|
+
export { isOracleSource, parseOracleSourceList } from "./source-list.js";
|
|
43
47
|
// Per-source READ-plane resolution — which tickers a source can price
|
|
44
48
|
// off-chain and with which ids (`resolveOracleReadPlan`), and which
|
|
45
49
|
// Hermes-compatible base the hermes plans execute against
|
|
@@ -58,7 +62,10 @@ export { PythLazerRule, LazerApiKeyMissingError } from "./rules/pyth-lazer-rule.
|
|
|
58
62
|
// read-plane accessor (mirrors `pythCoreHermesEndpoint`).
|
|
59
63
|
// WaterX quote-center rule (first-party ed25519 signed batches; `feedWaterxRule`
|
|
60
64
|
// stays internal to `aggregate.ts`).
|
|
61
|
-
export { WaterxRule, parseSignedEnvelope, BATCH_PRICE_INTENT, WATERX_INFRA, waterxQuoteCenterEndpoint,
|
|
65
|
+
export { WaterxRule, parseSignedEnvelope, BATCH_PRICE_INTENT, WATERX_INFRA, waterxQuoteCenterEndpoint,
|
|
66
|
+
// Rule-owned payload accessor (kind-check + unwrap in one place) — never
|
|
67
|
+
// hand-cast the payload shape.
|
|
68
|
+
waterxEnvelopeOf, } from "./rules/waterx-rule.js";
|
|
62
69
|
// `resolveOracleRule` is the ONE source→rule registry — exported so external
|
|
63
70
|
// consumers (e.g. a BE prefetch cache that keys per source and needs each
|
|
64
71
|
// source's `supportedTickers`/`fetchUpdateData`) resolve through it instead of
|
|
@@ -20,14 +20,28 @@ import type { OracleHost } from "./host.ts";
|
|
|
20
20
|
import type { OracleFeeSource, PythCache } from "./pyth.ts";
|
|
21
21
|
export type PriceUpdateRuleKind = "pyth_rule" | "pyth_lazer_rule" | "supra_rule" | "constant_rule" | "waterx_rule";
|
|
22
22
|
/**
|
|
23
|
-
* The
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
23
|
+
* The canonical list of selectable oracle sources — the SINGLE authority the
|
|
24
|
+
* {@link OracleSource} union derives from (the value-list→union derive
|
|
25
|
+
* idiom of `unified-client.ts`'s `NON_CLIENT_FIRST`, plus `Object.freeze`
|
|
26
|
+
* so the immutability is RUNTIME truth: `as const` alone would let a JS
|
|
27
|
+
* consumer push into the array and desync the membership Set built from it
|
|
28
|
+
* in `source-list.ts`). Runtime membership checks and the `ORACLE_SOURCE`
|
|
29
|
+
* env parser live there, on `isOracleSource` / `parseOracleSourceList`.
|
|
30
|
+
* Only sources belong here: `supra_rule` and `constant_rule` are auxiliary
|
|
31
|
+
* rules fed alongside whichever sources are selected (see
|
|
32
|
+
* `aggregateTicker`), not sources themselves — the `satisfies` keeps
|
|
33
|
+
* entries inside `PriceUpdateRuleKind` but adding an auxiliary rule to this
|
|
34
|
+
* list is an (incorrect) editorial decision this comment exists to prevent.
|
|
29
35
|
*/
|
|
30
|
-
export
|
|
36
|
+
export declare const ORACLE_SOURCES: readonly ["pyth_rule", "pyth_lazer_rule", "waterx_rule"];
|
|
37
|
+
/**
|
|
38
|
+
* The kinds listable in a client's `oracleSource` create option (see
|
|
39
|
+
* `OracleHost.oracleSources`) — i.e. rules that can serve as the on-chain
|
|
40
|
+
* price *update* leg `refreshOraclePrices` runs before aggregating. Derived
|
|
41
|
+
* from {@link ORACLE_SOURCES}. The SDK never reads `process.env` — consumers
|
|
42
|
+
* resolve their own env var to this type.
|
|
43
|
+
*/
|
|
44
|
+
export type OracleSource = (typeof ORACLE_SOURCES)[number];
|
|
31
45
|
/**
|
|
32
46
|
* Off-chain payload fetched by a rule, tagged by `kind` so a caller holding
|
|
33
47
|
* several rules' results can tell them apart. `payload` is `unknown` here —
|
|
@@ -69,14 +83,22 @@ export declare function assertRuleUpdateData<T>(data: RuleUpdateData, kind: Pric
|
|
|
69
83
|
* return when its collector-feed leg needs a value produced by the update leg
|
|
70
84
|
* *within the same PTB*. Pyth Core needs none (its feed leg reads the shared
|
|
71
85
|
* `PriceInfoObject` the update leg refreshed), so it returns `void`. The Lazer
|
|
72
|
-
* rule returns the verified
|
|
86
|
+
* rule returns the verified-update result of its network's verify entry — one
|
|
73
87
|
* signature verification covers every feed in the payload, and
|
|
74
88
|
* `pyth_lazer_rule::feed` takes it by reference per ticker (see
|
|
75
89
|
* `aggregateTicker`'s `lazerUpdate` arg).
|
|
76
90
|
*/
|
|
77
91
|
export type RuleUpdateHandle = {
|
|
78
92
|
readonly kind: "pyth_lazer_rule";
|
|
79
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Opaque result of this network's `LAZER_INFRA.verify_entry` in this PTB,
|
|
95
|
+
* passed straight to `pyth_lazer_rule::feed`. The Move type is
|
|
96
|
+
* network-dependent and never named here: mainnet's
|
|
97
|
+
* `pyth_lazer::parse_and_verify_le_ecdsa_update_v2` yields
|
|
98
|
+
* `pyth_lazer::update_v2::Update`, testnet's v1
|
|
99
|
+
* `…_le_ecdsa_update` yields `pyth_lazer::update::Update`, and each
|
|
100
|
+
* network's `pyth_lazer_rule` is published bound to the matching one.
|
|
101
|
+
*/
|
|
80
102
|
readonly update: TransactionArgument;
|
|
81
103
|
};
|
|
82
104
|
/**
|
|
@@ -15,6 +15,25 @@
|
|
|
15
15
|
* entry via `rule-registry.ts`, then drives fetch + `buildUpdateCalls`
|
|
16
16
|
* through this port; `aggregate.ts` stays the sole orchestrator.
|
|
17
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* The canonical list of selectable oracle sources — the SINGLE authority the
|
|
20
|
+
* {@link OracleSource} union derives from (the value-list→union derive
|
|
21
|
+
* idiom of `unified-client.ts`'s `NON_CLIENT_FIRST`, plus `Object.freeze`
|
|
22
|
+
* so the immutability is RUNTIME truth: `as const` alone would let a JS
|
|
23
|
+
* consumer push into the array and desync the membership Set built from it
|
|
24
|
+
* in `source-list.ts`). Runtime membership checks and the `ORACLE_SOURCE`
|
|
25
|
+
* env parser live there, on `isOracleSource` / `parseOracleSourceList`.
|
|
26
|
+
* Only sources belong here: `supra_rule` and `constant_rule` are auxiliary
|
|
27
|
+
* rules fed alongside whichever sources are selected (see
|
|
28
|
+
* `aggregateTicker`), not sources themselves — the `satisfies` keeps
|
|
29
|
+
* entries inside `PriceUpdateRuleKind` but adding an auxiliary rule to this
|
|
30
|
+
* list is an (incorrect) editorial decision this comment exists to prevent.
|
|
31
|
+
*/
|
|
32
|
+
export const ORACLE_SOURCES = Object.freeze([
|
|
33
|
+
"pyth_rule",
|
|
34
|
+
"pyth_lazer_rule",
|
|
35
|
+
"waterx_rule",
|
|
36
|
+
]);
|
|
18
37
|
/**
|
|
19
38
|
* Shared null → kind → shape guard ladder for a `PriceUpdateRule.buildUpdateCalls`
|
|
20
39
|
* payload — every rule's `buildUpdateCalls` needs the exact same three checks,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* (FE/BE price facades) resolve through this instead of hardcoding which
|
|
11
11
|
* sources share which feeds namespace.
|
|
12
12
|
*/
|
|
13
|
+
import { ownEntry } from "../utils/record.js";
|
|
13
14
|
import { pythCoreHermesEndpoint, pythProHermesEndpoint } from "./pyth.js";
|
|
14
15
|
/**
|
|
15
16
|
* Resolve `source`'s read plan for `tickers`. Pure config lookup — no
|
|
@@ -43,26 +44,32 @@ export function resolveOracleReadPlan(host, source, tickers) {
|
|
|
43
44
|
switch (source) {
|
|
44
45
|
case "pyth_rule":
|
|
45
46
|
case "pyth_lazer_rule": {
|
|
47
|
+
// All ticker lookups go through `ownEntry` (own-keys-only): a ticker
|
|
48
|
+
// named like an Object.prototype key ("toString", "constructor", …)
|
|
49
|
+
// must read as not-listed, not as an inherited Function.
|
|
46
50
|
const hexFeeds = host.config.packages.pyth_rule?.feeds;
|
|
47
51
|
const feedIdByTicker = new Map();
|
|
48
52
|
for (const ticker of tickers) {
|
|
49
|
-
const feedId = hexFeeds
|
|
53
|
+
const feedId = ownEntry(hexFeeds, ticker)?.feed_id;
|
|
50
54
|
if (feedId !== undefined)
|
|
51
55
|
feedIdByTicker.set(ticker, feedId);
|
|
52
56
|
}
|
|
53
57
|
// For pyth_rule the write and read namespaces coincide, so `unreadable`
|
|
54
58
|
// is always empty; for lazer it is exactly the hex-entry gap.
|
|
55
59
|
const writeFeeds = source === "pyth_lazer_rule" ? host.config.packages.pyth_lazer_rule?.feeds : hexFeeds;
|
|
56
|
-
const unreadable = tickers.filter((ticker) => writeFeeds
|
|
60
|
+
const unreadable = tickers.filter((ticker) => ownEntry(writeFeeds, ticker) !== undefined && !feedIdByTicker.has(ticker));
|
|
57
61
|
return { plane: "hermes", feedIdByTicker, unreadable };
|
|
58
62
|
}
|
|
59
63
|
case "waterx_rule": {
|
|
60
64
|
// Absent feeds block ⇒ serves nothing (see the OracleReadPlan doc) —
|
|
61
|
-
// never claim tickers the config doesn't name.
|
|
65
|
+
// never claim tickers the config doesn't name. `ownEntry` (own-keys-
|
|
66
|
+
// only, never the `in` operator or a bare bracket read) so a
|
|
67
|
+
// prototype-key ticker can't count as feeds-listed and poison the
|
|
68
|
+
// quote-center batch (which 404s whole batches on unknown symbols).
|
|
62
69
|
const feeds = host.config.packages.waterx_rule?.feeds;
|
|
63
70
|
return {
|
|
64
71
|
plane: "quote_center",
|
|
65
|
-
tickers:
|
|
72
|
+
tickers: tickers.filter((ticker) => ownEntry(feeds, ticker) !== undefined),
|
|
66
73
|
unreadable: [],
|
|
67
74
|
};
|
|
68
75
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* across rules by `kind`. Mechanical wrap only — no on-chain/off-chain logic
|
|
7
7
|
* changes vs `../pyth.ts` / `./pyth-rule.ts`.
|
|
8
8
|
*/
|
|
9
|
+
import { ownEntry } from "../../utils/record.js";
|
|
9
10
|
import { assertRuleUpdateData, } from "../price-update-rule.js";
|
|
10
11
|
import { buildPythPriceUpdateCalls, endpointSupportedFeedIds, fetchPriceFeedsUpdateData, pythCoreHermesEndpoint, } from "../pyth.js";
|
|
11
12
|
/**
|
|
@@ -76,7 +77,7 @@ export const PythCoreRule = {
|
|
|
76
77
|
for (const ticker of tickers) {
|
|
77
78
|
// Same lookup as `host.getPythFeed(ticker)` minus its throw — an
|
|
78
79
|
// unlisted ticker is a miss here, not an error.
|
|
79
|
-
const feedId = host.config.packages.pyth_rule?.feeds
|
|
80
|
+
const feedId = ownEntry(host.config.packages.pyth_rule?.feeds, ticker)?.feed_id;
|
|
80
81
|
if (feedId === undefined || !packedFeedIds.has(feedId))
|
|
81
82
|
return null;
|
|
82
83
|
feedIds.push(feedId);
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* appends per lazer-routed ticker. Fetches one `leEcdsa` payload for all
|
|
5
5
|
* requested integer feed ids from the Lazer HTTP API (Bearer-authenticated
|
|
6
6
|
* via the `pythApiKey` create option), verifies it ONCE on-chain via
|
|
7
|
-
* `pyth_lazer
|
|
8
|
-
* `Update` PTB value back through a `RuleUpdateHandle` for the feed
|
|
7
|
+
* `pyth_lazer`'s verify entry for that network (see `LAZER_INFRA`), and hands the
|
|
8
|
+
* resulting `Update` PTB value back through a `RuleUpdateHandle` for the feed
|
|
9
|
+
* calls.
|
|
9
10
|
*/
|
|
10
11
|
import type { Transaction, TransactionArgument } from "@mysten/sui/transactions";
|
|
11
12
|
import type { Network } from "../../constants.ts";
|
|
@@ -21,16 +22,33 @@ import { type PriceUpdateRule } from "../price-update-rule.ts";
|
|
|
21
22
|
* `POST /v1/latest_price` (Bearer-authenticated). The service is
|
|
22
23
|
* network-agnostic (one signed payload verifies on any chain that trusts the
|
|
23
24
|
* Lazer signers), so both networks share the production host.
|
|
24
|
-
* - `verifier_package` — the Sui package carrying
|
|
25
|
-
* `
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* - `verifier_package` / `verify_entry` — the Sui package carrying the verify
|
|
26
|
+
* call, and which entry to call. These track what `pyth_lazer_rule` binds on
|
|
27
|
+
* that network, so they move together:
|
|
28
|
+
* - **mainnet** — the v2 package `0xefbfd064…` and the **v2** entry. The rule
|
|
29
|
+
* was republished v2-bound after a 2026-08-05 mainnet probe: the ORIGINAL
|
|
30
|
+
* package `0x7b502c…` now aborts `EDifferentVersion` (`state::current_cap`)
|
|
31
|
+
* for any payload — the shared `State` has been migrated past that code —
|
|
32
|
+
* and its v1 entry aborts `EInvalidChannel` on `fixed_rate@1000ms`, the only
|
|
33
|
+
* channel WaterX's Pyth Pro grant permits.
|
|
34
|
+
* - **testnet** — still the original v1 publish, which has no `update_v2`
|
|
35
|
+
* module at all, so the v1 entry is the only one that exists there.
|
|
36
|
+
* Both entries take `(state, clock, bytes)` and accept the same `leEcdsa`
|
|
37
|
+
* payload. Values mirror the contract repo's `pyth_lazer_rule/Move.toml`
|
|
38
|
+
* published-at pins.
|
|
29
39
|
*/
|
|
30
40
|
export declare const LAZER_INFRA: Record<Network, {
|
|
31
41
|
endpoint: string;
|
|
32
42
|
verifier_package: string;
|
|
43
|
+
verify_entry: LazerVerifyEntry;
|
|
44
|
+
channel: string;
|
|
33
45
|
}>;
|
|
46
|
+
/**
|
|
47
|
+
* The `pyth_lazer` verify entry a network's deployed rule consumes. `_v2`
|
|
48
|
+
* returns `update_v2::Update`; the v1 entry returns `update::Update`, and the
|
|
49
|
+
* two are NOT interchangeable — the rule's `feed` takes one concrete type.
|
|
50
|
+
*/
|
|
51
|
+
export type LazerVerifyEntry = "parse_and_verify_le_ecdsa_update" | "parse_and_verify_le_ecdsa_update_v2";
|
|
34
52
|
/** `pyth_lazer_rule`'s narrowed `RuleUpdateData.payload` shape. */
|
|
35
53
|
export interface PythLazerUpdatePayload {
|
|
36
54
|
/** One signed `leEcdsa` message carrying every requested feed. */
|