@gearbox-protocol/sdk 12.9.9 → 12.10.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 (48) hide show
  1. package/dist/cjs/history/errors.js +2 -0
  2. package/dist/cjs/permissionless/utils/price-update/get-prices.js +0 -3
  3. package/dist/cjs/permissionless/utils/price-update/get-updatable-feeds.js +2 -23
  4. package/dist/cjs/sdk/GearboxSDK.js +5 -25
  5. package/dist/cjs/sdk/market/pricefeeds/PriceFeedsRegister.js +8 -6
  6. package/dist/cjs/sdk/market/pricefeeds/PythPriceFeed.js +3 -27
  7. package/dist/cjs/sdk/market/pricefeeds/RedstonePriceFeed.js +2 -0
  8. package/dist/cjs/sdk/market/pricefeeds/index.js +0 -2
  9. package/dist/cjs/sdk/market/pricefeeds/updates/RedstoneUpdater.js +3 -202
  10. package/dist/cjs/sdk/market/pricefeeds/updates/fetchRedstonePayloads.js +4 -126
  11. package/dist/cjs/sdk/market/pricefeeds/updates/index.js +0 -7
  12. package/dist/cjs/sdk/options.js +1 -5
  13. package/dist/esm/history/errors.js +2 -0
  14. package/dist/esm/permissionless/utils/price-update/get-prices.js +0 -3
  15. package/dist/esm/permissionless/utils/price-update/get-updatable-feeds.js +2 -27
  16. package/dist/esm/sdk/GearboxSDK.js +5 -25
  17. package/dist/esm/sdk/market/pricefeeds/PriceFeedsRegister.js +8 -6
  18. package/dist/esm/sdk/market/pricefeeds/PythPriceFeed.js +4 -28
  19. package/dist/esm/sdk/market/pricefeeds/RedstonePriceFeed.js +2 -0
  20. package/dist/esm/sdk/market/pricefeeds/index.js +0 -2
  21. package/dist/esm/sdk/market/pricefeeds/updates/RedstoneUpdater.js +3 -202
  22. package/dist/esm/sdk/market/pricefeeds/updates/fetchRedstonePayloads.js +4 -130
  23. package/dist/esm/sdk/market/pricefeeds/updates/index.js +0 -4
  24. package/dist/esm/sdk/options.js +2 -9
  25. package/dist/types/permissionless/utils/price-update/get-updatable-feeds.d.ts +4 -3
  26. package/dist/types/sdk/market/pricefeeds/PriceFeedsRegister.d.ts +2 -3
  27. package/dist/types/sdk/market/pricefeeds/PythPriceFeed.d.ts +2 -25
  28. package/dist/types/sdk/market/pricefeeds/index.d.ts +1 -1
  29. package/dist/types/sdk/market/pricefeeds/updates/RedstoneUpdater.d.ts +5 -10
  30. package/dist/types/sdk/market/pricefeeds/updates/fetchRedstonePayloads.d.ts +2 -6
  31. package/dist/types/sdk/market/pricefeeds/updates/index.d.ts +0 -2
  32. package/dist/types/sdk/options.d.ts +0 -6
  33. package/package.json +6 -7
  34. package/dist/cjs/sdk/market/pricefeeds/updates/PriceUpdateTx.js +0 -55
  35. package/dist/cjs/sdk/market/pricefeeds/updates/PriceUpdatesCache.js +0 -78
  36. package/dist/cjs/sdk/market/pricefeeds/updates/PythAccumulatorUpdateData.js +0 -195
  37. package/dist/cjs/sdk/market/pricefeeds/updates/PythUpdater.js +0 -183
  38. package/dist/cjs/sdk/market/pricefeeds/updates/fetchPythPayloads.js +0 -135
  39. package/dist/esm/sdk/market/pricefeeds/updates/PriceUpdateTx.js +0 -31
  40. package/dist/esm/sdk/market/pricefeeds/updates/PriceUpdatesCache.js +0 -54
  41. package/dist/esm/sdk/market/pricefeeds/updates/PythAccumulatorUpdateData.js +0 -157
  42. package/dist/esm/sdk/market/pricefeeds/updates/PythUpdater.js +0 -158
  43. package/dist/esm/sdk/market/pricefeeds/updates/fetchPythPayloads.js +0 -115
  44. package/dist/types/sdk/market/pricefeeds/updates/PriceUpdateTx.d.ts +0 -13
  45. package/dist/types/sdk/market/pricefeeds/updates/PriceUpdatesCache.d.ts +0 -24
  46. package/dist/types/sdk/market/pricefeeds/updates/PythAccumulatorUpdateData.d.ts +0 -41
  47. package/dist/types/sdk/market/pricefeeds/updates/PythUpdater.d.ts +0 -26
  48. package/dist/types/sdk/market/pricefeeds/updates/fetchPythPayloads.d.ts +0 -45
