@gearbox-protocol/sdk 8.1.6 → 8.2.0-next.1

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.
Files changed (67) hide show
  1. package/dist/cjs/plugins/degen-distributors/DegenDistributorsPlugin.js +3 -1
  2. package/dist/cjs/plugins/pools-history/Pools7DAgoPlugin.js +6 -1
  3. package/dist/cjs/sdk/GearboxSDK.js +20 -8
  4. package/dist/cjs/sdk/abi/oracles.js +141 -0
  5. package/dist/cjs/sdk/chain/chains.js +4 -4
  6. package/dist/cjs/sdk/market/pricefeeds/AbstractPriceFeed.js +2 -1
  7. package/dist/cjs/sdk/market/pricefeeds/PriceFeedsRegister.js +17 -23
  8. package/dist/cjs/sdk/market/pricefeeds/PythPriceFeed.js +9 -1
  9. package/dist/cjs/sdk/market/pricefeeds/RedstonePriceFeed.js +9 -7
  10. package/dist/cjs/sdk/market/pricefeeds/index.js +2 -0
  11. package/dist/cjs/sdk/market/pricefeeds/isUpdatablePriceFeed.js +30 -0
  12. package/dist/cjs/sdk/market/pricefeeds/updates/PriceUpdateTx.js +52 -0
  13. package/dist/cjs/sdk/market/pricefeeds/{RedstoneCache.js → updates/PriceUpdatesCache.js} +12 -12
  14. package/dist/cjs/sdk/market/pricefeeds/updates/PythUpdater.js +189 -0
  15. package/dist/cjs/sdk/market/pricefeeds/{RedstoneUpdater.js → updates/RedstoneUpdater.js} +21 -45
  16. package/dist/cjs/sdk/market/pricefeeds/updates/index.js +31 -0
  17. package/dist/cjs/sdk/market/pricefeeds/updates/types.js +16 -0
  18. package/dist/cjs/sdk/sdk-legacy/tokens/tokenData.js +1 -2
  19. package/dist/cjs/sdk/utils/retry.js +4 -2
  20. package/dist/cjs/sdk/utils/viem/cast.js +5 -2
  21. package/dist/cjs/sdk/utils/viem/simulateMulticall.js +3 -1
  22. package/dist/cjs/sdk/utils/viem/simulateWithPriceUpdates.js +2 -2
  23. package/dist/esm/plugins/degen-distributors/DegenDistributorsPlugin.js +3 -1
  24. package/dist/esm/plugins/pools-history/Pools7DAgoPlugin.js +6 -1
  25. package/dist/esm/sdk/GearboxSDK.js +20 -8
  26. package/dist/esm/sdk/abi/oracles.js +140 -0
  27. package/dist/esm/sdk/chain/chains.js +4 -4
  28. package/dist/esm/sdk/market/pricefeeds/AbstractPriceFeed.js +2 -1
  29. package/dist/esm/sdk/market/pricefeeds/PriceFeedsRegister.js +18 -24
  30. package/dist/esm/sdk/market/pricefeeds/PythPriceFeed.js +9 -1
  31. package/dist/esm/sdk/market/pricefeeds/RedstonePriceFeed.js +8 -5
  32. package/dist/esm/sdk/market/pricefeeds/index.js +1 -0
  33. package/dist/esm/sdk/market/pricefeeds/isUpdatablePriceFeed.js +6 -0
  34. package/dist/esm/sdk/market/pricefeeds/updates/PriceUpdateTx.js +28 -0
  35. package/dist/esm/sdk/market/pricefeeds/{RedstoneCache.js → updates/PriceUpdatesCache.js} +8 -8
  36. package/dist/esm/sdk/market/pricefeeds/updates/PythUpdater.js +169 -0
  37. package/dist/esm/sdk/market/pricefeeds/{RedstoneUpdater.js → updates/RedstoneUpdater.js} +21 -44
  38. package/dist/esm/sdk/market/pricefeeds/updates/index.js +6 -0
  39. package/dist/esm/sdk/market/pricefeeds/updates/types.js +0 -0
  40. package/dist/esm/sdk/sdk-legacy/tokens/tokenData.js +1 -2
  41. package/dist/esm/sdk/utils/retry.js +4 -2
  42. package/dist/esm/sdk/utils/viem/cast.js +5 -2
  43. package/dist/esm/sdk/utils/viem/simulateMulticall.js +3 -1
  44. package/dist/esm/sdk/utils/viem/simulateWithPriceUpdates.js +2 -2
  45. package/dist/types/sdk/GearboxSDK.d.ts +5 -1
  46. package/dist/types/sdk/abi/oracles.d.ts +212 -0
  47. package/dist/types/sdk/chain/chains.d.ts +1 -1
  48. package/dist/types/sdk/market/pricefeeds/AbstractPriceFeed.d.ts +2 -2
  49. package/dist/types/sdk/market/pricefeeds/PriceFeedsRegister.d.ts +4 -4
  50. package/dist/types/sdk/market/pricefeeds/PythPriceFeed.d.ts +218 -4
  51. package/dist/types/sdk/market/pricefeeds/RedstonePriceFeed.d.ts +4 -4
  52. package/dist/types/sdk/market/pricefeeds/index.d.ts +1 -0
  53. package/dist/types/sdk/market/pricefeeds/isUpdatablePriceFeed.d.ts +2 -0
  54. package/dist/types/sdk/market/pricefeeds/types.d.ts +5 -2
  55. package/dist/types/sdk/market/pricefeeds/updates/PriceUpdateTx.d.ts +10 -0
  56. package/dist/types/sdk/market/pricefeeds/updates/PriceUpdatesCache.d.ts +17 -0
  57. package/dist/types/sdk/market/pricefeeds/updates/PythUpdater.d.ts +40 -0
  58. package/dist/types/sdk/market/pricefeeds/{RedstoneUpdater.d.ts → updates/RedstoneUpdater.d.ts} +11 -18
  59. package/dist/types/sdk/market/pricefeeds/updates/RedstoneUpdater.test.d.ts +1 -0
  60. package/dist/types/sdk/market/pricefeeds/updates/index.d.ts +3 -0
  61. package/dist/types/sdk/market/pricefeeds/updates/types.d.ts +21 -0
  62. package/dist/types/sdk/utils/retry.d.ts +1 -0
  63. package/dist/types/sdk/utils/viem/cast.d.ts +1 -1
  64. package/dist/types/sdk/utils/viem/simulateMulticall.d.ts +1 -1
  65. package/dist/types/sdk/utils/viem/simulateWithPriceUpdates.d.ts +1 -1
  66. package/package.json +4 -1
  67. package/dist/types/sdk/market/pricefeeds/RedstoneCache.d.ts +0 -25
