@oddmaki-protocol/sdk 1.8.0 → 1.10.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/dist/index.mjs CHANGED
@@ -8,7 +8,7 @@ var CONTRACT_ADDRESSES = {
8
8
  [baseSepolia.id]: {
9
9
  diamond: "0x31a4126aec35b36d46dd371eb0f0d5b71e1c2292",
10
10
  conditionalTokens: "0x7364747372Ac4a175B5326f5B2C9CB1C271d32e8",
11
- usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
11
+ usdc: "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
12
12
  subgraph: "https://api.studio.thegraph.com/query/1716020/oddmaki-base-sepolia/version/latest"
13
13
  },
14
14
  [base.id]: {
@@ -5213,6 +5213,11 @@ var PythResolutionFacet_default = [
5213
5213
  type: "int64",
5214
5214
  internalType: "int64"
5215
5215
  },
5216
+ {
5217
+ name: "openTime",
5218
+ type: "uint256",
5219
+ internalType: "uint256"
5220
+ },
5216
5221
  {
5217
5222
  name: "closeTime",
5218
5223
  type: "uint256",
@@ -5252,11 +5257,6 @@ var PythResolutionFacet_default = [
5252
5257
  name: "resolutionWindow",
5253
5258
  type: "uint256",
5254
5259
  internalType: "uint256"
5255
- },
5256
- {
5257
- name: "pythUpdateData",
5258
- type: "bytes[]",
5259
- internalType: "bytes[]"
5260
5260
  }
5261
5261
  ],
5262
5262
  outputs: [
@@ -5266,20 +5266,7 @@ var PythResolutionFacet_default = [
5266
5266
  internalType: "uint256"
5267
5267
  }
5268
5268
  ],
5269
- stateMutability: "payable"
5270
- },
5271
- {
5272
- type: "function",
5273
- name: "getOpenMaxStaleness",
5274
- inputs: [],
5275
- outputs: [
5276
- {
5277
- name: "",
5278
- type: "uint256",
5279
- internalType: "uint256"
5280
- }
5281
- ],
5282
- stateMutability: "view"
5269
+ stateMutability: "nonpayable"
5283
5270
  },
5284
5271
  {
5285
5272
  type: "function",
@@ -5296,34 +5283,34 @@ var PythResolutionFacet_default = [
5296
5283
  },
5297
5284
  {
5298
5285
  type: "function",
5299
- name: "resolvePriceMarketPyth",
5286
+ name: "markPriceMarketInvalid",
5300
5287
  inputs: [
5301
5288
  {
5302
5289
  name: "marketId",
5303
5290
  type: "uint256",
5304
5291
  internalType: "uint256"
5305
- },
5306
- {
5307
- name: "pythUpdateData",
5308
- type: "bytes[]",
5309
- internalType: "bytes[]"
5310
5292
  }
5311
5293
  ],
5312
5294
  outputs: [],
5313
- stateMutability: "payable"
5295
+ stateMutability: "nonpayable"
5314
5296
  },
5315
5297
  {
5316
5298
  type: "function",
5317
- name: "setOpenMaxStaleness",
5299
+ name: "resolvePriceMarketPyth",
5318
5300
  inputs: [
5319
5301
  {
5320
- name: "openMaxStaleness",
5302
+ name: "marketId",
5321
5303
  type: "uint256",
5322
5304
  internalType: "uint256"
5305
+ },
5306
+ {
5307
+ name: "pythUpdateData",
5308
+ type: "bytes[]",
5309
+ internalType: "bytes[]"
5323
5310
  }
5324
5311
  ],
5325
5312
  outputs: [],
5326
- stateMutability: "nonpayable"
5313
+ stateMutability: "payable"
5327
5314
  },
5328
5315
  {
5329
5316
  type: "function",
@@ -5461,19 +5448,6 @@ var PythResolutionFacet_default = [
5461
5448
  ],
5462
5449
  anonymous: false
5463
5450
  },
5464
- {
5465
- type: "event",
5466
- name: "OpenMaxStalenessUpdated",
5467
- inputs: [
5468
- {
5469
- name: "openMaxStaleness",
5470
- type: "uint256",
5471
- indexed: false,
5472
- internalType: "uint256"
5473
- }
5474
- ],
5475
- anonymous: false
5476
- },
5477
5451
  {
5478
5452
  type: "event",
5479
5453
  name: "PriceMarketCreatedPyth",
@@ -5529,6 +5503,25 @@ var PythResolutionFacet_default = [
5529
5503
  ],
5530
5504
  anonymous: false
5531
5505
  },
5506
+ {
5507
+ type: "event",
5508
+ name: "PriceMarketInvalidated",
5509
+ inputs: [
5510
+ {
5511
+ name: "marketId",
5512
+ type: "uint256",
5513
+ indexed: true,
5514
+ internalType: "uint256"
5515
+ },
5516
+ {
5517
+ name: "caller",
5518
+ type: "address",
5519
+ indexed: true,
5520
+ internalType: "address"
5521
+ }
5522
+ ],
5523
+ anonymous: false
5524
+ },
5532
5525
  {
5533
5526
  type: "event",
5534
5527
  name: "PriceMarketResolvedPyth",
@@ -5551,6 +5544,12 @@ var PythResolutionFacet_default = [
5551
5544
  indexed: false,
5552
5545
  internalType: "int64"
5553
5546
  },
5547
+ {
5548
+ name: "openPriceTime",
5549
+ type: "uint256",
5550
+ indexed: false,
5551
+ internalType: "uint256"
5552
+ },
5554
5553
  {
5555
5554
  name: "outcome",
5556
5555
  type: "string",
@@ -5594,17 +5593,17 @@ var PythResolutionFacet_default = [
5594
5593
  },
5595
5594
  {
5596
5595
  type: "error",
5597
- name: "CloseTimeNotReached",
5596
+ name: "AssertionInProgress",
5598
5597
  inputs: []
5599
5598
  },
5600
5599
  {
5601
5600
  type: "error",
5602
- name: "CloseTimeTooFar",
5601
+ name: "CloseTimeNotAfterOpenTime",
5603
5602
  inputs: []
5604
5603
  },
5605
5604
  {
5606
5605
  type: "error",
5607
- name: "CloseTimeTooSoon",
5606
+ name: "CloseTimeNotReached",
5608
5607
  inputs: []
5609
5608
  },
5610
5609
  {
@@ -5638,6 +5637,11 @@ var PythResolutionFacet_default = [
5638
5637
  }
5639
5638
  ]
5640
5639
  },
5640
+ {
5641
+ type: "error",
5642
+ name: "GracePeriodNotElapsed",
5643
+ inputs: []
5644
+ },
5641
5645
  {
5642
5646
  type: "error",
5643
5647
  name: "InsufficientPythFee",
@@ -5663,6 +5667,11 @@ var PythResolutionFacet_default = [
5663
5667
  name: "InvalidMarketId",
5664
5668
  inputs: []
5665
5669
  },
5670
+ {
5671
+ type: "error",
5672
+ name: "InvalidOpenTime",
5673
+ inputs: []
5674
+ },
5666
5675
  {
5667
5676
  type: "error",
5668
5677
  name: "InvalidOutcomesLength",
@@ -5680,7 +5689,12 @@ var PythResolutionFacet_default = [
5680
5689
  },
5681
5690
  {
5682
5691
  type: "error",
5683
- name: "NoValidPriceUpdate",
5692
+ name: "NoClosePriceInWindow",
5693
+ inputs: []
5694
+ },
5695
+ {
5696
+ type: "error",
5697
+ name: "NoOpenPriceInWindow",
5684
5698
  inputs: []
5685
5699
  },
5686
5700
  {
@@ -6707,6 +6721,24 @@ var ERC20_default = [
6707
6721
  ],
6708
6722
  stateMutability: "view"
6709
6723
  },
6724
+ {
6725
+ type: "function",
6726
+ name: "mint",
6727
+ inputs: [
6728
+ {
6729
+ name: "to",
6730
+ type: "address",
6731
+ internalType: "address"
6732
+ },
6733
+ {
6734
+ name: "amount",
6735
+ type: "uint256",
6736
+ internalType: "uint256"
6737
+ }
6738
+ ],
6739
+ outputs: [],
6740
+ stateMutability: "nonpayable"
6741
+ },
6710
6742
  {
6711
6743
  type: "function",
6712
6744
  name: "name",
@@ -6925,6 +6957,24 @@ var UmaOracle_default = [
6925
6957
  ],
6926
6958
  stateMutability: "view"
6927
6959
  },
6960
+ {
6961
+ type: "function",
6962
+ name: "disputeAssertion",
6963
+ inputs: [
6964
+ {
6965
+ name: "assertionId",
6966
+ type: "bytes32",
6967
+ internalType: "bytes32"
6968
+ },
6969
+ {
6970
+ name: "disputer",
6971
+ type: "address",
6972
+ internalType: "address"
6973
+ }
6974
+ ],
6975
+ outputs: [],
6976
+ stateMutability: "nonpayable"
6977
+ },
6928
6978
  {
6929
6979
  type: "function",
6930
6980
  name: "getAssertion",
@@ -7102,24 +7152,6 @@ var UmaOracle_default = [
7102
7152
  ],
7103
7153
  outputs: [],
7104
7154
  stateMutability: "nonpayable"
7105
- },
7106
- {
7107
- type: "function",
7108
- name: "disputeAssertion",
7109
- inputs: [
7110
- {
7111
- name: "assertionId",
7112
- type: "bytes32",
7113
- internalType: "bytes32"
7114
- },
7115
- {
7116
- name: "disputer",
7117
- type: "address",
7118
- internalType: "address"
7119
- }
7120
- ],
7121
- outputs: [],
7122
- stateMutability: "nonpayable"
7123
7155
  }
7124
7156
  ];
7125
7157
 
@@ -9914,7 +9946,7 @@ var GET_PRICE_MARKET_SERIES = gql`
9914
9946
  venueId
9915
9947
  name
9916
9948
  }
9917
- markets {
9949
+ markets(first: 1000, orderBy: marketId, orderDirection: asc) {
9918
9950
  id
9919
9951
  marketId
9920
9952
  question
@@ -11457,41 +11489,45 @@ var DEFAULT_FRESH_MAX_AGE_SECONDS = 120;
11457
11489
  var DEFAULT_FRESH_MAX_ATTEMPTS = 3;
11458
11490
  var PriceMarketModule = class extends BaseModule {
11459
11491
  /**
11460
- * Create a Pyth-powered price market
11492
+ * Create a Pyth-powered price market. Three shapes via one entry point:
11461
11493
  *
11462
- * When strikePrice > 0, creates a strike market resolved against
11463
- * the explicit target price. No Pyth update data or ETH is needed.
11464
- * When strikePrice is 0 or omitted, captures the current Pyth price
11465
- * and uses it as the reference (standard Up/Down market).
11494
+ * - **Immediate Up/Down** (`strikePrice = 0`, `openTime = 0` or omitted):
11495
+ * `openTime` is set to `block.timestamp` at creation; open price is
11496
+ * captured at resolution from the earliest Hermes VAA in
11497
+ * `[openTime, openTime + resolutionWindow]`.
11498
+ *
11499
+ * - **Scheduled Up/Down** (`strikePrice = 0`, `openTime > now`):
11500
+ * Market exists from creation and is tradable. At `openTime` the open
11501
+ * price window begins; capture happens at resolution as above.
11502
+ *
11503
+ * - **Explicit strike** (`strikePrice > 0`): Above/Below market resolved
11504
+ * against the caller-supplied target. `openTime` is ignored and stored
11505
+ * as `block.timestamp`; no open-price capture occurs at resolution.
11506
+ *
11507
+ * Creation never touches Pyth — no VAA submission, no Pyth fee. The
11508
+ * resolver pays Pyth fees later when calling {@link resolvePyth}.
11466
11509
  *
11467
11510
  * @param params.venueId - The venue to create the market in
11468
11511
  * @param params.pythFeedId - Pyth price feed ID (e.g., ETH/USD)
11469
- * @param params.strikePrice - Target price in feed's exponent scale (0 = use current price)
11470
- * @param params.closeTime - Absolute close timestamp (must be 300–86400s from now)
11512
+ * @param params.strikePrice - Target price (0 = capture open at resolution)
11513
+ * @param params.openTime - When the market opens (0 = immediate / now)
11514
+ * @param params.closeTime - Absolute close timestamp (must be > effective openTime)
11471
11515
  * @param params.outcomes - Market outcome labels (e.g., ["Up", "Down"] or ["Above", "Below"])
11472
- * @param params.tickSize - Price increment for the orderbook
11516
+ * @param params.tickSize - Price increment for the orderbook (1e15 or 1e16)
11473
11517
  * @param params.collateralToken - ERC20 collateral token address
11474
- * @param params.question - Market question for UMA ancillary data (fallback)
11518
+ * @param params.question - Market question for ancillary data
11475
11519
  * @param params.liveness - UMA challenge period in seconds (0 = default)
11476
11520
  * @param params.tags - Optional tags for the market
11477
- * @param params.resolutionWindow - Pyth timestamp tolerance in seconds (0 = default 60s)
11521
+ * @param params.resolutionWindow - Pyth timestamp tolerance in seconds (0 = default 60s, max 300s)
11478
11522
  */
11479
11523
  async createPyth(params) {
11480
11524
  const wallet = this.walletClient;
11481
11525
  const account = await this.getSignerAccount();
11482
11526
  const outcomes = params.outcomes ?? ["Up", "Down"];
11483
- const isStrikeMarket = params.strikePrice && params.strikePrice > BigInt(0);
11484
11527
  const { encodedTags, ancillaryData } = await this._prepareCreationCommon(params, await this.getSignerAddress());
11485
11528
  if (!isValidTickSize(params.tickSize)) {
11486
11529
  throw new Error("Invalid tickSize: must be 1e15 (0.1%) or 1e16 (1%)");
11487
11530
  }
11488
- let pythUpdateData = [];
11489
- let valueSent = BigInt(0);
11490
- if (!isStrikeMarket) {
11491
- const pythResult = await this._preparePythUpdate(params.pythFeedId);
11492
- pythUpdateData = pythResult.pythUpdateData;
11493
- valueSent = pythResult.valueSent;
11494
- }
11495
11531
  const { request } = await this.publicClient.simulateContract({
11496
11532
  address: this.config.diamondAddress,
11497
11533
  abi: PythResolutionFacet_default,
@@ -11500,6 +11536,7 @@ var PriceMarketModule = class extends BaseModule {
11500
11536
  params.venueId,
11501
11537
  params.pythFeedId,
11502
11538
  params.strikePrice ?? BigInt(0),
11539
+ params.openTime ?? BigInt(0),
11503
11540
  params.closeTime,
11504
11541
  outcomes,
11505
11542
  params.tickSize,
@@ -11507,20 +11544,21 @@ var PriceMarketModule = class extends BaseModule {
11507
11544
  ancillaryData,
11508
11545
  params.liveness ?? BigInt(0),
11509
11546
  encodedTags,
11510
- params.resolutionWindow ?? BigInt(0),
11511
- pythUpdateData
11547
+ params.resolutionWindow ?? BigInt(0)
11512
11548
  ],
11513
- account,
11514
- value: valueSent
11549
+ account
11515
11550
  });
11516
11551
  return wallet.writeContract(request);
11517
11552
  }
11518
11553
  /**
11519
- * Resolve a price market using Pyth closing price
11554
+ * Resolve a price market using Pyth.
11520
11555
  *
11521
- * Anyone can call this after the market's closeTime has passed.
11522
- * Fetches the historical Pyth price at closeTime and submits it.
11523
- * Works for both standard price markets and strike markets.
11556
+ * Anyone can call this after `closeTime`. For deferred Up/Down markets
11557
+ * (`strikePrice == 0`), the SDK fetches **two** Hermes VAAs one for the
11558
+ * open window, one for the close window and submits them together so the
11559
+ * on-chain facet can capture the open price and compare against the close
11560
+ * in a single transaction. For explicit-strike markets only the close VAA
11561
+ * is fetched.
11524
11562
  */
11525
11563
  async resolvePyth(marketId) {
11526
11564
  const wallet = this.walletClient;
@@ -11535,10 +11573,21 @@ var PriceMarketModule = class extends BaseModule {
11535
11573
  `Close time not reached. Current: ${now.toString()}, CloseTime: ${pm.closeTime.toString()}`
11536
11574
  );
11537
11575
  }
11538
- const pythUpdateData = await this.fetchPythHistoricalData(
11576
+ const isDeferred = pm.strikePrice === BigInt(0);
11577
+ const closeVAA = await this.fetchPythHistoricalData(
11539
11578
  pm.feedId,
11540
11579
  Number(pm.closeTime)
11541
11580
  );
11581
+ let pythUpdateData;
11582
+ if (isDeferred) {
11583
+ const openVAA = await this.fetchPythHistoricalData(
11584
+ pm.feedId,
11585
+ Number(pm.openTime)
11586
+ );
11587
+ pythUpdateData = [...openVAA, ...closeVAA];
11588
+ } else {
11589
+ pythUpdateData = closeVAA;
11590
+ }
11542
11591
  const pythAddress = await this.getPythContract();
11543
11592
  const pythFee = await this.publicClient.readContract({
11544
11593
  address: pythAddress,
@@ -11569,7 +11618,7 @@ var PriceMarketModule = class extends BaseModule {
11569
11618
  });
11570
11619
  }
11571
11620
  /**
11572
- * Check if a price market can be resolved
11621
+ * Check if a price market can be resolved (closeTime reached and not yet resolved).
11573
11622
  */
11574
11623
  async canResolve(marketId) {
11575
11624
  return await this.publicClient.readContract({
@@ -11627,36 +11676,6 @@ var PriceMarketModule = class extends BaseModule {
11627
11676
  });
11628
11677
  return wallet.writeContract(request);
11629
11678
  }
11630
- /**
11631
- * Get the effective opening-price staleness window in seconds.
11632
- *
11633
- * A submitted VAA's `publishTime` must fall within
11634
- * `[block.timestamp - openMaxStaleness, block.timestamp + OPEN_FUTURE_SKEW]`
11635
- * at `createPriceMarketPyth` time. Defaults to 300s when unset on-chain.
11636
- */
11637
- async getOpenMaxStaleness() {
11638
- return await this.publicClient.readContract({
11639
- address: this.config.diamondAddress,
11640
- abi: PythResolutionFacet_default,
11641
- functionName: "getOpenMaxStaleness"
11642
- });
11643
- }
11644
- /**
11645
- * Set the opening-price staleness window (seconds). Diamond owner only.
11646
- * Pass 0 to fall back to the built-in default.
11647
- */
11648
- async setOpenMaxStaleness(openMaxStaleness) {
11649
- const wallet = this.walletClient;
11650
- const account = await this.getSignerAccount();
11651
- const { request } = await this.publicClient.simulateContract({
11652
- address: this.config.diamondAddress,
11653
- abi: PythResolutionFacet_default,
11654
- functionName: "setOpenMaxStaleness",
11655
- args: [openMaxStaleness],
11656
- account
11657
- });
11658
- return wallet.writeContract(request);
11659
- }
11660
11679
  // ---- Private helpers ----
11661
11680
  /**
11662
11681
  * Shared pre-flight checks: creation fee allowance, ancillary data, tags
@@ -11688,33 +11707,18 @@ var PriceMarketModule = class extends BaseModule {
11688
11707
  );
11689
11708
  return { encodedTags, ancillaryData };
11690
11709
  }
11691
- /**
11692
- * Fetch Pyth update data and compute the required ETH value (for Up/Down markets)
11693
- */
11694
- async _preparePythUpdate(feedId) {
11695
- const pythUpdateData = await this.fetchPythUpdateData(feedId);
11696
- const pythAddress = await this.getPythContract();
11697
- const pythFee = await this.publicClient.readContract({
11698
- address: pythAddress,
11699
- abi: PYTH_GET_UPDATE_FEE_ABI,
11700
- functionName: "getUpdateFee",
11701
- args: [pythUpdateData]
11702
- });
11703
- const valueSent = pythFee > BigInt(0) ? pythFee : BigInt(1e9);
11704
- return { pythUpdateData, valueSent };
11705
- }
11706
11710
  formatAncillaryData(question) {
11707
11711
  let data = `q:title:${question.title}`;
11708
11712
  data += `,description:${question.description}`;
11709
11713
  return stringToHex(data);
11710
11714
  }
11711
11715
  /**
11712
- * Fetch latest Pyth price update from Hermes — bytes plus the VAA's publishTime.
11716
+ * Fetch the latest Pyth price update from Hermes — the bytes plus the
11717
+ * VAA's publishTime.
11713
11718
  *
11714
- * Use this (or {@link fetchFreshPythUpdate}) instead of re-rolling Hermes
11715
- * calls when building `createPriceMarketPyth` transactions. The on-chain
11716
- * staleness window defaults to 300s — if the user takes longer than that
11717
- * to sign, the VAA will be rejected.
11719
+ * Useful for UI projected-strike display: callers can render the current
11720
+ * Pyth price (and its publishTime) before a deferred market's open window
11721
+ * closes, then switch to the on-chain strike once resolution happens.
11718
11722
  */
11719
11723
  async fetchPythLatestUpdate(feedId) {
11720
11724
  const url = `${PYTH_HERMES_BASE}/v2/updates/price/latest?ids[]=${feedId}`;
@@ -11741,14 +11745,14 @@ var PriceMarketModule = class extends BaseModule {
11741
11745
  };
11742
11746
  }
11743
11747
  /**
11744
- * Return a Pyth update that is fresh enough to pass the on-chain staleness check.
11748
+ * Return a Pyth update that is fresh enough for client-side projected-strike
11749
+ * display.
11745
11750
  *
11746
- * If `cached` is provided and still within `maxAgeSeconds`, it is returned as-is.
11747
- * Otherwise Hermes is re-queried; if the newly fetched VAA is also older than
11748
- * `maxAgeSeconds` (e.g. feed is quiet), it retries up to `maxAttempts`.
11751
+ * If `cached` is provided and still within `maxAgeSeconds`, returns it
11752
+ * unchanged. Otherwise re-queries Hermes; if the new VAA is also stale
11753
+ * (quiet feed), retries up to `maxAttempts`.
11749
11754
  *
11750
- * Defaults: `maxAgeSeconds = 120` (leaves ~180s headroom under the 300s default
11751
- * on-chain window), `maxAttempts = 3`.
11755
+ * Defaults: `maxAgeSeconds = 120`, `maxAttempts = 3`.
11752
11756
  */
11753
11757
  async fetchFreshPythUpdate(feedId, options = {}) {
11754
11758
  const maxAgeSeconds = options.maxAgeSeconds ?? DEFAULT_FRESH_MAX_AGE_SECONDS;
@@ -11771,14 +11775,55 @@ var PriceMarketModule = class extends BaseModule {
11771
11775
  return latest;
11772
11776
  }
11773
11777
  /**
11774
- * Fetch latest Pyth price update data from Hermes API (raw bytes only).
11778
+ * Resolve the projected open price for a deferred Up/Down market what the
11779
+ * UI should display as the "strike" between `openTime` and resolution.
11780
+ *
11781
+ * For deferred markets, the on-chain `strikePrice` is 0 until the resolver
11782
+ * fires {@link resolvePyth}. This helper replicates the same rule the
11783
+ * contract will use ("earliest in-window Hermes VAA in
11784
+ * `[openTime, openTime + resolutionWindow]`") by querying Hermes at
11785
+ * `openTime`, so the value rendered in the UI matches what the on-chain
11786
+ * strike will be once resolution lands.
11787
+ *
11788
+ * Returns `null` when the projection isn't applicable:
11789
+ * - The market is already resolved — caller should read `pm.strikePrice` directly.
11790
+ * - The market is explicit-strike (`pm.strikePrice > 0`) — same, read from chain.
11791
+ * - The market hasn't reached `openTime` yet — no VAA exists; UI should show
11792
+ * "strike set at HH:MM" or similar pending state.
11793
+ *
11794
+ * `result.canonical === true` means the open window has fully elapsed and the
11795
+ * value will not change. Before that, render the price with a "pending" hint.
11775
11796
  */
11776
- async fetchPythUpdateData(feedId) {
11777
- const { updateData } = await this.fetchPythLatestUpdate(feedId);
11778
- return updateData;
11797
+ async fetchProjectedOpenPrice(marketId) {
11798
+ const pm = await this.get(marketId);
11799
+ if (pm.resolved) return null;
11800
+ if (pm.strikePrice !== BigInt(0)) return null;
11801
+ const now = BigInt(Math.floor(Date.now() / 1e3));
11802
+ if (now < pm.openTime) return null;
11803
+ const url = `${PYTH_HERMES_BASE}/v2/updates/price/${pm.openTime}?ids[]=${pm.feedId}`;
11804
+ const response = await fetch(url);
11805
+ if (!response.ok) {
11806
+ throw new Error(
11807
+ `Pyth Hermes API error: ${response.status} ${response.statusText}`
11808
+ );
11809
+ }
11810
+ const data = await response.json();
11811
+ const parsed = data.parsed?.[0];
11812
+ if (!parsed?.price) {
11813
+ throw new Error("Pyth Hermes response missing parsed.price for open VAA");
11814
+ }
11815
+ return {
11816
+ price: BigInt(parsed.price.price),
11817
+ publishTime: BigInt(parsed.price.publish_time),
11818
+ expo: Number(parsed.price.expo),
11819
+ canonical: now >= pm.openTime + pm.resolutionWindow,
11820
+ openTime: pm.openTime
11821
+ };
11779
11822
  }
11780
11823
  /**
11781
- * Fetch historical Pyth price update data at a specific timestamp
11824
+ * Fetch historical Pyth price update data at a specific timestamp.
11825
+ * Used internally by {@link resolvePyth} to fetch open-window and
11826
+ * close-window VAAs.
11782
11827
  */
11783
11828
  async fetchPythHistoricalData(feedId, publishTime) {
11784
11829
  const url = `${PYTH_HERMES_BASE}/v2/updates/price/${publishTime}?ids[]=${feedId}`;