@@ -1,135 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
- var fetchPythPayloads_exports = {};
20
- __export(fetchPythPayloads_exports, {
21
- fetchPythPayloads: () => fetchPythPayloads
22
- });
23
- module.exports = __toCommonJS(fetchPythPayloads_exports);
24
- var import_buffer = require("buffer");
25
- var import_viem = require("viem");
26
- var import_utils = require("../../../utils/index.js");
27
- var import_PythAccumulatorUpdateData = require("./PythAccumulatorUpdateData.js");
28
- async function fetchPythPayloads(options) {
29
- const {
30
- dataFeedsIds,
31
- ignoreMissingFeeds,
32
- historicalTimestampSeconds,
33
- logger,
34
- apiProxy,
35
- customFetch = fetch,
36
- returnPrices
37
- } = options;
38
- const ids = Array.from(new Set(dataFeedsIds)).sort();
39
- if (ids.length === 0) {
40
- return [];
41
- }
42
- let api = apiProxy ?? "https://hermes.pyth.network/v2/updates/price/";
43
- api = api.endsWith("/") ? api : `${api}/`;
44
- const url = new URL(api + (historicalTimestampSeconds ?? "latest"));
45
- url.searchParams.append("parsed", returnPrices ? "true" : "false");
46
- if (ignoreMissingFeeds) {
47
- url.searchParams.append("ignore_invalid_price_ids", "true");
48
- }
49
- for (const id of ids) {
50
- url.searchParams.append("ids[]", id);
51
- }
52
- const resp = await (0, import_utils.retry)(
53
- async () => {
54
- const resp2 = await customFetch(url.toString());
55
- if (!resp2.ok) {
56
- const body = await resp2.text();
57
- throw new Error(
58
- `failed to fetch pyth payloads: ${resp2.statusText}: ${body}`
59
- );
60
- }
61
- const data = await resp2.json();
62
- return data;
63
- },
64
- { attempts: 3, exponent: 2, interval: 200 }
65
- );
66
- const result = respToCalldata(resp, returnPrices);
67
- if (result.length !== ids.length) {
68
- if (ignoreMissingFeeds) {
69
- logger?.warn(`expected ${ids.length} price feeds, got ${result.length}`);
70
- } else {
71
- throw new Error(
72
- `expected ${ids.length} price feeds, got ${result.length}`
73
- );
74
- }
75
- }
76
- return result;
77
- }
78
- function respToCalldata(resp, returnPrices) {
79
- if (resp.binary.data.length === 0) {
80
- return [];
81
- }
82
- const updates = splitAccumulatorUpdates(resp.binary.data[0]);
83
- const priceMap = /* @__PURE__ */ new Map();
84
- if (returnPrices && resp.parsed) {
85
- for (const feed of resp.parsed) {
86
- const feedId = feed.id.startsWith("0x") ? feed.id : `0x${feed.id}`;
87
- priceMap.set(feedId.toLowerCase(), {
88
- price: BigInt(feed.price.price),
89
- decimals: Math.abs(feed.price.expo)
90
- });
91
- }
92
- }
93
- return updates.map(({ data, dataFeedId, timestamp }) => {
94
- const base = {
95
- dataFeedId,
96
- data: (0, import_viem.encodeAbiParameters)(
97
- [{ type: "uint256" }, { type: "bytes[]" }],
98
- [BigInt(timestamp), [data]]
99
- ),
100
- timestamp,
101
- cached: false
102
- };
103
- if (returnPrices) {
104
- const priceInfo = priceMap.get(dataFeedId.toLowerCase());
105
- if (!priceInfo) {
106
- throw new Error(`Price info not found for feed ${dataFeedId}`);
107
- }
108
- return {
109
- ...base,
110
- price: priceInfo.price,
111
- decimals: priceInfo.decimals
112
- };
113
- }
114
- return base;
115
- });
116
- }
117
- function splitAccumulatorUpdates(binary) {
118
- const data = import_buffer.Buffer.from(binary, "hex");
119
- const parsed = (0, import_PythAccumulatorUpdateData.parseAccumulatorUpdateData)(data);
120
- const results = [];
121
- for (let i = 0; i < parsed.updates.length; i++) {
122
- const upd = parsed.updates[i].message;
123
- const msg = (0, import_PythAccumulatorUpdateData.parsePriceFeedMessage)(upd);
124
- results.push({
125
- dataFeedId: (0, import_viem.toHex)(msg.feedId),
126
- timestamp: msg.publishTime.toNumber(),
127
- data: (0, import_viem.toHex)((0, import_PythAccumulatorUpdateData.sliceAccumulatorUpdateData)(data, i, i + 1))
128
- });
129
- }
130
- return results;
131
- }
132
- // Annotate the CommonJS export names for ESM import in node:
133
- 0 && (module.exports = {
134
- fetchPythPayloads
135
- });
@@ -1,31 +0,0 @@
1
- const MAX_DATA_TIMESTAMP_DELAY_SECONDS = 10n * 60n;
2
- const MAX_DATA_TIMESTAMP_AHEAD_SECONDS = 60n;
3
- class PriceUpdateTx {
4
- /**
5
- * raw tx with encoded `priceFeed.updatePrice` call
6
- */
7
- raw;
8
- data;
9
- constructor(raw, data) {
10
- this.raw = raw;
11
- this.data = data;
12
- }
13
- get pretty() {
14
- const cached = this.data.cached ? " (cached)" : "";
15
- return `${this.name} feed ${this.data.dataFeedId} at ${this.data.priceFeed} with timestamp ${this.data.timestamp}${cached}`;
16
- }
17
- validateTimestamp(blockTimestamp) {
18
- const { timestamp: expectedPayloadTimestamp } = this.data;
19
- if (blockTimestamp < expectedPayloadTimestamp) {
20
- if (BigInt(expectedPayloadTimestamp) - blockTimestamp > MAX_DATA_TIMESTAMP_AHEAD_SECONDS) {
21
- return "in future";
22
- }
23
- } else if (blockTimestamp - BigInt(expectedPayloadTimestamp) > MAX_DATA_TIMESTAMP_DELAY_SECONDS) {
24
- return "too old";
25
- }
26
- return "valid";
27
- }
28
- }
29
- export {
30
- PriceUpdateTx
31
- };
@@ -1,54 +0,0 @@
1
- class PriceUpdatesCache {
2
- static #caches = /* @__PURE__ */ new Map();
3
- /**
4
- * Price update caches can be shared across networks
5
- * @param id - unique key to identify the cache
6
- * @param opts
7
- * @returns
8
- */
9
- static get(id, opts) {
10
- const key = `${id}:${opts.historical ? "historical" : "latest"}:${opts.ttl}`;
11
- const cache = PriceUpdatesCache.#caches.get(key);
12
- if (cache) {
13
- return cache;
14
- }
15
- const newCache = new PriceUpdatesCache(opts);
16
- PriceUpdatesCache.#caches.set(key, newCache);
17
- return newCache;
18
- }
19
- #cache = /* @__PURE__ */ new Map();
20
- #ttlMs;
21
- #historical;
22
- constructor(opts) {
23
- this.#ttlMs = opts.ttl;
24
- this.#historical = opts.historical;
25
- }
26
- get(...path) {
27
- const key = this.#cacheKey(...path);
28
- const data = this.#cache.get(key);
29
- if (!data) {
30
- return void 0;
31
- }
32
- if (this.#expired(data)) {
33
- this.#cache.delete(key);
34
- return void 0;
35
- }
36
- return data;
37
- }
38
- set(value, ...path) {
39
- const key = this.#cacheKey(...path);
40
- this.#cache.set(key, value);
41
- }
42
- #expired(value) {
43
- if (this.#historical) {
44
- return false;
45
- }
46
- return value.timestamp * 1e3 + this.#ttlMs < Date.now();
47
- }
48
- #cacheKey(...path) {
49
- return path.join(":");
50
- }
51
- }
52
- export {
53
- PriceUpdatesCache
54
- };
@@ -1,157 +0,0 @@
1
- import BN from "bn.js";
2
- import { Buffer } from "buffer";
3
- const ACCUMULATOR_MAGIC = "504e4155";
4
- const MAJOR_VERSION = 1;
5
- const MINOR_VERSION = 0;
6
- const KECCAK160_HASH_SIZE = 20;
7
- const PRICE_FEED_MESSAGE_VARIANT = 0;
8
- const TWAP_MESSAGE_VARIANT = 1;
9
- function isAccumulatorUpdateData(updateBytes) {
10
- return updateBytes.toString("hex").slice(0, 8) === ACCUMULATOR_MAGIC && updateBytes[4] === MAJOR_VERSION && updateBytes[5] === MINOR_VERSION;
11
- }
12
- function parsePriceFeedMessage(message) {
13
- let cursor = 0;
14
- const variant = message.readUInt8(cursor);
15
- if (variant !== PRICE_FEED_MESSAGE_VARIANT) {
16
- throw new Error("Not a price feed message");
17
- }
18
- cursor += 1;
19
- const feedId = message.subarray(cursor, cursor + 32);
20
- cursor += 32;
21
- const price = new BN(message.subarray(cursor, cursor + 8), "be");
22
- cursor += 8;
23
- const confidence = new BN(message.subarray(cursor, cursor + 8), "be");
24
- cursor += 8;
25
- const exponent = message.readInt32BE(cursor);
26
- cursor += 4;
27
- const publishTime = new BN(message.subarray(cursor, cursor + 8), "be");
28
- cursor += 8;
29
- const prevPublishTime = new BN(message.subarray(cursor, cursor + 8), "be");
30
- cursor += 8;
31
- const emaPrice = new BN(message.subarray(cursor, cursor + 8), "be");
32
- cursor += 8;
33
- const emaConf = new BN(message.subarray(cursor, cursor + 8), "be");
34
- cursor += 8;
35
- return {
36
- feedId,
37
- price,
38
- confidence,
39
- exponent,
40
- publishTime,
41
- prevPublishTime,
42
- emaPrice,
43
- emaConf
44
- };
45
- }
46
- function parseTwapMessage(message) {
47
- let cursor = 0;
48
- const variant = message.readUInt8(cursor);
49
- if (variant !== TWAP_MESSAGE_VARIANT) {
50
- throw new Error("Not a twap message");
51
- }
52
- cursor += 1;
53
- const feedId = message.subarray(cursor, cursor + 32);
54
- cursor += 32;
55
- const cumulativePrice = new BN(message.subarray(cursor, cursor + 16), "be");
56
- cursor += 16;
57
- const cumulativeConf = new BN(message.subarray(cursor, cursor + 16), "be");
58
- cursor += 16;
59
- const numDownSlots = new BN(message.subarray(cursor, cursor + 8), "be");
60
- cursor += 8;
61
- const exponent = message.readInt32BE(cursor);
62
- cursor += 4;
63
- const publishTime = new BN(message.subarray(cursor, cursor + 8), "be");
64
- cursor += 8;
65
- const prevPublishTime = new BN(message.subarray(cursor, cursor + 8), "be");
66
- cursor += 8;
67
- const publishSlot = new BN(message.subarray(cursor, cursor + 8), "be");
68
- cursor += 8;
69
- return {
70
- feedId,
71
- cumulativePrice,
72
- cumulativeConf,
73
- numDownSlots,
74
- exponent,
75
- publishTime,
76
- prevPublishTime,
77
- publishSlot
78
- };
79
- }
80
- function sliceAccumulatorUpdateData(data, start, end) {
81
- if (!isAccumulatorUpdateData(data)) {
82
- throw new Error("Invalid accumulator message");
83
- }
84
- let cursor = 6;
85
- const trailingPayloadSize = data.readUint8(cursor);
86
- cursor += 1 + trailingPayloadSize;
87
- cursor += 1;
88
- const vaaSize = data.readUint16BE(cursor);
89
- cursor += 2;
90
- cursor += vaaSize;
91
- const endOfVaa = cursor;
92
- const updates = [];
93
- const numUpdates = data.readUInt8(cursor);
94
- cursor += 1;
95
- for (let i = 0; i < numUpdates; i++) {
96
- const updateStart = cursor;
97
- const messageSize = data.readUint16BE(cursor);
98
- cursor += 2;
99
- cursor += messageSize;
100
- const numProofs = data.readUInt8(cursor);
101
- cursor += 1;
102
- cursor += KECCAK160_HASH_SIZE * numProofs;
103
- updates.push(data.subarray(updateStart, cursor));
104
- }
105
- if (cursor !== data.length) {
106
- throw new Error("Didn't reach the end of the message");
107
- }
108
- const sliceUpdates = updates.slice(start, end);
109
- return Buffer.concat([
110
- data.subarray(0, endOfVaa),
111
- Buffer.from([sliceUpdates.length]),
112
- ...updates.slice(start, end)
113
- ]);
114
- }
115
- function parseAccumulatorUpdateData(data) {
116
- if (!isAccumulatorUpdateData(data)) {
117
- throw new Error("Invalid accumulator message");
118
- }
119
- let cursor = 6;
120
- const trailingPayloadSize = data.readUint8(cursor);
121
- cursor += 1 + trailingPayloadSize;
122
- cursor += 1;
123
- const vaaSize = data.readUint16BE(cursor);
124
- cursor += 2;
125
- const vaa = data.subarray(cursor, cursor + vaaSize);
126
- cursor += vaaSize;
127
- const numUpdates = data.readUInt8(cursor);
128
- const updates = [];
129
- cursor += 1;
130
- for (let i = 0; i < numUpdates; i++) {
131
- const messageSize = data.readUint16BE(cursor);
132
- cursor += 2;
133
- const message = data.subarray(cursor, cursor + messageSize);
134
- cursor += messageSize;
135
- const numProofs = data.readUInt8(cursor);
136
- cursor += 1;
137
- const proof = [];
138
- for (let j = 0; j < numProofs; j++) {
139
- proof.push(
140
- Array.from(data.subarray(cursor, cursor + KECCAK160_HASH_SIZE))
141
- );
142
- cursor += KECCAK160_HASH_SIZE;
143
- }
144
- updates.push({ message, proof });
145
- }
146
- if (cursor !== data.length) {
147
- throw new Error("Didn't reach the end of the message");
148
- }
149
- return { vaa, updates };
150
- }
151
- export {
152
- isAccumulatorUpdateData,
153
- parseAccumulatorUpdateData,
154
- parsePriceFeedMessage,
155
- parseTwapMessage,
156
- sliceAccumulatorUpdateData
157
- };
@@ -1,158 +0,0 @@
1
- import { z } from "zod/v4";
2
- import { SDKConstruct } from "../../../base/index.js";
3
- import { fetchPythPayloads } from "./fetchPythPayloads.js";
4
- import { PriceUpdatesCache } from "./PriceUpdatesCache.js";
5
- import { PriceUpdateTx } from "./PriceUpdateTx.js";
6
- class PythUpdateTx extends PriceUpdateTx {
7
- name = "pyth";
8
- }
9
- const PythOptions = z.object({
10
- /**
11
- * Fixed pyth historic timestamp in seconds
12
- * Set to true to enable pyth historical mode using timestamp from attach block
13
- */
14
- historicTimestamp: z.union([z.number().nonnegative(), z.literal(true)]).optional(),
15
- /**
16
- * Override Hermes API with this proxy. Can be used to set caching proxies, to avoid rate limiting
17
- */
18
- apiProxy: z.url().optional(),
19
- /**
20
- * TTL for pyth cache in milliseconds
21
- * If 0, disables caching
22
- * If not set, uses some default value
23
- * Cache is always enabled in historical mode
24
- */
25
- cacheTTL: z.number().nonnegative().optional(),
26
- /**
27
- * When true, no error will be thrown when pyth is unable to fetch data for some feeds
28
- */
29
- ignoreMissingFeeds: z.boolean().optional()
30
- });
31
- class PythUpdater extends SDKConstruct {
32
- #cache;
33
- #historicalTimestamp;
34
- #apiProxy;
35
- #ignoreMissingFeeds;
36
- constructor(sdk, opts = {}) {
37
- super(sdk);
38
- const { apiProxy, cacheTTL, ignoreMissingFeeds, historicTimestamp } = opts;
39
- this.#ignoreMissingFeeds = ignoreMissingFeeds;
40
- this.#apiProxy = apiProxy;
41
- if (historicTimestamp) {
42
- this.#historicalTimestamp = historicTimestamp === true ? Number(this.sdk.timestamp) : historicTimestamp;
43
- this.logger?.debug(
44
- `using historical timestamp ${this.#historicalTimestamp}`
45
- );
46
- }
47
- this.#cache = PriceUpdatesCache.get("pyth", {
48
- // currently staleness period is 240 seconds on all networks, add some buffer
49
- // this period of 4 minutes is selected based on time that is required for user to sign transaction with wallet
50
- // so it's unlikely to decrease
51
- ttl: cacheTTL ?? 225 * 1e3,
52
- historical: !!historicTimestamp
53
- });
54
- }
55
- async getUpdateTxs(feeds) {
56
- if (feeds.length === 0) {
57
- return [];
58
- }
59
- this.logger?.debug(
60
- `generating update transactions for ${feeds.length} pyth price feeds`
61
- );
62
- const pythFeeds = new Map(
63
- feeds.filter(isPyth).map((f) => [f.priceFeedId, f])
64
- );
65
- const payloads = await this.#getPayloads(new Set(pythFeeds.keys()));
66
- let [minTimestamp, maxTimestamp] = [Number.POSITIVE_INFINITY, 0];
67
- const results = [];
68
- for (const p of payloads) {
69
- const priceFeed = pythFeeds.get(p.dataFeedId);
70
- if (!priceFeed) {
71
- throw new Error(`cannot find price feed for ${p.dataFeedId}`);
72
- }
73
- const { dataFeedId, timestamp, cached, data } = p;
74
- minTimestamp = Math.min(minTimestamp, timestamp);
75
- maxTimestamp = Math.max(maxTimestamp, timestamp);
76
- results.push(
77
- new PythUpdateTx(priceFeed.createPriceUpdateTx(data), {
78
- dataFeedId,
79
- priceFeed: priceFeed.address,
80
- timestamp,
81
- cached
82
- })
83
- );
84
- }
85
- let tsRange = "";
86
- if (results.length) {
87
- const minDelta = BigInt(minTimestamp) - this.sdk.timestamp;
88
- tsRange = `, timestamps ${minTimestamp} (${minDelta})`;
89
- if (minTimestamp !== maxTimestamp) {
90
- const maxDelta = BigInt(maxTimestamp) - this.sdk.timestamp;
91
- tsRange = `${tsRange} - ${maxTimestamp} (${maxDelta})`;
92
- }
93
- }
94
- this.logger?.debug(
95
- `generated ${results.length} update transactions for pyth price feeds: ${Array.from(pythFeeds.keys()).join(", ")}${tsRange}`
96
- );
97
- return results;
98
- }
99
- /**
100
- * Gets pyth payloads
101
- * @param dataFeedsIds
102
- * @returns
103
- */
104
- async #getPayloads(dataFeedsIds) {
105
- this.logger?.debug(
106
- `getting pyth payloads for ${dataFeedsIds.size} price feeds: ${Array.from(dataFeedsIds).join(", ")}`
107
- );
108
- const fromCache = [];
109
- const uncached = [];
110
- for (const priceFeedsId of dataFeedsIds) {
111
- const cached = this.#cache.get(priceFeedsId);
112
- if (cached) {
113
- fromCache.push({ ...cached, cached: true });
114
- } else {
115
- uncached.push(priceFeedsId);
116
- }
117
- }
118
- const fromPyth = await this.#fetchPayloads(
119
- new Set(uncached)
120
- );
121
- for (const resp of fromPyth) {
122
- this.#cache.set(resp, resp.dataFeedId);
123
- }
124
- this.logger?.debug(
125
- `got ${fromPyth.length} new pyth updates and ${fromCache.length} from cache`
126
- );
127
- return [...fromCache, ...fromPyth];
128
- }
129
- /**
130
- * Fetches pyth payloads from Hermes API
131
- * @param dataFeedsIds
132
- * @returns
133
- */
134
- async #fetchPayloads(dataFeedsIds) {
135
- if (dataFeedsIds.size === 0) {
136
- return [];
137
- }
138
- const ids = Array.from(dataFeedsIds);
139
- const tsStr = this.#historicalTimestamp ? ` with historical timestamp ${this.#historicalTimestamp}` : "";
140
- this.logger?.debug(
141
- `fetching pyth payloads for ${dataFeedsIds.size} price feeds: ${ids.join(", ")}${tsStr}`
142
- );
143
- return fetchPythPayloads({
144
- dataFeedsIds,
145
- historicalTimestampSeconds: this.#historicalTimestamp,
146
- apiProxy: this.#apiProxy,
147
- ignoreMissingFeeds: this.#ignoreMissingFeeds,
148
- logger: this.logger
149
- });
150
- }
151
- }
152
- function isPyth(pf) {
153
- return pf.contractType === "PRICE_FEED::PYTH";
154
- }
155
- export {
156
- PythOptions,
157
- PythUpdater
158
- };
@@ -1,115 +0,0 @@
1
- import { Buffer } from "buffer";
2
- import { encodeAbiParameters, toHex } from "viem";
3
- import { retry } from "../../../utils/index.js";
4
- import {
5
- parseAccumulatorUpdateData,
6
- parsePriceFeedMessage,
7
- sliceAccumulatorUpdateData
8
- } from "./PythAccumulatorUpdateData.js";
9
- async function fetchPythPayloads(options) {
10
- const {
11
- dataFeedsIds,
12
- ignoreMissingFeeds,
13
- historicalTimestampSeconds,
14
- logger,
15
- apiProxy,
16
- customFetch = fetch,
17
- returnPrices
18
- } = options;
19
- const ids = Array.from(new Set(dataFeedsIds)).sort();
20
- if (ids.length === 0) {
21
- return [];
22
- }
23
- let api = apiProxy ?? "https://hermes.pyth.network/v2/updates/price/";
24
- api = api.endsWith("/") ? api : `${api}/`;
25
- const url = new URL(api + (historicalTimestampSeconds ?? "latest"));
26
- url.searchParams.append("parsed", returnPrices ? "true" : "false");
27
- if (ignoreMissingFeeds) {
28
- url.searchParams.append("ignore_invalid_price_ids", "true");
29
- }
30
- for (const id of ids) {
31
- url.searchParams.append("ids[]", id);
32
- }
33
- const resp = await retry(
34
- async () => {
35
- const resp2 = await customFetch(url.toString());
36
- if (!resp2.ok) {
37
- const body = await resp2.text();
38
- throw new Error(
39
- `failed to fetch pyth payloads: ${resp2.statusText}: ${body}`
40
- );
41
- }
42
- const data = await resp2.json();
43
- return data;
44
- },
45
- { attempts: 3, exponent: 2, interval: 200 }
46
- );
47
- const result = respToCalldata(resp, returnPrices);
48
- if (result.length !== ids.length) {
49
- if (ignoreMissingFeeds) {
50
- logger?.warn(`expected ${ids.length} price feeds, got ${result.length}`);
51
- } else {
52
- throw new Error(
53
- `expected ${ids.length} price feeds, got ${result.length}`
54
- );
55
- }
56
- }
57
- return result;
58
- }
59
- function respToCalldata(resp, returnPrices) {
60
- if (resp.binary.data.length === 0) {
61
- return [];
62
- }
63
- const updates = splitAccumulatorUpdates(resp.binary.data[0]);
64
- const priceMap = /* @__PURE__ */ new Map();
65
- if (returnPrices && resp.parsed) {
66
- for (const feed of resp.parsed) {
67
- const feedId = feed.id.startsWith("0x") ? feed.id : `0x${feed.id}`;
68
- priceMap.set(feedId.toLowerCase(), {
69
- price: BigInt(feed.price.price),
70
- decimals: Math.abs(feed.price.expo)
71
- });
72
- }
73
- }
74
- return updates.map(({ data, dataFeedId, timestamp }) => {
75
- const base = {
76
- dataFeedId,
77
- data: encodeAbiParameters(
78
- [{ type: "uint256" }, { type: "bytes[]" }],
79
- [BigInt(timestamp), [data]]
80
- ),
81
- timestamp,
82
- cached: false
83
- };
84
- if (returnPrices) {
85
- const priceInfo = priceMap.get(dataFeedId.toLowerCase());
86
- if (!priceInfo) {
87
- throw new Error(`Price info not found for feed ${dataFeedId}`);
88
- }
89
- return {
90
- ...base,
91
- price: priceInfo.price,
92
- decimals: priceInfo.decimals
93
- };
94
- }
95
- return base;
96
- });
97
- }
98
- function splitAccumulatorUpdates(binary) {
99
- const data = Buffer.from(binary, "hex");
100
- const parsed = parseAccumulatorUpdateData(data);
101
- const results = [];
102
- for (let i = 0; i < parsed.updates.length; i++) {
103
- const upd = parsed.updates[i].message;
104
- const msg = parsePriceFeedMessage(upd);
105
- results.push({
106
- dataFeedId: toHex(msg.feedId),
107
- timestamp: msg.publishTime.toNumber(),
108
- data: toHex(sliceAccumulatorUpdateData(data, i, i + 1))
109
- });
110
- }
111
- return results;
112
- }
113
- export {
114
- fetchPythPayloads
115
- };
@@ -1,13 +0,0 @@
1
- import type { IPriceUpdateTx, RawTx } from "../../../types/index.js";
2
- import type { IPriceUpdateTask } from "./types.js";
3
- export declare abstract class PriceUpdateTx<T extends IPriceUpdateTask = IPriceUpdateTask> implements IPriceUpdateTx<T> {
4
- /**
5
- * raw tx with encoded `priceFeed.updatePrice` call
6
- */
7
- readonly raw: RawTx;
8
- readonly data: T;
9
- abstract readonly name: string;
10
- constructor(raw: RawTx, data: T);
11
- get pretty(): string;
12
- validateTimestamp(blockTimestamp: bigint): "valid" | "too old" | "in future";
13
- }
@@ -1,24 +0,0 @@
1
- import type { TimestampedCalldata } from "./types.js";
2
- export interface PriceUpdatesCacheOptions {
3
- /**
4
- * Assume that in historical mode we only need to fetch once and then reuse from cache forever
5
- */
6
- historical: boolean;
7
- /**
8
- * TTL in milliseconds
9
- */
10
- ttl: number;
11
- }
12
- export declare class PriceUpdatesCache {
13
- #private;
14
- /**
15
- * Price update caches can be shared across networks
16
- * @param id - unique key to identify the cache
17
- * @param opts
18
- * @returns
19
- */
20
- static get(id: string, opts: PriceUpdatesCacheOptions): PriceUpdatesCache;
21
- private constructor();
22
- get(...path: Array<number | string>): Omit<TimestampedCalldata, "cached"> | undefined;
23
- set(value: Omit<TimestampedCalldata, "cached">, ...path: Array<number | string>): void;
24
- }