@@ -4497,6 +4497,145 @@ const pendleTWAPPTPriceFeedAbi = [
4497
4497
  inputs: []
4498
4498
  }
4499
4499
  ];
4500
+ const pythPriceFeedAbi = [
4501
+ {
4502
+ type: "constructor",
4503
+ inputs: [
4504
+ { name: "_token", type: "address", internalType: "address" },
4505
+ { name: "_priceFeedId", type: "bytes32", internalType: "bytes32" },
4506
+ { name: "_pyth", type: "address", internalType: "address" },
4507
+ {
4508
+ name: "_maxConfToPriceRatio",
4509
+ type: "uint256",
4510
+ internalType: "uint256"
4511
+ },
4512
+ { name: "descriptionTicker", type: "string", internalType: "string" }
4513
+ ],
4514
+ stateMutability: "nonpayable"
4515
+ },
4516
+ { type: "receive", stateMutability: "payable" },
4517
+ {
4518
+ type: "function",
4519
+ name: "contractType",
4520
+ inputs: [],
4521
+ outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }],
4522
+ stateMutability: "view"
4523
+ },
4524
+ {
4525
+ type: "function",
4526
+ name: "decimals",
4527
+ inputs: [],
4528
+ outputs: [{ name: "", type: "uint8", internalType: "uint8" }],
4529
+ stateMutability: "view"
4530
+ },
4531
+ {
4532
+ type: "function",
4533
+ name: "description",
4534
+ inputs: [],
4535
+ outputs: [{ name: "", type: "string", internalType: "string" }],
4536
+ stateMutability: "view"
4537
+ },
4538
+ {
4539
+ type: "function",
4540
+ name: "latestRoundData",
4541
+ inputs: [],
4542
+ outputs: [
4543
+ { name: "", type: "uint80", internalType: "uint80" },
4544
+ { name: "", type: "int256", internalType: "int256" },
4545
+ { name: "", type: "uint256", internalType: "uint256" },
4546
+ { name: "", type: "uint256", internalType: "uint256" },
4547
+ { name: "", type: "uint80", internalType: "uint80" }
4548
+ ],
4549
+ stateMutability: "view"
4550
+ },
4551
+ {
4552
+ type: "function",
4553
+ name: "maxConfToPriceRatio",
4554
+ inputs: [],
4555
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
4556
+ stateMutability: "view"
4557
+ },
4558
+ {
4559
+ type: "function",
4560
+ name: "priceFeedId",
4561
+ inputs: [],
4562
+ outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }],
4563
+ stateMutability: "view"
4564
+ },
4565
+ {
4566
+ type: "function",
4567
+ name: "pyth",
4568
+ inputs: [],
4569
+ outputs: [{ name: "", type: "address", internalType: "address" }],
4570
+ stateMutability: "view"
4571
+ },
4572
+ {
4573
+ type: "function",
4574
+ name: "serialize",
4575
+ inputs: [],
4576
+ outputs: [{ name: "", type: "bytes", internalType: "bytes" }],
4577
+ stateMutability: "view"
4578
+ },
4579
+ {
4580
+ type: "function",
4581
+ name: "skipPriceCheck",
4582
+ inputs: [],
4583
+ outputs: [{ name: "", type: "bool", internalType: "bool" }],
4584
+ stateMutability: "view"
4585
+ },
4586
+ {
4587
+ type: "function",
4588
+ name: "token",
4589
+ inputs: [],
4590
+ outputs: [{ name: "", type: "address", internalType: "address" }],
4591
+ stateMutability: "view"
4592
+ },
4593
+ {
4594
+ type: "function",
4595
+ name: "updatable",
4596
+ inputs: [],
4597
+ outputs: [{ name: "", type: "bool", internalType: "bool" }],
4598
+ stateMutability: "view"
4599
+ },
4600
+ {
4601
+ type: "function",
4602
+ name: "updatePrice",
4603
+ inputs: [{ name: "data", type: "bytes", internalType: "bytes" }],
4604
+ outputs: [],
4605
+ stateMutability: "nonpayable"
4606
+ },
4607
+ {
4608
+ type: "function",
4609
+ name: "version",
4610
+ inputs: [],
4611
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
4612
+ stateMutability: "view"
4613
+ },
4614
+ {
4615
+ type: "event",
4616
+ name: "UpdatePrice",
4617
+ inputs: [
4618
+ {
4619
+ name: "price",
4620
+ type: "uint256",
4621
+ indexed: false,
4622
+ internalType: "uint256"
4623
+ }
4624
+ ],
4625
+ anonymous: false
4626
+ },
4627
+ { type: "error", name: "ConfToPriceRatioTooHighException", inputs: [] },
4628
+ {
4629
+ type: "error",
4630
+ name: "IncorrectExpectedPublishTimestampException",
4631
+ inputs: []
4632
+ },
4633
+ { type: "error", name: "IncorrectParameterException", inputs: [] },
4634
+ { type: "error", name: "IncorrectPriceDecimalsException", inputs: [] },
4635
+ { type: "error", name: "IncorrectPriceException", inputs: [] },
4636
+ { type: "error", name: "PriceTimestampTooFarAheadException", inputs: [] },
4637
+ { type: "error", name: "PriceTimestampTooFarBehindException", inputs: [] }
4638
+ ];
4500
4639
  export {
4501
4640
  boundedPriceFeedAbi,
4502
4641
  bptStablePriceFeedAbi,
@@ -4518,6 +4657,7 @@ export {
4518
4657
  iyVaultAbi,
4519
4658
  mellowLrtPriceFeedAbi,
4520
4659
  pendleTWAPPTPriceFeedAbi,
4660
+ pythPriceFeedAbi,
4521
4661
  redstonePriceFeedAbi,
4522
4662
  wstEthPriceFeedAbi,
4523
4663
  yearnPriceFeedAbi,
@@ -48,11 +48,11 @@ const chains = {
48
48
  network: "Mainnet",
49
49
  defaultMarketConfigurators: {
50
50
  "0x354fe9f450F60b8547f88BE042E4A45b46128a06": "Chaos Labs",
51
- "0x4d427D418342d8CE89a7634c3a402851978B680A": "K3",
52
- "0xc168343c791d56dd1da4b4b8b0cc1c1ec1a16e6b": "cp0x",
53
- "0x3b56538833fc02f4f0e75609390f26ded0c32e42": "Re7"
51
+ "0x4d427D418342d8CE89a7634c3a402851978B680A": "K3"
52
+ },
53
+ testMarketConfigurators: {
54
+ "0xc168343c791d56dd1da4b4b8b0cc1c1ec1a16e6b": "cp0x"
54
55
  },
55
- testMarketConfigurators: {},
56
56
  isPublic: true,
57
57
  wellKnownToken: {
58
58
  address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
@@ -1,5 +1,6 @@
1
1
  import { ilpPriceFeedAbi } from "../../abi/index.js";
2
2
  import { BaseContract } from "../../base/index.js";
3
+ import { isUpdatablePriceFeed } from "./isUpdatablePriceFeed.js";
3
4
  import { PriceFeedRef } from "./PriceFeedRef.js";
4
5
  class PartialPriceFeedInitError extends Error {
5
6
  priceFeed;
@@ -104,7 +105,7 @@ class AbstractPriceFeedContract extends BaseContract {
104
105
  const underlying = this.underlyingPriceFeeds.flatMap(
105
106
  (f) => f.priceFeed.updatableDependencies()
106
107
  );
107
- return this.updatable ? [this, ...underlying] : underlying;
108
+ return isUpdatablePriceFeed(this) ? [this, ...underlying] : underlying;
108
109
  }
109
110
  }
110
111
  export {
@@ -22,8 +22,8 @@ import { ExternalPriceFeedContract } from "./ExternalPriceFeed.js";
22
22
  import { MellowLRTPriceFeedContract } from "./MellowLRTPriceFeed.js";
23
23
  import { PendleTWAPPTPriceFeed } from "./PendleTWAPPTPriceFeed.js";
24
24
  import { PythPriceFeed } from "./PythPriceFeed.js";
25
- import { isRedstone, RedstonePriceFeedContract } from "./RedstonePriceFeed.js";
26
- import { RedstoneUpdater } from "./RedstoneUpdater.js";
25
+ import { RedstonePriceFeedContract } from "./RedstonePriceFeed.js";
26
+ import { PythUpdater, RedstoneUpdater } from "./updates/index.js";
27
27
  import { WstETHPriceFeedContract } from "./WstETHPriceFeed.js";
28
28
  import { YearnPriceFeedContract } from "./YearnPriceFeed.js";
29
29
  import { ZeroPriceFeedContract } from "./ZeroPriceFeed.js";
@@ -32,11 +32,14 @@ class PriceFeedRegister extends SDKConstruct {
32
32
  #hooks = new Hooks();
33
33
  #feeds = new AddressMap(void 0, "priceFeeds");
34
34
  #latestUpdate;
35
- redstoneUpdater;
35
+ updaters;
36
36
  constructor(sdk, opts = {}) {
37
37
  super(sdk);
38
38
  this.logger = childLogger("PriceFeedRegister", sdk.logger);
39
- this.redstoneUpdater = new RedstoneUpdater(sdk, opts?.redstone);
39
+ this.updaters = [
40
+ new RedstoneUpdater(sdk, opts?.redstone),
41
+ new PythUpdater(sdk, opts?.pyth)
42
+ ];
40
43
  }
41
44
  addHook = this.#hooks.addHook.bind(this.#hooks);
42
45
  removeHook = this.#hooks.removeHook.bind(this.#hooks);
@@ -49,31 +52,22 @@ class PriceFeedRegister extends SDKConstruct {
49
52
  async generatePriceFeedsUpdateTxs(priceFeeds, logContext = {}) {
50
53
  const updateables = priceFeeds ? priceFeeds.flatMap((pf) => pf.updatableDependencies()) : this.#feeds.values();
51
54
  const txs = [];
52
- const redstonePFs = [];
53
55
  const latestUpdate = {
54
- redstone: [],
56
+ updates: [],
55
57
  timestamp: Math.floor(Date.now() / 1e3)
56
58
  };
57
- for (const pf of updateables) {
58
- if (isRedstone(pf)) {
59
- redstonePFs.push(pf);
60
- }
61
- }
59
+ const updates = (await Promise.all(
60
+ this.updaters.map((u) => u.getUpdateTxs(updateables, logContext))
61
+ )).flat();
62
62
  let maxTimestamp = 0;
63
- if (redstonePFs.length > 0) {
64
- const redstoneUpdates = await this.redstoneUpdater.getUpdateTxs(
65
- redstonePFs,
66
- logContext
67
- );
68
- for (const tx of redstoneUpdates) {
69
- const { data } = tx;
70
- const { timestamp } = data;
71
- if (timestamp > maxTimestamp) {
72
- maxTimestamp = timestamp;
73
- }
74
- txs.push(tx);
75
- latestUpdate.redstone.push(data);
63
+ for (const tx of updates) {
64
+ const { data } = tx;
65
+ const { timestamp } = data;
66
+ if (timestamp > maxTimestamp) {
67
+ maxTimestamp = timestamp;
76
68
  }
69
+ txs.push(tx);
70
+ latestUpdate.updates.push(data);
77
71
  }
78
72
  const result = { txs, timestamp: maxTimestamp };
79
73
  const tsDelta = BigInt(maxTimestamp) - this.sdk.timestamp;
@@ -1,6 +1,7 @@
1
1
  import { decodeAbiParameters } from "viem";
2
+ import { pythPriceFeedAbi } from "../../abi/oracles.js";
2
3
  import { AbstractPriceFeedContract } from "./AbstractPriceFeed.js";
3
- const abi = [];
4
+ const abi = pythPriceFeedAbi;
4
5
  class PythPriceFeed extends AbstractPriceFeedContract {
5
6
  token;
6
7
  priceFeedId;
@@ -40,6 +41,13 @@ class PythPriceFeed extends AbstractPriceFeedContract {
40
41
  this.pyth = decoded[2];
41
42
  }
42
43
  }
44
+ createPriceUpdateTx(data) {
45
+ return this.createRawTx({
46
+ functionName: "updatePrice",
47
+ args: [data],
48
+ description: `updating pyth price for ${this.priceFeedId} [${this.labelAddress(this.address)}]`
49
+ });
50
+ }
43
51
  }
44
52
  export {
45
53
  PythPriceFeed
@@ -75,11 +75,14 @@ class RedstonePriceFeedContract extends AbstractPriceFeedContract {
75
75
  lastPayloadTimestamp: this.lastPayloadTimestamp.toString()
76
76
  };
77
77
  }
78
- }
79
- function isRedstone(pf) {
80
- return pf.contractType === "PRICE_FEED::REDSTONE";
78
+ createPriceUpdateTx(data) {
79
+ return this.createRawTx({
80
+ functionName: "updatePrice",
81
+ args: [data],
82
+ description: `updating redstone price for ${this.dataId} [${this.labelAddress(this.address)}]`
83
+ });
84
+ }
81
85
  }
82
86
  export {
83
- RedstonePriceFeedContract,
84
- isRedstone
87
+ RedstonePriceFeedContract
85
88
  };
@@ -8,6 +8,7 @@ export * from "./CurveStablePriceFeed.js";
8
8
  export * from "./CurveUSDPriceFeed.js";
9
9
  export * from "./Erc4626PriceFeed.js";
10
10
  export * from "./ExternalPriceFeed.js";
11
+ export * from "./isUpdatablePriceFeed.js";
11
12
  export * from "./MellowLRTPriceFeed.js";
12
13
  export * from "./PendleTWAPPTPriceFeed.js";
13
14
  export * from "./PriceFeedRef.js";
@@ -0,0 +1,6 @@
1
+ function isUpdatablePriceFeed(pf) {
2
+ return pf.updatable;
3
+ }
4
+ export {
5
+ isUpdatablePriceFeed
6
+ };
@@ -0,0 +1,28 @@
1
+ const MAX_DATA_TIMESTAMP_DELAY_SECONDS = 10n * 60n;
2
+ const MAX_DATA_TIMESTAMP_AHEAD_SECONDS = 60n;
3
+ class PriceUpdateTx {
4
+ raw;
5
+ data;
6
+ constructor(raw, data) {
7
+ this.raw = raw;
8
+ this.data = data;
9
+ }
10
+ get pretty() {
11
+ const cached = this.data.cached ? " (cached)" : "";
12
+ return `${this.name} feed ${this.data.dataFeedId} at ${this.data.priceFeed} with timestamp ${this.data.timestamp}${cached}`;
13
+ }
14
+ validateTimestamp(blockTimestamp) {
15
+ const { timestamp: expectedPayloadTimestamp } = this.data;
16
+ if (blockTimestamp < expectedPayloadTimestamp) {
17
+ if (BigInt(expectedPayloadTimestamp) - blockTimestamp > MAX_DATA_TIMESTAMP_AHEAD_SECONDS) {
18
+ return "in future";
19
+ }
20
+ } else if (blockTimestamp - BigInt(expectedPayloadTimestamp) > MAX_DATA_TIMESTAMP_DELAY_SECONDS) {
21
+ return "too old";
22
+ }
23
+ return "valid";
24
+ }
25
+ }
26
+ export {
27
+ PriceUpdateTx
28
+ };
@@ -1,4 +1,4 @@
1
- class RedstoneCache {
1
+ class PriceUpdatesCache {
2
2
  #cache = /* @__PURE__ */ new Map();
3
3
  #ttlMs;
4
4
  #historical;
@@ -6,8 +6,8 @@ class RedstoneCache {
6
6
  this.#ttlMs = opts.ttl;
7
7
  this.#historical = opts.historical;
8
8
  }
9
- get(dataServiceId, dataFeedId, uniqueSignersCount) {
10
- const key = this.#cacheKey(dataServiceId, dataFeedId, uniqueSignersCount);
9
+ get(...path) {
10
+ const key = this.#cacheKey(...path);
11
11
  const data = this.#cache.get(key);
12
12
  if (!data) {
13
13
  return void 0;
@@ -18,8 +18,8 @@ class RedstoneCache {
18
18
  }
19
19
  return data;
20
20
  }
21
- set(dataServiceId, dataFeedId, uniqueSignersCount, value) {
22
- const key = this.#cacheKey(dataServiceId, dataFeedId, uniqueSignersCount);
21
+ set(value, ...path) {
22
+ const key = this.#cacheKey(...path);
23
23
  this.#cache.set(key, value);
24
24
  }
25
25
  #expired(value) {
@@ -28,10 +28,10 @@ class RedstoneCache {
28
28
  }
29
29
  return value.timestamp * 1e3 + this.#ttlMs < Date.now();
30
30
  }
31
- #cacheKey(dataServiceId, dataFeedId, uniqueSignersCount) {
32
- return `${dataServiceId}:${dataFeedId}:${uniqueSignersCount}`;
31
+ #cacheKey(...path) {
32
+ return path.join(":");
33
33
  }
34
34
  }
35
35
  export {
36
- RedstoneCache
36
+ PriceUpdatesCache
37
37
  };
@@ -0,0 +1,169 @@
1
+ import {
2
+ parseAccumulatorUpdateData,
3
+ parsePriceFeedMessage,
4
+ sliceAccumulatorUpdateData
5
+ } from "@pythnetwork/price-service-sdk";
6
+ import { encodeAbiParameters, toHex } from "viem";
7
+ import { SDKConstruct } from "../../../base/index.js";
8
+ import { childLogger, retry } from "../../../utils/index.js";
9
+ import { PriceUpdatesCache } from "./PriceUpdatesCache.js";
10
+ import { PriceUpdateTx } from "./PriceUpdateTx.js";
11
+ class PythUpdateTx extends PriceUpdateTx {
12
+ name = "pyth";
13
+ }
14
+ class PythUpdater extends SDKConstruct {
15
+ #logger;
16
+ #cache;
17
+ #historicalTimestamp;
18
+ #api;
19
+ #ignoreMissingFeeds;
20
+ constructor(sdk, opts = {}) {
21
+ super(sdk);
22
+ this.#logger = childLogger("PythUpdater", sdk.logger);
23
+ this.#ignoreMissingFeeds = opts.ignoreMissingFeeds;
24
+ this.#api = opts.apiProxy ?? "https://hermes.pyth.network/v2/updates/price/";
25
+ this.#api = this.#api.endsWith("/") ? this.#api : this.#api + "/";
26
+ let ts = opts.historicTimestamp;
27
+ if (ts) {
28
+ this.#historicalTimestamp = ts === true ? Number(this.sdk.timestamp) : ts;
29
+ }
30
+ this.#cache = new PriceUpdatesCache({
31
+ // currently staleness period is 240 seconds on all networks, add some buffer
32
+ // this period of 4 minutes is selected based on time that is required for user to sign transaction with wallet
33
+ // so it's unlikely to decrease
34
+ ttl: opts.cacheTTL ?? 225 * 1e3,
35
+ historical: !!ts
36
+ });
37
+ }
38
+ async getUpdateTxs(feeds, logContext = {}) {
39
+ this.#logger?.debug(
40
+ logContext,
41
+ `generating update transactions for ${feeds.length} pyth price feeds`
42
+ );
43
+ const pythFeeds = new Map(
44
+ feeds.filter(isPyth).map((f) => [f.priceFeedId, f])
45
+ );
46
+ const payloads = await this.#getPayloads(new Set(pythFeeds.keys()));
47
+ const results = payloads.map((p) => {
48
+ const priceFeed = pythFeeds.get(p.dataFeedId);
49
+ if (!priceFeed) {
50
+ throw new Error(`cannot find price feed for ${p.dataFeedId}`);
51
+ }
52
+ const { dataFeedId, timestamp, cached, data } = p;
53
+ return new PythUpdateTx(priceFeed.createPriceUpdateTx(data), {
54
+ dataFeedId,
55
+ priceFeed: priceFeed.address,
56
+ timestamp,
57
+ cached
58
+ });
59
+ });
60
+ this.#logger?.debug(
61
+ logContext,
62
+ `generated ${results.length} update transactions for pyth price feeds`
63
+ );
64
+ return results;
65
+ }
66
+ /**
67
+ * Gets pyth payloads
68
+ * @param dataFeedsIds
69
+ * @returns
70
+ */
71
+ async #getPayloads(dataFeedsIds) {
72
+ this.#logger?.debug(
73
+ `getting pyth payloads for ${dataFeedsIds.size} price feeds: ${Array.from(dataFeedsIds).join(", ")}`
74
+ );
75
+ const fromCache = [];
76
+ const uncached = [];
77
+ for (const priceFeedsId of dataFeedsIds) {
78
+ const cached = this.#cache.get(priceFeedsId);
79
+ if (cached) {
80
+ fromCache.push({ ...cached, cached: true });
81
+ } else {
82
+ uncached.push(priceFeedsId);
83
+ }
84
+ }
85
+ const fromPyth = await this.#fetchPayloads(
86
+ new Set(uncached)
87
+ );
88
+ for (const resp of fromPyth) {
89
+ this.#cache.set(resp, resp.dataFeedId);
90
+ }
91
+ this.#logger?.debug(
92
+ `got ${fromPyth.length} new pyth updates and ${fromCache.length} from cache`
93
+ );
94
+ return [...fromCache, ...fromPyth];
95
+ }
96
+ /**
97
+ * Fetches pyth payloads from Hermes API
98
+ * @param dataFeedsIds
99
+ * @returns
100
+ */
101
+ async #fetchPayloads(dataFeedsIds) {
102
+ if (dataFeedsIds.size === 0) {
103
+ return [];
104
+ }
105
+ const ids = Array.from(dataFeedsIds);
106
+ const tsStr = this.#historicalTimestamp ? ` with historical timestamp ${this.#historicalTimestamp}` : "";
107
+ this.#logger?.debug(
108
+ `fetching pyth payloads for ${dataFeedsIds.size} price feeds: ${ids.join(", ")}${tsStr}`
109
+ );
110
+ const url = new URL(this.#api + (this.#historicalTimestamp ?? "latest"));
111
+ url.searchParams.append("parsed", "false");
112
+ if (this.#ignoreMissingFeeds) {
113
+ url.searchParams.append("ignore_invalid_price_ids", "true");
114
+ }
115
+ for (const id of dataFeedsIds) {
116
+ url.searchParams.append("ids[]", id);
117
+ }
118
+ const resp = await retry(
119
+ async () => {
120
+ const resp2 = await fetch(url.toString());
121
+ if (!resp2.ok) {
122
+ const body = await resp2.text();
123
+ throw new Error(
124
+ `failed to fetch pyth payloads: ${resp2.statusText}: ${body}`
125
+ );
126
+ }
127
+ const data = await resp2.json();
128
+ return data;
129
+ },
130
+ { attempts: 3, exponent: 2, interval: 200 }
131
+ );
132
+ return respToCalldata(resp);
133
+ }
134
+ }
135
+ function isPyth(pf) {
136
+ return pf.contractType === "PRICE_FEED::PYTH";
137
+ }
138
+ function respToCalldata(resp) {
139
+ const updates = splitAccumulatorUpdates(resp.binary.data[0]);
140
+ return updates.map(({ data, dataFeedId, timestamp }) => {
141
+ return {
142
+ dataFeedId,
143
+ data: encodeAbiParameters(
144
+ [{ type: "uint256" }, { type: "bytes[]" }],
145
+ [BigInt(timestamp), [data]]
146
+ ),
147
+ timestamp,
148
+ cached: false
149
+ };
150
+ });
151
+ }
152
+ function splitAccumulatorUpdates(binary) {
153
+ const data = Buffer.from(binary, "hex");
154
+ const parsed = parseAccumulatorUpdateData(data);
155
+ const results = [];
156
+ for (let i = 0; i < parsed.updates.length; i++) {
157
+ const upd = parsed.updates[i].message;
158
+ const msg = parsePriceFeedMessage(upd);
159
+ results.push({
160
+ dataFeedId: toHex(msg.feedId),
161
+ timestamp: msg.publishTime.toNumber(),
162
+ data: toHex(sliceAccumulatorUpdateData(data, i, i + 1))
163
+ });
164
+ }
165
+ return results;
166
+ }
167
+ export {
168
+ PythUpdater
169
+ };
@@ -1,33 +1,12 @@
1
1
  import { DataServiceWrapper } from "@redstone-finance/evm-connector";
2
2
  import { RedstonePayload } from "@redstone-finance/protocol";
3
3
  import { encodeAbiParameters, toBytes } from "viem";
4
- import { SDKConstruct } from "../../base/index.js";
5
- import { AddressMap, childLogger, retry } from "../../utils/index.js";
6
- import { RedstoneCache } from "./RedstoneCache.js";
7
- const MAX_DATA_TIMESTAMP_DELAY_SECONDS = 10n * 60n;
8
- const MAX_DATA_TIMESTAMP_AHEAD_SECONDS = 60n;
9
- class RedstoneUpdateTx {
10
- raw;
11
- data;
12
- constructor(raw, data) {
13
- this.raw = raw;
14
- this.data = data;
15
- }
16
- get pretty() {
17
- const cached = this.data.cached ? " (cached)" : "";
18
- return `redstone feed ${this.data.dataFeedId} at ${this.data.priceFeed} with timestamp ${this.data.timestamp}${cached}`;
19
- }
20
- validateTimestamp(blockTimestamp) {
21
- const { timestamp: expectedPayloadTimestamp } = this.data;
22
- if (blockTimestamp < expectedPayloadTimestamp) {
23
- if (BigInt(expectedPayloadTimestamp) - blockTimestamp > MAX_DATA_TIMESTAMP_AHEAD_SECONDS) {
24
- return "in future";
25
- }
26
- } else if (blockTimestamp - BigInt(expectedPayloadTimestamp) > MAX_DATA_TIMESTAMP_DELAY_SECONDS) {
27
- return "too old";
28
- }
29
- return "valid";
30
- }
4
+ import { SDKConstruct } from "../../../base/index.js";
5
+ import { AddressMap, childLogger, retry } from "../../../utils/index.js";
6
+ import { PriceUpdatesCache } from "./PriceUpdatesCache.js";
7
+ import { PriceUpdateTx } from "./PriceUpdateTx.js";
8
+ class RedstoneUpdateTx extends PriceUpdateTx {
9
+ name = "redstone";
31
10
  }
32
11
  class RedstoneUpdater extends SDKConstruct {
33
12
  #logger;
@@ -47,7 +26,7 @@ class RedstoneUpdater extends SDKConstruct {
47
26
  ts = ts === true ? Number(this.sdk.timestamp) * 1e3 : ts;
48
27
  this.#historicalTimestampMs = 6e4 * Math.floor(ts / 6e4);
49
28
  }
50
- this.#cache = new RedstoneCache({
29
+ this.#cache = new PriceUpdatesCache({
51
30
  // currently staleness period is 240 seconds on all networks, add some buffer
52
31
  // this period of 4 minutes is selected based on time that is required for user to sign transaction with wallet
53
32
  // so it's unlikely to decrease
@@ -63,6 +42,9 @@ class RedstoneUpdater extends SDKConstruct {
63
42
  const groupedFeeds = {};
64
43
  const priceFeeds = /* @__PURE__ */ new Map();
65
44
  for (const feed of feeds) {
45
+ if (!isRedstone(feed)) {
46
+ continue;
47
+ }
66
48
  const key = `${feed.dataServiceId}:${feed.signersThreshold}`;
67
49
  if (!groupedFeeds[key]) {
68
50
  groupedFeeds[key] = /* @__PURE__ */ new Set();
@@ -88,20 +70,13 @@ class RedstoneUpdater extends SDKConstruct {
88
70
  }
89
71
  for (const priceFeed of pfsForDataId.values()) {
90
72
  results.push(
91
- new RedstoneUpdateTx(
92
- priceFeed.createRawTx({
93
- functionName: "updatePrice",
94
- args: [data],
95
- description: `updating price for ${dataFeedId} [${this.labelAddress(priceFeed.address)}]`
96
- }),
97
- {
98
- dataFeedId,
99
- dataServiceId,
100
- priceFeed: priceFeed.address,
101
- timestamp,
102
- cached
103
- }
104
- )
73
+ new RedstoneUpdateTx(priceFeed.createPriceUpdateTx(data), {
74
+ dataFeedId,
75
+ dataServiceId,
76
+ priceFeed: priceFeed.address,
77
+ timestamp,
78
+ cached
79
+ })
105
80
  );
106
81
  }
107
82
  }
@@ -144,7 +119,7 @@ class RedstoneUpdater extends SDKConstruct {
144
119
  uniqueSignersCount
145
120
  );
146
121
  for (const resp of fromRedstone) {
147
- this.#cache.set(dataServiceId, resp.dataFeedId, uniqueSignersCount, resp);
122
+ this.#cache.set(resp, dataServiceId, resp.dataFeedId, uniqueSignersCount);
148
123
  }
149
124
  this.#logger?.debug(
150
125
  `got ${fromRedstone.length} new redstone updates and ${fromCache.length} from cache`
@@ -245,7 +220,9 @@ function getCalldataWithTimestamp(dataFeedId, packages, unsignedMetadata) {
245
220
  cached: false
246
221
  };
247
222
  }
223
+ function isRedstone(pf) {
224
+ return pf.contractType === "PRICE_FEED::REDSTONE";
225
+ }
248
226
  export {
249
- RedstoneUpdateTx,
250
227
  RedstoneUpdater
251
228
  };
@@ -0,0 +1,6 @@
1
+ import { PythUpdater } from "./PythUpdater.js";
2
+ import { RedstoneUpdater } from "./RedstoneUpdater.js";
3
+ export {
4
+ PythUpdater,
5
+ RedstoneUpdater
6
+ };