@waterx/sdk 2.1.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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/src/client.d.ts +109 -0
- package/dist/src/client.js +197 -0
- package/dist/src/config.d.ts +229 -0
- package/dist/src/config.js +107 -0
- package/dist/src/constants.d.ts +64 -0
- package/dist/src/constants.js +71 -0
- package/dist/src/fetch.d.ts +144 -0
- package/dist/src/fetch.js +419 -0
- package/dist/src/generated/bucket_v2_framework/account.d.ts +107 -0
- package/dist/src/generated/bucket_v2_framework/account.js +129 -0
- package/dist/src/generated/bucket_v2_framework/deps/std/type_name.d.ts +17 -0
- package/dist/src/generated/bucket_v2_framework/deps/std/type_name.js +19 -0
- package/dist/src/generated/bucket_v2_framework/deps/sui/balance.d.ts +12 -0
- package/dist/src/generated/bucket_v2_framework/deps/sui/balance.js +14 -0
- package/dist/src/generated/bucket_v2_framework/deps/sui/vec_map.d.ts +36 -0
- package/dist/src/generated/bucket_v2_framework/deps/sui/vec_map.js +27 -0
- package/dist/src/generated/bucket_v2_framework/deps/sui/vec_set.d.ts +16 -0
- package/dist/src/generated/bucket_v2_framework/deps/sui/vec_set.js +19 -0
- package/dist/src/generated/bucket_v2_framework/double.d.ts +384 -0
- package/dist/src/generated/bucket_v2_framework/double.js +466 -0
- package/dist/src/generated/bucket_v2_framework/float.d.ts +364 -0
- package/dist/src/generated/bucket_v2_framework/float.js +440 -0
- package/dist/src/generated/bucket_v2_framework/liability.d.ts +197 -0
- package/dist/src/generated/bucket_v2_framework/liability.js +205 -0
- package/dist/src/generated/bucket_v2_framework/linked_table.d.ts +384 -0
- package/dist/src/generated/bucket_v2_framework/linked_table.js +382 -0
- package/dist/src/generated/bucket_v2_framework/sheet.d.ts +424 -0
- package/dist/src/generated/bucket_v2_framework/sheet.js +344 -0
- package/dist/src/generated/native_custody/custody_vault.d.ts +572 -0
- package/dist/src/generated/native_custody/custody_vault.js +483 -0
- package/dist/src/generated/native_custody/deps/bucket_v2_framework/float.d.ts +8 -0
- package/dist/src/generated/native_custody/deps/bucket_v2_framework/float.js +10 -0
- package/dist/src/generated/native_custody/deps/bucket_v2_framework/liability.d.ts +11 -0
- package/dist/src/generated/native_custody/deps/bucket_v2_framework/liability.js +13 -0
- package/dist/src/generated/native_custody/deps/bucket_v2_framework/sheet.d.ts +49 -0
- package/dist/src/generated/native_custody/deps/bucket_v2_framework/sheet.js +18 -0
- package/dist/src/generated/native_custody/deps/std/type_name.d.ts +17 -0
- package/dist/src/generated/native_custody/deps/std/type_name.js +19 -0
- package/dist/src/generated/native_custody/deps/sui/balance.d.ts +12 -0
- package/dist/src/generated/native_custody/deps/sui/balance.js +14 -0
- package/dist/src/generated/native_custody/deps/sui/vec_map.d.ts +36 -0
- package/dist/src/generated/native_custody/deps/sui/vec_map.js +27 -0
- package/dist/src/generated/native_custody/deps/sui/vec_set.d.ts +16 -0
- package/dist/src/generated/native_custody/deps/sui/vec_set.js +19 -0
- package/dist/src/generated/native_custody/events.d.ts +36 -0
- package/dist/src/generated/native_custody/events.js +36 -0
- package/dist/src/generated/pyth_sponsor_rule/deps/sui/balance.d.ts +12 -0
- package/dist/src/generated/pyth_sponsor_rule/deps/sui/balance.js +14 -0
- package/dist/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.d.ts +68 -0
- package/dist/src/generated/pyth_sponsor_rule/pyth_sponsor_rule.js +74 -0
- package/dist/src/generated/utils/index.d.ts +30 -0
- package/dist/src/generated/utils/index.js +157 -0
- package/dist/src/generated/waterx_account/account.d.ts +1818 -0
- package/dist/src/generated/waterx_account/account.js +1723 -0
- package/dist/src/generated/waterx_account/deps/bucket_v2_framework/liability.d.ts +11 -0
- package/dist/src/generated/waterx_account/deps/bucket_v2_framework/liability.js +13 -0
- package/dist/src/generated/waterx_account/deps/bucket_v2_framework/sheet.d.ts +49 -0
- package/dist/src/generated/waterx_account/deps/bucket_v2_framework/sheet.js +18 -0
- package/dist/src/generated/waterx_account/deps/std/type_name.d.ts +17 -0
- package/dist/src/generated/waterx_account/deps/std/type_name.js +19 -0
- package/dist/src/generated/waterx_account/deps/sui/balance.d.ts +12 -0
- package/dist/src/generated/waterx_account/deps/sui/balance.js +14 -0
- package/dist/src/generated/waterx_account/deps/sui/object_table.d.ts +17 -0
- package/dist/src/generated/waterx_account/deps/sui/object_table.js +19 -0
- package/dist/src/generated/waterx_account/deps/sui/table.d.ts +29 -0
- package/dist/src/generated/waterx_account/deps/sui/table.js +31 -0
- package/dist/src/generated/waterx_account/deps/sui/vec_map.d.ts +36 -0
- package/dist/src/generated/waterx_account/deps/sui/vec_map.js +27 -0
- package/dist/src/generated/waterx_account/deps/sui/vec_set.d.ts +16 -0
- package/dist/src/generated/waterx_account/deps/sui/vec_set.js +19 -0
- package/dist/src/generated/waterx_account/direct_rule.d.ts +77 -0
- package/dist/src/generated/waterx_account/direct_rule.js +78 -0
- package/dist/src/generated/waterx_account/events.d.ts +180 -0
- package/dist/src/generated/waterx_account/events.js +156 -0
- package/dist/src/generated/waterx_account/version.d.ts +10 -0
- package/dist/src/generated/waterx_account/version.js +8 -0
- package/dist/src/generated/waterx_credit/credit_registry.d.ts +708 -0
- package/dist/src/generated/waterx_credit/credit_registry.js +658 -0
- package/dist/src/generated/waterx_credit/deps/std/type_name.d.ts +17 -0
- package/dist/src/generated/waterx_credit/deps/std/type_name.js +19 -0
- package/dist/src/generated/waterx_credit/deps/sui/table.d.ts +29 -0
- package/dist/src/generated/waterx_credit/deps/sui/table.js +31 -0
- package/dist/src/generated/waterx_credit/deps/sui/vec_map.d.ts +36 -0
- package/dist/src/generated/waterx_credit/deps/sui/vec_map.js +27 -0
- package/dist/src/generated/waterx_credit/deps/sui/vec_set.d.ts +16 -0
- package/dist/src/generated/waterx_credit/deps/sui/vec_set.js +19 -0
- package/dist/src/generated/waterx_credit/limited_supply.d.ts +107 -0
- package/dist/src/generated/waterx_credit/limited_supply.js +131 -0
- package/dist/src/generated/waterx_oracle/aggregator.d.ts +211 -0
- package/dist/src/generated/waterx_oracle/aggregator.js +213 -0
- package/dist/src/generated/waterx_oracle/collector.d.ts +119 -0
- package/dist/src/generated/waterx_oracle/collector.js +105 -0
- package/dist/src/generated/waterx_oracle/deps/bucket_v2_framework/float.d.ts +8 -0
- package/dist/src/generated/waterx_oracle/deps/bucket_v2_framework/float.js +10 -0
- package/dist/src/generated/waterx_oracle/deps/std/type_name.d.ts +17 -0
- package/dist/src/generated/waterx_oracle/deps/std/type_name.js +19 -0
- package/dist/src/generated/waterx_oracle/deps/sui/vec_map.d.ts +36 -0
- package/dist/src/generated/waterx_oracle/deps/sui/vec_map.js +27 -0
- package/dist/src/generated/waterx_oracle/deps/sui/vec_set.d.ts +16 -0
- package/dist/src/generated/waterx_oracle/deps/sui/vec_set.js +19 -0
- package/dist/src/generated/waterx_oracle/oracle.d.ts +298 -0
- package/dist/src/generated/waterx_oracle/oracle.js +323 -0
- package/dist/src/generated/waterx_oracle/version.d.ts +10 -0
- package/dist/src/generated/waterx_oracle/version.js +8 -0
- package/dist/src/generated/waterx_perp/account_data.d.ts +269 -0
- package/dist/src/generated/waterx_perp/account_data.js +284 -0
- package/dist/src/generated/waterx_perp/admin.d.ts +19 -0
- package/dist/src/generated/waterx_perp/admin.js +22 -0
- package/dist/src/generated/waterx_perp/deps/bucket_v2_framework/double.d.ts +8 -0
- package/dist/src/generated/waterx_perp/deps/bucket_v2_framework/double.js +10 -0
- package/dist/src/generated/waterx_perp/deps/bucket_v2_framework/float.d.ts +8 -0
- package/dist/src/generated/waterx_perp/deps/bucket_v2_framework/float.js +10 -0
- package/dist/src/generated/waterx_perp/deps/bucket_v2_framework/liability.d.ts +11 -0
- package/dist/src/generated/waterx_perp/deps/bucket_v2_framework/liability.js +13 -0
- package/dist/src/generated/waterx_perp/deps/bucket_v2_framework/linked_table.d.ts +8 -0
- package/dist/src/generated/waterx_perp/deps/bucket_v2_framework/linked_table.js +18 -0
- package/dist/src/generated/waterx_perp/deps/bucket_v2_framework/sheet.d.ts +49 -0
- package/dist/src/generated/waterx_perp/deps/bucket_v2_framework/sheet.js +18 -0
- package/dist/src/generated/waterx_perp/deps/std/type_name.d.ts +17 -0
- package/dist/src/generated/waterx_perp/deps/std/type_name.js +19 -0
- package/dist/src/generated/waterx_perp/deps/sui/balance.d.ts +15 -0
- package/dist/src/generated/waterx_perp/deps/sui/balance.js +17 -0
- package/dist/src/generated/waterx_perp/deps/sui/coin.d.ts +14 -0
- package/dist/src/generated/waterx_perp/deps/sui/coin.js +15 -0
- package/dist/src/generated/waterx_perp/deps/sui/vec_map.d.ts +36 -0
- package/dist/src/generated/waterx_perp/deps/sui/vec_map.js +27 -0
- package/dist/src/generated/waterx_perp/deps/sui/vec_set.d.ts +16 -0
- package/dist/src/generated/waterx_perp/deps/sui/vec_set.js +19 -0
- package/dist/src/generated/waterx_perp/events.d.ts +1283 -0
- package/dist/src/generated/waterx_perp/events.js +1018 -0
- package/dist/src/generated/waterx_perp/global_config.d.ts +775 -0
- package/dist/src/generated/waterx_perp/global_config.js +836 -0
- package/dist/src/generated/waterx_perp/keyed_big_vector.d.ts +553 -0
- package/dist/src/generated/waterx_perp/keyed_big_vector.js +554 -0
- package/dist/src/generated/waterx_perp/lp_pool.d.ts +1648 -0
- package/dist/src/generated/waterx_perp/lp_pool.js +1521 -0
- package/dist/src/generated/waterx_perp/market_config.d.ts +715 -0
- package/dist/src/generated/waterx_perp/market_config.js +780 -0
- package/dist/src/generated/waterx_perp/math.d.ts +63 -0
- package/dist/src/generated/waterx_perp/math.js +64 -0
- package/dist/src/generated/waterx_perp/memo.d.ts +251 -0
- package/dist/src/generated/waterx_perp/memo.js +323 -0
- package/dist/src/generated/waterx_perp/order_book.d.ts +290 -0
- package/dist/src/generated/waterx_perp/order_book.js +329 -0
- package/dist/src/generated/waterx_perp/position.d.ts +1005 -0
- package/dist/src/generated/waterx_perp/position.js +1103 -0
- package/dist/src/generated/waterx_perp/request.d.ts +544 -0
- package/dist/src/generated/waterx_perp/request.js +541 -0
- package/dist/src/generated/waterx_perp/response.d.ts +247 -0
- package/dist/src/generated/waterx_perp/response.js +280 -0
- package/dist/src/generated/waterx_perp/trading.d.ts +2821 -0
- package/dist/src/generated/waterx_perp/trading.js +2372 -0
- package/dist/src/generated/waterx_perp/version.d.ts +10 -0
- package/dist/src/generated/waterx_perp/version.js +8 -0
- package/dist/src/generated/waterx_perp/witness.d.ts +18 -0
- package/dist/src/generated/waterx_perp/witness.js +21 -0
- package/dist/src/generated/waterx_perp_view/deps/std/type_name.d.ts +17 -0
- package/dist/src/generated/waterx_perp_view/deps/std/type_name.js +19 -0
- package/dist/src/generated/waterx_perp_view/view.d.ts +447 -0
- package/dist/src/generated/waterx_perp_view/view.js +414 -0
- package/dist/src/generated/waterx_pyth_rule/deps/sui/vec_map.d.ts +36 -0
- package/dist/src/generated/waterx_pyth_rule/deps/sui/vec_map.js +27 -0
- package/dist/src/generated/waterx_pyth_rule/pyth_rule.d.ts +90 -0
- package/dist/src/generated/waterx_pyth_rule/pyth_rule.js +72 -0
- package/dist/src/generated/waterx_referral/deps/sui/table.d.ts +29 -0
- package/dist/src/generated/waterx_referral/deps/sui/table.js +31 -0
- package/dist/src/generated/waterx_referral/deps/sui/vec_set.d.ts +16 -0
- package/dist/src/generated/waterx_referral/deps/sui/vec_set.js +19 -0
- package/dist/src/generated/waterx_referral/referral_table.d.ts +171 -0
- package/dist/src/generated/waterx_referral/referral_table.js +192 -0
- package/dist/src/generated/waterx_staking/deps/bucket_v2_framework/double.d.ts +8 -0
- package/dist/src/generated/waterx_staking/deps/bucket_v2_framework/double.js +10 -0
- package/dist/src/generated/waterx_staking/deps/sui/balance.d.ts +12 -0
- package/dist/src/generated/waterx_staking/deps/sui/balance.js +14 -0
- package/dist/src/generated/waterx_staking/deps/sui/table.d.ts +29 -0
- package/dist/src/generated/waterx_staking/deps/sui/table.js +31 -0
- package/dist/src/generated/waterx_staking/deps/sui/vec_set.d.ts +16 -0
- package/dist/src/generated/waterx_staking/deps/sui/vec_set.js +19 -0
- package/dist/src/generated/waterx_staking/waterx_staking.d.ts +746 -0
- package/dist/src/generated/waterx_staking/waterx_staking.js +731 -0
- package/dist/src/generated/waterx_staking/witness.d.ts +16 -0
- package/dist/src/generated/waterx_staking/witness.js +18 -0
- package/dist/src/generated/withdrawal_queue/deps/bucket_v2_framework/linked_table.d.ts +8 -0
- package/dist/src/generated/withdrawal_queue/deps/bucket_v2_framework/linked_table.js +18 -0
- package/dist/src/generated/withdrawal_queue/deps/std/type_name.d.ts +17 -0
- package/dist/src/generated/withdrawal_queue/deps/std/type_name.js +19 -0
- package/dist/src/generated/withdrawal_queue/deps/sui/balance.d.ts +12 -0
- package/dist/src/generated/withdrawal_queue/deps/sui/balance.js +14 -0
- package/dist/src/generated/withdrawal_queue/deps/sui/vec_set.d.ts +16 -0
- package/dist/src/generated/withdrawal_queue/deps/sui/vec_set.js +19 -0
- package/dist/src/generated/withdrawal_queue/withdrawal_queue.d.ts +483 -0
- package/dist/src/generated/withdrawal_queue/withdrawal_queue.js +480 -0
- package/dist/src/generated/wlp/wlp.d.ts +28 -0
- package/dist/src/generated/wlp/wlp.js +32 -0
- package/dist/src/generated/wormhole_bridge/deps/sui/table.d.ts +29 -0
- package/dist/src/generated/wormhole_bridge/deps/sui/table.js +31 -0
- package/dist/src/generated/wormhole_bridge/deps/sui/vec_map.d.ts +36 -0
- package/dist/src/generated/wormhole_bridge/deps/sui/vec_map.js +27 -0
- package/dist/src/generated/wormhole_bridge/deps/sui/vec_set.d.ts +16 -0
- package/dist/src/generated/wormhole_bridge/deps/sui/vec_set.js +19 -0
- package/dist/src/generated/wormhole_bridge/deps/wormhole_sdk/consumed_vaas.d.ts +12 -0
- package/dist/src/generated/wormhole_bridge/deps/wormhole_sdk/consumed_vaas.js +9 -0
- package/dist/src/generated/wormhole_bridge/deps/wormhole_sdk/emitter.d.ts +14 -0
- package/dist/src/generated/wormhole_bridge/deps/wormhole_sdk/emitter.js +16 -0
- package/dist/src/generated/wormhole_bridge/deps/wormhole_sdk/set.d.ts +16 -0
- package/dist/src/generated/wormhole_bridge/deps/wormhole_sdk/set.js +15 -0
- package/dist/src/generated/wormhole_bridge/events.d.ts +32 -0
- package/dist/src/generated/wormhole_bridge/events.js +28 -0
- package/dist/src/generated/wormhole_bridge/wormhole_bridge.d.ts +498 -0
- package/dist/src/generated/wormhole_bridge/wormhole_bridge.js +494 -0
- package/dist/src/index.d.ts +28 -0
- package/dist/src/index.js +34 -0
- package/dist/src/tx-builders.d.ts +242 -0
- package/dist/src/tx-builders.js +320 -0
- package/dist/src/user/account.d.ts +129 -0
- package/dist/src/user/account.js +166 -0
- package/dist/src/user/credit.d.ts +135 -0
- package/dist/src/user/credit.js +255 -0
- package/dist/src/user/custody.d.ts +71 -0
- package/dist/src/user/custody.js +112 -0
- package/dist/src/user/index.d.ts +8 -0
- package/dist/src/user/index.js +8 -0
- package/dist/src/user/order.d.ts +89 -0
- package/dist/src/user/order.js +134 -0
- package/dist/src/user/referral.d.ts +21 -0
- package/dist/src/user/referral.js +41 -0
- package/dist/src/user/staking.d.ts +54 -0
- package/dist/src/user/staking.js +109 -0
- package/dist/src/user/trading.d.ts +119 -0
- package/dist/src/user/trading.js +262 -0
- package/dist/src/user/wlp.d.ts +58 -0
- package/dist/src/user/wlp.js +99 -0
- package/dist/src/utils/account-request.d.ts +9 -0
- package/dist/src/utils/account-request.js +17 -0
- package/dist/src/utils/config.d.ts +11 -0
- package/dist/src/utils/config.js +15 -0
- package/dist/src/utils/math.d.ts +201 -0
- package/dist/src/utils/math.js +343 -0
- package/dist/src/utils/pyth.d.ts +89 -0
- package/dist/src/utils/pyth.js +316 -0
- package/dist/src/utils/wormhole.d.ts +86 -0
- package/dist/src/utils/wormhole.js +154 -0
- package/package.json +66 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pyth price feed integration for the WaterX v3 oracle (ticker-based).
|
|
3
|
+
*
|
|
4
|
+
* Hermes REST fetches price update VAAs; on-chain a single PTB:
|
|
5
|
+
* 1. wormhole::vaa::parse_and_verify
|
|
6
|
+
* 2. pyth::create_authenticated_price_infos_using_accumulator
|
|
7
|
+
* 3. pyth::update_single_price_feed (one per feed)
|
|
8
|
+
* 4. for each ticker: oracle::new_collector(symbol) → pyth_rule::feed → oracle::aggregate
|
|
9
|
+
*
|
|
10
|
+
* No type parameters on `feed` / `aggregate` anymore — the oracle is one
|
|
11
|
+
* shared object keyed by ticker. `pyth_rule::feed` looks up the on-chain
|
|
12
|
+
* `PriceInfoObject` ID by ticker via the `pyth_rule::Config` identifier map.
|
|
13
|
+
*/
|
|
14
|
+
import { fromHex, toHex } from "@mysten/bcs";
|
|
15
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Pyth-sponsor flow — pays Pyth update fees from a shared sponsor pool AND
|
|
18
|
+
// attaches the `PythSponsorRule` witness to a TradingRequest. Required when
|
|
19
|
+
// the market's `request_checklist` contains `PythSponsorRule`.
|
|
20
|
+
// ============================================================================
|
|
21
|
+
import { reimburse as sponsorReimburse, request as sponsorRequest, } from "../generated/pyth_sponsor_rule/pyth_sponsor_rule.js";
|
|
22
|
+
import { aggregate as aggregateCall, newCollector } from "../generated/waterx_oracle/oracle.js";
|
|
23
|
+
import { feed as pythRuleFeed } from "../generated/waterx_pyth_rule/pyth_rule.js";
|
|
24
|
+
export class PythCache {
|
|
25
|
+
pythStateInfo;
|
|
26
|
+
wormholePackageId;
|
|
27
|
+
priceTableInfo;
|
|
28
|
+
priceFeedObjectIdCache = new Map();
|
|
29
|
+
}
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Hermes REST
|
|
32
|
+
// ============================================================================
|
|
33
|
+
export async function fetchPriceFeedsUpdateData(endpoint, priceIds) {
|
|
34
|
+
if (priceIds.length === 0)
|
|
35
|
+
return [];
|
|
36
|
+
const url = new URL("/v2/updates/price/latest", endpoint);
|
|
37
|
+
priceIds.forEach((id) => url.searchParams.append("ids[]", id));
|
|
38
|
+
const res = await fetch(url.toString(), { signal: AbortSignal.timeout(15_000) });
|
|
39
|
+
if (!res.ok)
|
|
40
|
+
throw new Error(`Hermes price fetch failed: ${res.status} ${await res.text()}`);
|
|
41
|
+
const json = (await res.json());
|
|
42
|
+
const data = json.binary?.data;
|
|
43
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
44
|
+
throw new Error("Hermes returned no binary price data");
|
|
45
|
+
}
|
|
46
|
+
return data.map((hex) => fromHex(hex));
|
|
47
|
+
}
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// On-chain helpers (cached)
|
|
50
|
+
// ============================================================================
|
|
51
|
+
function pkgFromUpgradeCap(json, objectId) {
|
|
52
|
+
const upgradeCap = json.upgrade_cap ??
|
|
53
|
+
json.fields?.upgrade_cap;
|
|
54
|
+
const nested = upgradeCap?.fields?.package;
|
|
55
|
+
const pkg = typeof upgradeCap?.package === "string"
|
|
56
|
+
? upgradeCap.package
|
|
57
|
+
: typeof nested === "string"
|
|
58
|
+
? nested
|
|
59
|
+
: undefined;
|
|
60
|
+
if (!pkg)
|
|
61
|
+
throw new Error(`Cannot resolve package id for object ${objectId}`);
|
|
62
|
+
return pkg;
|
|
63
|
+
}
|
|
64
|
+
async function getPythStateInfo(client, pythStateId, cache) {
|
|
65
|
+
if (cache?.pythStateInfo)
|
|
66
|
+
return cache.pythStateInfo;
|
|
67
|
+
const result = await client.getObject({ objectId: pythStateId, include: { json: true } });
|
|
68
|
+
const json = result.object?.json;
|
|
69
|
+
if (!json)
|
|
70
|
+
throw new Error("Unable to fetch pyth state");
|
|
71
|
+
const packageId = pkgFromUpgradeCap(json, pythStateId);
|
|
72
|
+
const fields = json.fields;
|
|
73
|
+
const fee = (fields?.base_update_fee ?? json.base_update_fee);
|
|
74
|
+
if (fee === undefined)
|
|
75
|
+
throw new Error("Unable to fetch pyth state base_update_fee");
|
|
76
|
+
const info = { packageId, baseUpdateFee: BigInt(fee) };
|
|
77
|
+
if (cache)
|
|
78
|
+
cache.pythStateInfo = info;
|
|
79
|
+
return info;
|
|
80
|
+
}
|
|
81
|
+
async function getWormholePackageId(client, wormholeStateId, cache) {
|
|
82
|
+
if (cache?.wormholePackageId)
|
|
83
|
+
return cache.wormholePackageId;
|
|
84
|
+
const result = await client.getObject({ objectId: wormholeStateId, include: { json: true } });
|
|
85
|
+
const json = result.object?.json;
|
|
86
|
+
if (!json)
|
|
87
|
+
throw new Error(`Cannot resolve wormhole package id from ${wormholeStateId}`);
|
|
88
|
+
const pkg = pkgFromUpgradeCap(json, wormholeStateId);
|
|
89
|
+
if (cache)
|
|
90
|
+
cache.wormholePackageId = pkg;
|
|
91
|
+
return pkg;
|
|
92
|
+
}
|
|
93
|
+
async function getPriceTableInfo(client, pythStateId, cache) {
|
|
94
|
+
if (cache?.priceTableInfo)
|
|
95
|
+
return cache.priceTableInfo;
|
|
96
|
+
const list = await client.listDynamicFields({ parentId: pythStateId });
|
|
97
|
+
const entry = list.dynamicFields.find((e) => {
|
|
98
|
+
const vt = e.valueType || e.objectType || "";
|
|
99
|
+
return vt.includes("PriceIdentifier") || vt.includes("price_info");
|
|
100
|
+
});
|
|
101
|
+
if (!entry)
|
|
102
|
+
throw new Error("Price table not found in Pyth state dynamic fields");
|
|
103
|
+
const childId = entry.childId ?? entry.objectId;
|
|
104
|
+
const typeStr = entry.valueType || entry.objectType || "";
|
|
105
|
+
if (!childId)
|
|
106
|
+
throw new Error("Price table missing childId");
|
|
107
|
+
const pkgMatch = typeStr.match(/(0x[a-fA-F0-9]+)::price_identifier::PriceIdentifier/);
|
|
108
|
+
if (!pkgMatch)
|
|
109
|
+
throw new Error(`Cannot extract package from price table type: ${typeStr}`);
|
|
110
|
+
const info = { id: childId, fieldType: pkgMatch[1] };
|
|
111
|
+
if (cache)
|
|
112
|
+
cache.priceTableInfo = info;
|
|
113
|
+
return info;
|
|
114
|
+
}
|
|
115
|
+
async function getPriceFeedObjectId(client, table, feedId, cache, pythStateId) {
|
|
116
|
+
const normalized = feedId.replace(/^0x/, "");
|
|
117
|
+
const cacheKey = pythStateId ? `${pythStateId}:${normalized}` : normalized;
|
|
118
|
+
if (cache?.priceFeedObjectIdCache.has(cacheKey)) {
|
|
119
|
+
return cache.priceFeedObjectIdCache.get(cacheKey);
|
|
120
|
+
}
|
|
121
|
+
const keyBytes = bcs
|
|
122
|
+
.struct("PriceIdentifier", { bytes: bcs.vector(bcs.u8()) })
|
|
123
|
+
.serialize({ bytes: fromHex(normalized) })
|
|
124
|
+
.toBytes();
|
|
125
|
+
const result = await client.getDynamicField({
|
|
126
|
+
parentId: table.id,
|
|
127
|
+
name: { type: `${table.fieldType}::price_identifier::PriceIdentifier`, bcs: keyBytes },
|
|
128
|
+
});
|
|
129
|
+
const value = result.dynamicField?.value;
|
|
130
|
+
const objectId = !value?.bcs || value.bcs.length < 32 ? undefined : "0x" + toHex(value.bcs);
|
|
131
|
+
if (cache)
|
|
132
|
+
cache.priceFeedObjectIdCache.set(cacheKey, objectId);
|
|
133
|
+
return objectId;
|
|
134
|
+
}
|
|
135
|
+
// ============================================================================
|
|
136
|
+
// Accumulator parsing
|
|
137
|
+
// ============================================================================
|
|
138
|
+
function extractVaaBytes(accumulatorMessage) {
|
|
139
|
+
const view = new DataView(accumulatorMessage.buffer, accumulatorMessage.byteOffset, accumulatorMessage.byteLength);
|
|
140
|
+
const trailingPayloadSize = view.getUint8(6);
|
|
141
|
+
const vaaSizeOffset = 7 + trailingPayloadSize + 1; // +1 for proof_type
|
|
142
|
+
const vaaSize = view.getUint16(vaaSizeOffset, false);
|
|
143
|
+
const vaaOffset = vaaSizeOffset + 2;
|
|
144
|
+
return accumulatorMessage.subarray(vaaOffset, vaaOffset + vaaSize);
|
|
145
|
+
}
|
|
146
|
+
// ============================================================================
|
|
147
|
+
// Pyth update calls
|
|
148
|
+
// ============================================================================
|
|
149
|
+
/**
|
|
150
|
+
* Append the on-chain Pyth update PTB block. Returns `PriceInfoObject` IDs
|
|
151
|
+
* (one per `feedIds`, same order). After this you can call
|
|
152
|
+
* `pyth_rule::feed` per ticker against the matching `PriceInfoObject`.
|
|
153
|
+
*
|
|
154
|
+
* If `sponsorFund` is provided, the per-feed update fee comes from the
|
|
155
|
+
* sponsor pool (`pyth_sponsor_rule::split`) instead of `tx.gas`.
|
|
156
|
+
*/
|
|
157
|
+
export async function buildPythPriceUpdateCalls(tx, client, updates, feedIds, cache, sponsorFund) {
|
|
158
|
+
if (updates.length === 0) {
|
|
159
|
+
throw new Error("No price update data provided; Hermes returned empty results");
|
|
160
|
+
}
|
|
161
|
+
if (updates.length > 1) {
|
|
162
|
+
throw new Error("Only a single accumulator message is supported per transaction");
|
|
163
|
+
}
|
|
164
|
+
const pyth = client.pyth;
|
|
165
|
+
const [stateInfo, wormholePackageId, table] = await Promise.all([
|
|
166
|
+
getPythStateInfo(client.grpcClient, pyth.state_id, cache),
|
|
167
|
+
getWormholePackageId(client.grpcClient, pyth.wormhole_state_id, cache),
|
|
168
|
+
getPriceTableInfo(client.grpcClient, pyth.state_id, cache),
|
|
169
|
+
]);
|
|
170
|
+
const priceInfoObjectIds = await Promise.all(feedIds.map((feedId) => getPriceFeedObjectId(client.grpcClient, table, feedId, cache, pyth.state_id)));
|
|
171
|
+
const { packageId: pythPackageId, baseUpdateFee } = stateInfo;
|
|
172
|
+
// 1. Verify VAA
|
|
173
|
+
const vaa = extractVaaBytes(updates[0]);
|
|
174
|
+
const [verifiedVaa] = tx.moveCall({
|
|
175
|
+
target: `${wormholePackageId}::vaa::parse_and_verify`,
|
|
176
|
+
arguments: [tx.object(pyth.wormhole_state_id), tx.pure.vector("u8", vaa), tx.object.clock()],
|
|
177
|
+
});
|
|
178
|
+
// 2. Authenticate price infos
|
|
179
|
+
const [hotPotato0] = tx.moveCall({
|
|
180
|
+
target: `${pythPackageId}::pyth::create_authenticated_price_infos_using_accumulator`,
|
|
181
|
+
arguments: [
|
|
182
|
+
tx.object(pyth.state_id),
|
|
183
|
+
tx.pure.vector("u8", updates[0]),
|
|
184
|
+
verifiedVaa,
|
|
185
|
+
tx.object.clock(),
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
// 3. Per-feed update
|
|
189
|
+
let hotPotato = hotPotato0;
|
|
190
|
+
for (let i = 0; i < feedIds.length; i++) {
|
|
191
|
+
const priceInfoObjectId = priceInfoObjectIds[i];
|
|
192
|
+
if (!priceInfoObjectId) {
|
|
193
|
+
throw new Error(`Pyth feed ${feedIds[i]} not registered on-chain in Pyth state`);
|
|
194
|
+
}
|
|
195
|
+
const feeCoin = sponsorFund
|
|
196
|
+
? tx.moveCall({
|
|
197
|
+
target: `${sponsorFund.packageId}::pyth_sponsor_rule::split`,
|
|
198
|
+
arguments: [sponsorFund.fund],
|
|
199
|
+
})[0]
|
|
200
|
+
: tx.splitCoins(tx.gas, [tx.pure.u64(baseUpdateFee)])[0];
|
|
201
|
+
[hotPotato] = tx.moveCall({
|
|
202
|
+
target: `${pythPackageId}::pyth::update_single_price_feed`,
|
|
203
|
+
arguments: [
|
|
204
|
+
tx.object(pyth.state_id),
|
|
205
|
+
hotPotato,
|
|
206
|
+
tx.object(priceInfoObjectId),
|
|
207
|
+
feeCoin,
|
|
208
|
+
tx.object.clock(),
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
// 4. Destroy hot potato
|
|
213
|
+
tx.moveCall({
|
|
214
|
+
target: `${pythPackageId}::hot_potato_vector::destroy`,
|
|
215
|
+
arguments: [hotPotato],
|
|
216
|
+
typeArguments: [`${pythPackageId}::price_info::PriceInfo`],
|
|
217
|
+
});
|
|
218
|
+
return priceInfoObjectIds;
|
|
219
|
+
}
|
|
220
|
+
/** All-in-one: fetch from Hermes, append update calls. Returns PriceInfoObject IDs. */
|
|
221
|
+
export async function updatePythPrices(tx, client, feedIds, cache, sponsorFund) {
|
|
222
|
+
const updates = await fetchPriceFeedsUpdateData(client.pyth.hermes_endpoint, feedIds);
|
|
223
|
+
return buildPythPriceUpdateCalls(tx, client, updates, feedIds, cache, sponsorFund);
|
|
224
|
+
}
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// Per-ticker refresh: collector → pyth_rule::feed → oracle::aggregate
|
|
227
|
+
// ============================================================================
|
|
228
|
+
/**
|
|
229
|
+
* For a given ticker, build the collector → feed → aggregate chain that
|
|
230
|
+
* refreshes the on-chain `Oracle` aggregator for that ticker.
|
|
231
|
+
*
|
|
232
|
+
* Caller must have first called `buildPythPriceUpdateCalls` /
|
|
233
|
+
* `updatePythPrices` so the corresponding `PriceInfoObject` is fresh.
|
|
234
|
+
*/
|
|
235
|
+
export function aggregateTickerWithPyth(tx, client, args) {
|
|
236
|
+
const collector = newCollector({
|
|
237
|
+
package: client.config.packages.waterx_oracle.published_at,
|
|
238
|
+
arguments: { symbol: args.ticker },
|
|
239
|
+
})(tx);
|
|
240
|
+
pythRuleFeed({
|
|
241
|
+
package: client.config.packages.pyth_rule.published_at,
|
|
242
|
+
arguments: {
|
|
243
|
+
collector: collector,
|
|
244
|
+
config: tx.object(client.config.packages.pyth_rule.config),
|
|
245
|
+
pythState: tx.object(client.pyth.state_id),
|
|
246
|
+
pythPriceInfo: tx.object(args.priceInfoObjectId),
|
|
247
|
+
},
|
|
248
|
+
})(tx);
|
|
249
|
+
aggregateCall({
|
|
250
|
+
package: client.config.packages.waterx_oracle.published_at,
|
|
251
|
+
arguments: {
|
|
252
|
+
oracle: tx.object(client.config.packages.waterx_oracle.oracle),
|
|
253
|
+
collector: collector,
|
|
254
|
+
},
|
|
255
|
+
})(tx);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Refresh multiple tickers via Pyth in one PTB. Updates Pyth on-chain
|
|
259
|
+
* first (one accumulator), then runs the collector → feed → aggregate
|
|
260
|
+
* cycle for each ticker.
|
|
261
|
+
*/
|
|
262
|
+
export async function refreshOraclePrices(tx, client, tickers, opts = {}) {
|
|
263
|
+
if (tickers.length === 0)
|
|
264
|
+
return;
|
|
265
|
+
// tickers are oracle tickers (e.g. "BTCUSD"); look up each one's pyth feed
|
|
266
|
+
// entry from config — both feed_id (off-chain) and price_info_object
|
|
267
|
+
// (on-chain) are consumed by the per-ticker aggregate cycle.
|
|
268
|
+
const entries = tickers.map((t) => client.getPythFeed(t));
|
|
269
|
+
const feedIds = entries.map((e) => e.feed_id);
|
|
270
|
+
await updatePythPrices(tx, client, feedIds, opts.cache, opts.sponsorFund);
|
|
271
|
+
tickers.forEach((ticker, i) => {
|
|
272
|
+
aggregateTickerWithPyth(tx, client, {
|
|
273
|
+
ticker,
|
|
274
|
+
priceInfoObjectId: entries[i].price_info_object,
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Opens a `Fund` hot potato from the shared PythSponsor pool. Pass the
|
|
280
|
+
* returned `{ fund, packageId }` straight to `refreshOraclePrices` as
|
|
281
|
+
* `sponsorFund`, then `reimbursePythSponsor` once the TradingRequest is
|
|
282
|
+
* built to attach the `PythSponsorRule` witness and return leftover
|
|
283
|
+
* balance.
|
|
284
|
+
*/
|
|
285
|
+
export function openPythSponsorFund(tx, client) {
|
|
286
|
+
const entry = client.config.packages.pyth_sponsor_rule;
|
|
287
|
+
if (!entry?.published_at || !entry.pyth_sponsor) {
|
|
288
|
+
throw new Error("pyth_sponsor_rule.{published_at,pyth_sponsor} missing — sponsor flow unavailable");
|
|
289
|
+
}
|
|
290
|
+
const [fund] = sponsorRequest({
|
|
291
|
+
package: entry.published_at,
|
|
292
|
+
arguments: { self: tx.object(entry.pyth_sponsor) },
|
|
293
|
+
})(tx);
|
|
294
|
+
return { fund: fund, packageId: entry.published_at };
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Consumes the `Fund` hot potato from `openPythSponsorFund`, returns any
|
|
298
|
+
* leftover SUI to the sponsor pool, and attaches the `PythSponsorRule`
|
|
299
|
+
* witness to the given `TradingRequest<C_TOKEN>` so `trading::execute`
|
|
300
|
+
* can satisfy a checklist that includes `PythSponsorRule`.
|
|
301
|
+
*/
|
|
302
|
+
export function reimbursePythSponsor(tx, client, fund, tradingRequest, collateralType) {
|
|
303
|
+
const entry = client.config.packages.pyth_sponsor_rule;
|
|
304
|
+
if (!entry?.published_at || !entry.pyth_sponsor) {
|
|
305
|
+
throw new Error("pyth_sponsor_rule missing from config");
|
|
306
|
+
}
|
|
307
|
+
sponsorReimburse({
|
|
308
|
+
package: entry.published_at,
|
|
309
|
+
arguments: {
|
|
310
|
+
self: tx.object(entry.pyth_sponsor),
|
|
311
|
+
fund: fund,
|
|
312
|
+
tradingReq: tradingRequest,
|
|
313
|
+
},
|
|
314
|
+
typeArguments: [collateralType],
|
|
315
|
+
})(tx);
|
|
316
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wormhole / Wormholescan integration for the cross-chain credit bridge.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for VAA discovery + (de)serialization — mirrors
|
|
5
|
+
* `utils/pyth.ts`'s role for Pyth's Hermes endpoint. Two directions:
|
|
6
|
+
*
|
|
7
|
+
* - Mint (EVM → Sui): fetch the signed VAA for an EVM `Deposit` by
|
|
8
|
+
* (emitter chain, emitter address, sequence); the raw bytes feed
|
|
9
|
+
* `wormhole_bridge::redeem_vaa`.
|
|
10
|
+
* - Withdraw (Sui → EVM): list recent VAAs emitted by the bridge's
|
|
11
|
+
* `EmitterCap` to submit on the EVM side.
|
|
12
|
+
*
|
|
13
|
+
* Endpoints / chain ids come from `client.wormhole` (network defaults in
|
|
14
|
+
* `WORMHOLE_DEFAULTS`, overridable via `WaterXConfig.wormhole`).
|
|
15
|
+
*/
|
|
16
|
+
import type { WaterXClient } from "../client.ts";
|
|
17
|
+
/**
|
|
18
|
+
* Normalize an emitter id to Wormholescan's `emitter` path segment:
|
|
19
|
+
* 64-char lowercase hex, no `0x`. Accepts a 0x EVM address (left-padded to
|
|
20
|
+
* 32 bytes) or a 32-byte Sui object id (the bridge `EmitterCap` id).
|
|
21
|
+
*/
|
|
22
|
+
export declare function toWormholescanEmitter(idOrAddress: string): string;
|
|
23
|
+
/** EVM-side naming alias of {@link toWormholescanEmitter}. */
|
|
24
|
+
export declare const padEvmEmitter: typeof toWormholescanEmitter;
|
|
25
|
+
export interface VaaResponse {
|
|
26
|
+
/** Base64-encoded signed VAA. */
|
|
27
|
+
vaa: string;
|
|
28
|
+
sequence: number;
|
|
29
|
+
emitterAddr: string;
|
|
30
|
+
guardianSetIndex: number;
|
|
31
|
+
}
|
|
32
|
+
export interface VaaListItem {
|
|
33
|
+
/** Wormholescan returns u64 as a string — keep it that way to avoid `number` precision loss. */
|
|
34
|
+
sequence: string;
|
|
35
|
+
/** Base64 VAA; absent until guardians have signed. */
|
|
36
|
+
vaa?: string;
|
|
37
|
+
emitterAddr?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface WormholescanOptions {
|
|
40
|
+
/** Override `fetch` (tests / non-global-fetch environments). */
|
|
41
|
+
fetchImpl?: typeof fetch;
|
|
42
|
+
/** Request timeout in ms. Default 10_000. */
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Fetch a single signed VAA. Returns `null` while guardians haven't signed
|
|
47
|
+
* it yet (HTTP 404 or no `vaa` field) so callers can retry on the next tick.
|
|
48
|
+
*/
|
|
49
|
+
export declare function fetchVaa(apiBase: string, chainId: number, emitter: string, sequence: number | string | bigint, opts?: WormholescanOptions): Promise<VaaResponse | null>;
|
|
50
|
+
/**
|
|
51
|
+
* List recent VAAs emitted by `emitter` on `chainId`, newest first. Used by
|
|
52
|
+
* the burn relayer to discover Sui→EVM `WithdrawalInitiated` messages
|
|
53
|
+
* (Wormholescan only returns guardian-signed VAAs, so the await is implicit).
|
|
54
|
+
*/
|
|
55
|
+
export declare function listVaasByEmitter(apiBase: string, chainId: number, emitter: string, opts?: WormholescanOptions & {
|
|
56
|
+
page?: number;
|
|
57
|
+
pageSize?: number;
|
|
58
|
+
}): Promise<VaaListItem[]>;
|
|
59
|
+
/** Poll {@link fetchVaa} until the VAA is signed or `timeoutMs` elapses. */
|
|
60
|
+
export declare function waitForVaa(apiBase: string, chainId: number, emitter: string, sequence: number | string | bigint, opts?: WormholescanOptions & {
|
|
61
|
+
intervalMs?: number;
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
onTick?: (attempt: number) => void;
|
|
64
|
+
signal?: AbortSignal;
|
|
65
|
+
}): Promise<VaaResponse>;
|
|
66
|
+
/** Base64 VAA → raw bytes (the form Sui `redeem_vaa(vector<u8>)` expects). */
|
|
67
|
+
export declare function vaaBase64ToBytes(b64: string): Uint8Array;
|
|
68
|
+
/** Raw bytes → base64 (round-trips a VAA pulled off-chain). */
|
|
69
|
+
export declare function vaaBytesToBase64(bytes: Uint8Array): string;
|
|
70
|
+
/** Base64 VAA → `0x`-prefixed hex (the form EVM `submitWithdrawalVAA(bytes)` expects). */
|
|
71
|
+
export declare function vaaBase64ToHex(b64: string): `0x${string}`;
|
|
72
|
+
/**
|
|
73
|
+
* Fetch the signed VAA for an inbound EVM `Deposit` using the client's
|
|
74
|
+
* configured Wormholescan endpoint. `emitter` is the EVM-side bridge
|
|
75
|
+
* address (0x form accepted — left-padded automatically); `chainId` is the
|
|
76
|
+
* source EVM chain's Wormhole chain id.
|
|
77
|
+
*/
|
|
78
|
+
export declare function fetchDepositVaa(client: WaterXClient, evmWormholeChainId: number, evmEmitter: string, sequence: number | string | bigint, opts?: WormholescanOptions): Promise<VaaResponse | null>;
|
|
79
|
+
/**
|
|
80
|
+
* List recent Sui→EVM VAAs from the configured bridge `EmitterCap` (burn
|
|
81
|
+
* relayer). Requires `packages.wormhole_bridge.emitter_cap` in config.
|
|
82
|
+
*/
|
|
83
|
+
export declare function listBridgeWithdrawalVaas(client: WaterXClient, opts?: WormholescanOptions & {
|
|
84
|
+
page?: number;
|
|
85
|
+
pageSize?: number;
|
|
86
|
+
}): Promise<VaaListItem[]>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wormhole / Wormholescan integration for the cross-chain credit bridge.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for VAA discovery + (de)serialization — mirrors
|
|
5
|
+
* `utils/pyth.ts`'s role for Pyth's Hermes endpoint. Two directions:
|
|
6
|
+
*
|
|
7
|
+
* - Mint (EVM → Sui): fetch the signed VAA for an EVM `Deposit` by
|
|
8
|
+
* (emitter chain, emitter address, sequence); the raw bytes feed
|
|
9
|
+
* `wormhole_bridge::redeem_vaa`.
|
|
10
|
+
* - Withdraw (Sui → EVM): list recent VAAs emitted by the bridge's
|
|
11
|
+
* `EmitterCap` to submit on the EVM side.
|
|
12
|
+
*
|
|
13
|
+
* Endpoints / chain ids come from `client.wormhole` (network defaults in
|
|
14
|
+
* `WORMHOLE_DEFAULTS`, overridable via `WaterXConfig.wormhole`).
|
|
15
|
+
*/
|
|
16
|
+
import { fromBase64, toBase64, toHex } from "@mysten/bcs";
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Emitter address formatting
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Normalize an emitter id to Wormholescan's `emitter` path segment:
|
|
22
|
+
* 64-char lowercase hex, no `0x`. Accepts a 0x EVM address (left-padded to
|
|
23
|
+
* 32 bytes) or a 32-byte Sui object id (the bridge `EmitterCap` id).
|
|
24
|
+
*/
|
|
25
|
+
export function toWormholescanEmitter(idOrAddress) {
|
|
26
|
+
const raw = idOrAddress.startsWith("0x") ? idOrAddress.slice(2) : idOrAddress;
|
|
27
|
+
return raw.toLowerCase().padStart(64, "0");
|
|
28
|
+
}
|
|
29
|
+
/** EVM-side naming alias of {@link toWormholescanEmitter}. */
|
|
30
|
+
export const padEvmEmitter = toWormholescanEmitter;
|
|
31
|
+
async function doFetch(url, opts) {
|
|
32
|
+
const fetchImpl = opts?.fetchImpl ?? globalThis.fetch;
|
|
33
|
+
if (!fetchImpl) {
|
|
34
|
+
throw new Error("wormhole: no global `fetch` available; pass opts.fetchImpl");
|
|
35
|
+
}
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? 10_000);
|
|
38
|
+
const onOuterAbort = () => controller.abort();
|
|
39
|
+
opts?.signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
40
|
+
try {
|
|
41
|
+
return await fetchImpl(url, { cache: "no-store", signal: controller.signal });
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
opts?.signal?.removeEventListener("abort", onOuterAbort);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fetch a single signed VAA. Returns `null` while guardians haven't signed
|
|
50
|
+
* it yet (HTTP 404 or no `vaa` field) so callers can retry on the next tick.
|
|
51
|
+
*/
|
|
52
|
+
export async function fetchVaa(apiBase, chainId, emitter, sequence, opts) {
|
|
53
|
+
const url = `${apiBase}/vaas/${chainId}/${emitter}/${sequence}`;
|
|
54
|
+
const res = await doFetch(url, opts);
|
|
55
|
+
if (res.status === 404)
|
|
56
|
+
return null;
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new Error(`Wormholescan ${res.status}: ${await res.text()}`);
|
|
59
|
+
}
|
|
60
|
+
const json = (await res.json());
|
|
61
|
+
if (!json?.data?.vaa)
|
|
62
|
+
return null;
|
|
63
|
+
return json.data;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* List recent VAAs emitted by `emitter` on `chainId`, newest first. Used by
|
|
67
|
+
* the burn relayer to discover Sui→EVM `WithdrawalInitiated` messages
|
|
68
|
+
* (Wormholescan only returns guardian-signed VAAs, so the await is implicit).
|
|
69
|
+
*/
|
|
70
|
+
export async function listVaasByEmitter(apiBase, chainId, emitter, opts) {
|
|
71
|
+
const page = opts?.page ?? 0;
|
|
72
|
+
const pageSize = opts?.pageSize ?? 20;
|
|
73
|
+
const url = `${apiBase}/vaas/${chainId}/${emitter}?page=${page}&pageSize=${pageSize}`;
|
|
74
|
+
const res = await doFetch(url, opts);
|
|
75
|
+
if (!res.ok) {
|
|
76
|
+
throw new Error(`Wormholescan list ${res.status}: ${await res.text()}`);
|
|
77
|
+
}
|
|
78
|
+
const json = (await res.json());
|
|
79
|
+
return json.data ?? [];
|
|
80
|
+
}
|
|
81
|
+
/** Poll {@link fetchVaa} until the VAA is signed or `timeoutMs` elapses. */
|
|
82
|
+
export async function waitForVaa(apiBase, chainId, emitter, sequence, opts = {}) {
|
|
83
|
+
const intervalMs = opts.intervalMs ?? 30_000;
|
|
84
|
+
const timeoutMs = opts.timeoutMs ?? 25 * 60_000;
|
|
85
|
+
const start = Date.now();
|
|
86
|
+
let attempt = 0;
|
|
87
|
+
while (Date.now() - start < timeoutMs) {
|
|
88
|
+
if (opts.signal?.aborted)
|
|
89
|
+
throw new Error("waitForVaa: aborted");
|
|
90
|
+
attempt++;
|
|
91
|
+
opts.onTick?.(attempt);
|
|
92
|
+
const vaa = await fetchVaa(apiBase, chainId, emitter, sequence, opts);
|
|
93
|
+
if (vaa)
|
|
94
|
+
return vaa;
|
|
95
|
+
await abortableSleep(intervalMs, opts.signal);
|
|
96
|
+
}
|
|
97
|
+
throw new Error("waitForVaa: timeout");
|
|
98
|
+
}
|
|
99
|
+
function abortableSleep(ms, signal) {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const timer = setTimeout(() => {
|
|
102
|
+
signal?.removeEventListener("abort", onAbort);
|
|
103
|
+
resolve();
|
|
104
|
+
}, ms);
|
|
105
|
+
const onAbort = () => {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
reject(new Error("waitForVaa: aborted"));
|
|
108
|
+
};
|
|
109
|
+
if (signal?.aborted) {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
reject(new Error("waitForVaa: aborted"));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// VAA (de)serialization — portable (Node + edge/browser)
|
|
119
|
+
// ============================================================================
|
|
120
|
+
/** Base64 VAA → raw bytes (the form Sui `redeem_vaa(vector<u8>)` expects). */
|
|
121
|
+
export function vaaBase64ToBytes(b64) {
|
|
122
|
+
return fromBase64(b64);
|
|
123
|
+
}
|
|
124
|
+
/** Raw bytes → base64 (round-trips a VAA pulled off-chain). */
|
|
125
|
+
export function vaaBytesToBase64(bytes) {
|
|
126
|
+
return toBase64(bytes);
|
|
127
|
+
}
|
|
128
|
+
/** Base64 VAA → `0x`-prefixed hex (the form EVM `submitWithdrawalVAA(bytes)` expects). */
|
|
129
|
+
export function vaaBase64ToHex(b64) {
|
|
130
|
+
return `0x${toHex(fromBase64(b64))}`;
|
|
131
|
+
}
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// Client-aware convenience
|
|
134
|
+
// ============================================================================
|
|
135
|
+
/**
|
|
136
|
+
* Fetch the signed VAA for an inbound EVM `Deposit` using the client's
|
|
137
|
+
* configured Wormholescan endpoint. `emitter` is the EVM-side bridge
|
|
138
|
+
* address (0x form accepted — left-padded automatically); `chainId` is the
|
|
139
|
+
* source EVM chain's Wormhole chain id.
|
|
140
|
+
*/
|
|
141
|
+
export function fetchDepositVaa(client, evmWormholeChainId, evmEmitter, sequence, opts) {
|
|
142
|
+
return fetchVaa(client.wormhole.wormholescan_api, evmWormholeChainId, toWormholescanEmitter(evmEmitter), sequence, opts);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* List recent Sui→EVM VAAs from the configured bridge `EmitterCap` (burn
|
|
146
|
+
* relayer). Requires `packages.wormhole_bridge.emitter_cap` in config.
|
|
147
|
+
*/
|
|
148
|
+
export function listBridgeWithdrawalVaas(client, opts) {
|
|
149
|
+
const emitterCap = client.config.packages.wormhole_bridge?.emitter_cap;
|
|
150
|
+
if (!emitterCap) {
|
|
151
|
+
throw new Error("wormhole_bridge.emitter_cap missing from config");
|
|
152
|
+
}
|
|
153
|
+
return listVaasByEmitter(client.wormhole.wormholescan_api, client.wormhole.sui_chain_id, toWormholescanEmitter(emitterCap), opts);
|
|
154
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@waterx/sdk",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "WaterX Perpetual Protocol SDK",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "WaterX",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/WaterXProtocol/waterx-sdk.git"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "dist/src/index.js",
|
|
13
|
+
"types": "dist/src/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"package.json"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && tsc-alias",
|
|
21
|
+
"codegen": "tsx scripts/codegen-summaries.ts && npx sui-ts-codegen generate && tsx scripts/fix-generated-imports.ts",
|
|
22
|
+
"lint": "pnpm lint:eslint && pnpm lint:prettier",
|
|
23
|
+
"lint:eslint": "eslint \"src/**/*.ts\" \"test/**/*.ts\" \"scripts/**/*.ts\" \"examples/**/*.ts\" \"vitest.config.ts\"",
|
|
24
|
+
"lint:prettier": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"scripts/**/*.ts\" \"examples/**/*.ts\" \"vitest.config.ts\" \"eslint.config.mjs\" \"sui-codegen.config.mjs\"",
|
|
25
|
+
"lint:fix": "eslint \"src/**/*.ts\" \"test/**/*.ts\" \"scripts/**/*.ts\" \"examples/**/*.ts\" \"vitest.config.ts\" --fix",
|
|
26
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"scripts/**/*.ts\" \"examples/**/*.ts\" \"vitest.config.ts\" \"eslint.config.mjs\" \"sui-codegen.config.mjs\"",
|
|
27
|
+
"env:init": "tsx scripts/init-local-env.ts",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"test:integration": "tsx scripts/run-integration.ts --testnet",
|
|
30
|
+
"test:integration:mainnet": "tsx scripts/run-integration.ts --mainnet",
|
|
31
|
+
"test:integration:lifecycle": "tsx scripts/run-integration.ts --testnet test/integration/user/trader-position-lifecycle.test.ts",
|
|
32
|
+
"test:integration:market-config": "tsx scripts/run-integration.ts --testnet test/integration/user/trader-market-onchain-config.test.ts",
|
|
33
|
+
"test:integration:persistent-state": "tsx scripts/run-integration.ts --testnet test/integration/user/trader-e2e-persistent-state.test.ts",
|
|
34
|
+
"test:integration:gap": "tsx scripts/run-integration.ts --testnet test/integration/user/trader-wlp-redeem.test.ts test/integration/user/trader-staking.test.ts test/integration/user/trader-keeper-roundtrip.test.ts test/integration/user/trader-custody.test.ts test/integration/user/trader-credit-pipeline.test.ts",
|
|
35
|
+
"test:integration:onboarding": "tsx scripts/run-integration.ts --testnet test/integration/user/trader-full-onboarding.test.ts",
|
|
36
|
+
"smoke:chain": "tsx scripts/run-smoke-chain.ts",
|
|
37
|
+
"smoke:chain:dry": "tsx scripts/run-smoke-chain.ts --dry-run"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@mysten/bcs": "^1.9.0",
|
|
41
|
+
"@mysten/sui": "^2.9.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@eslint/js": "^9.39.4",
|
|
45
|
+
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
|
|
46
|
+
"@mysten/bcs": "^1.9.2",
|
|
47
|
+
"@mysten/sui": "^2.9.1",
|
|
48
|
+
"@mysten/codegen": "^0.8.3",
|
|
49
|
+
"@types/node": "^24.12.0",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^8.58.0",
|
|
51
|
+
"@typescript-eslint/parser": "^8.58.0",
|
|
52
|
+
"eslint": "^9.39.4",
|
|
53
|
+
"eslint-config-prettier": "^10.1.8",
|
|
54
|
+
"globals": "^17.4.0",
|
|
55
|
+
"prettier": "^3.8.1",
|
|
56
|
+
"tsc-alias": "^1.8.16",
|
|
57
|
+
"tsconfig-paths": "^4.2.0",
|
|
58
|
+
"tsx": "^4.21.0",
|
|
59
|
+
"typescript": "^5.9.3",
|
|
60
|
+
"typescript-eslint": "^8.58.0",
|
|
61
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
62
|
+
"dotenv": "^17.4.0",
|
|
63
|
+
"vitest": "^3.2.4"
|
|
64
|
+
},
|
|
65
|
+
"packageManager": "pnpm@9.15.0"
|
|
66
|
+
}
|