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