@gearbox-protocol/sdk 12.5.0 → 12.6.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.
- package/dist/cjs/sdk/market/pricefeeds/updates/PythUpdater.js +16 -74
- package/dist/cjs/sdk/market/pricefeeds/updates/RedstoneUpdater.js +9 -96
- package/dist/cjs/sdk/market/pricefeeds/updates/fetchPythPayloads.js +111 -0
- package/dist/cjs/sdk/market/pricefeeds/updates/fetchRedstonePayloads.js +144 -0
- package/dist/cjs/sdk/market/pricefeeds/updates/index.js +6 -1
- package/dist/esm/sdk/market/pricefeeds/updates/PythUpdater.js +16 -78
- package/dist/esm/sdk/market/pricefeeds/updates/RedstoneUpdater.js +10 -99
- package/dist/esm/sdk/market/pricefeeds/updates/fetchPythPayloads.js +91 -0
- package/dist/esm/sdk/market/pricefeeds/updates/fetchRedstonePayloads.js +124 -0
- package/dist/esm/sdk/market/pricefeeds/updates/index.js +2 -0
- package/dist/types/sdk/market/pricefeeds/updates/fetchPythPayloads.d.ts +30 -0
- package/dist/types/sdk/market/pricefeeds/updates/fetchRedstonePayloads.d.ts +44 -0
- package/dist/types/sdk/market/pricefeeds/updates/index.d.ts +2 -0
- package/package.json +1 -1
|
@@ -22,14 +22,11 @@ __export(PythUpdater_exports, {
|
|
|
22
22
|
PythUpdater: () => PythUpdater
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(PythUpdater_exports);
|
|
25
|
-
var import_buffer = require("buffer");
|
|
26
|
-
var import_viem = require("viem");
|
|
27
25
|
var import_v4 = require("zod/v4");
|
|
28
26
|
var import_base = require("../../../base/index.js");
|
|
29
|
-
var
|
|
27
|
+
var import_fetchPythPayloads = require("./fetchPythPayloads.js");
|
|
30
28
|
var import_PriceUpdatesCache = require("./PriceUpdatesCache.js");
|
|
31
29
|
var import_PriceUpdateTx = require("./PriceUpdateTx.js");
|
|
32
|
-
var import_PythAccumulatorUpdateData = require("./PythAccumulatorUpdateData.js");
|
|
33
30
|
class PythUpdateTx extends import_PriceUpdateTx.PriceUpdateTx {
|
|
34
31
|
name = "pyth";
|
|
35
32
|
}
|
|
@@ -58,16 +55,15 @@ const PythOptions = import_v4.z.object({
|
|
|
58
55
|
class PythUpdater extends import_base.SDKConstruct {
|
|
59
56
|
#cache;
|
|
60
57
|
#historicalTimestamp;
|
|
61
|
-
#
|
|
58
|
+
#apiProxy;
|
|
62
59
|
#ignoreMissingFeeds;
|
|
63
60
|
constructor(sdk, opts = {}) {
|
|
64
61
|
super(sdk);
|
|
65
|
-
|
|
66
|
-
this.#
|
|
67
|
-
this.#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.#historicalTimestamp = ts === true ? Number(this.sdk.timestamp) : ts;
|
|
62
|
+
const { apiProxy, cacheTTL, ignoreMissingFeeds, historicTimestamp } = opts;
|
|
63
|
+
this.#ignoreMissingFeeds = ignoreMissingFeeds;
|
|
64
|
+
this.#apiProxy = apiProxy;
|
|
65
|
+
if (historicTimestamp) {
|
|
66
|
+
this.#historicalTimestamp = historicTimestamp === true ? Number(this.sdk.timestamp) : historicTimestamp;
|
|
71
67
|
this.logger?.debug(
|
|
72
68
|
`using historical timestamp ${this.#historicalTimestamp}`
|
|
73
69
|
);
|
|
@@ -76,8 +72,8 @@ class PythUpdater extends import_base.SDKConstruct {
|
|
|
76
72
|
// currently staleness period is 240 seconds on all networks, add some buffer
|
|
77
73
|
// this period of 4 minutes is selected based on time that is required for user to sign transaction with wallet
|
|
78
74
|
// so it's unlikely to decrease
|
|
79
|
-
ttl:
|
|
80
|
-
historical: !!
|
|
75
|
+
ttl: cacheTTL ?? 225 * 1e3,
|
|
76
|
+
historical: !!historicTimestamp
|
|
81
77
|
});
|
|
82
78
|
}
|
|
83
79
|
async getUpdateTxs(feeds) {
|
|
@@ -168,72 +164,18 @@ class PythUpdater extends import_base.SDKConstruct {
|
|
|
168
164
|
this.logger?.debug(
|
|
169
165
|
`fetching pyth payloads for ${dataFeedsIds.size} price feeds: ${ids.join(", ")}${tsStr}`
|
|
170
166
|
);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
const resp = await (0, import_utils.retry)(
|
|
180
|
-
async () => {
|
|
181
|
-
const resp2 = await fetch(url.toString());
|
|
182
|
-
if (!resp2.ok) {
|
|
183
|
-
const body = await resp2.text();
|
|
184
|
-
throw new Error(
|
|
185
|
-
`failed to fetch pyth payloads: ${resp2.statusText}: ${body}`
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
const data = await resp2.json();
|
|
189
|
-
return data;
|
|
190
|
-
},
|
|
191
|
-
{ attempts: 3, exponent: 2, interval: 200 }
|
|
192
|
-
);
|
|
193
|
-
const result = respToCalldata(resp);
|
|
194
|
-
if (!this.#ignoreMissingFeeds && result.length !== dataFeedsIds.size) {
|
|
195
|
-
throw new Error(
|
|
196
|
-
`expected ${dataFeedsIds.size} price feeds, got ${result.length}`
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
return result;
|
|
167
|
+
return (0, import_fetchPythPayloads.fetchPythPayloads)({
|
|
168
|
+
dataFeedsIds,
|
|
169
|
+
historicalTimestampSeconds: this.#historicalTimestamp,
|
|
170
|
+
apiProxy: this.#apiProxy,
|
|
171
|
+
ignoreMissingFeeds: this.#ignoreMissingFeeds,
|
|
172
|
+
logger: this.logger
|
|
173
|
+
});
|
|
200
174
|
}
|
|
201
175
|
}
|
|
202
176
|
function isPyth(pf) {
|
|
203
177
|
return pf.contractType === "PRICE_FEED::PYTH";
|
|
204
178
|
}
|
|
205
|
-
function respToCalldata(resp) {
|
|
206
|
-
if (resp.binary.data.length === 0) {
|
|
207
|
-
return [];
|
|
208
|
-
}
|
|
209
|
-
const updates = splitAccumulatorUpdates(resp.binary.data[0]);
|
|
210
|
-
return updates.map(({ data, dataFeedId, timestamp }) => {
|
|
211
|
-
return {
|
|
212
|
-
dataFeedId,
|
|
213
|
-
data: (0, import_viem.encodeAbiParameters)(
|
|
214
|
-
[{ type: "uint256" }, { type: "bytes[]" }],
|
|
215
|
-
[BigInt(timestamp), [data]]
|
|
216
|
-
),
|
|
217
|
-
timestamp,
|
|
218
|
-
cached: false
|
|
219
|
-
};
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
function splitAccumulatorUpdates(binary) {
|
|
223
|
-
const data = import_buffer.Buffer.from(binary, "hex");
|
|
224
|
-
const parsed = (0, import_PythAccumulatorUpdateData.parseAccumulatorUpdateData)(data);
|
|
225
|
-
const results = [];
|
|
226
|
-
for (let i = 0; i < parsed.updates.length; i++) {
|
|
227
|
-
const upd = parsed.updates[i].message;
|
|
228
|
-
const msg = (0, import_PythAccumulatorUpdateData.parsePriceFeedMessage)(upd);
|
|
229
|
-
results.push({
|
|
230
|
-
dataFeedId: (0, import_viem.toHex)(msg.feedId),
|
|
231
|
-
timestamp: msg.publishTime.toNumber(),
|
|
232
|
-
data: (0, import_viem.toHex)((0, import_PythAccumulatorUpdateData.sliceAccumulatorUpdateData)(data, i, i + 1))
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
return results;
|
|
236
|
-
}
|
|
237
179
|
// Annotate the CommonJS export names for ESM import in node:
|
|
238
180
|
0 && (module.exports = {
|
|
239
181
|
PythOptions,
|
|
@@ -22,13 +22,10 @@ __export(RedstoneUpdater_exports, {
|
|
|
22
22
|
RedstoneUpdater: () => RedstoneUpdater
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(RedstoneUpdater_exports);
|
|
25
|
-
var import_evm_connector = require("@redstone-finance/evm-connector");
|
|
26
|
-
var import_protocol = require("@redstone-finance/protocol");
|
|
27
|
-
var import_sdk = require("@redstone-finance/sdk");
|
|
28
|
-
var import_viem = require("viem");
|
|
29
25
|
var import_v4 = require("zod/v4");
|
|
30
26
|
var import_base = require("../../../base/index.js");
|
|
31
27
|
var import_utils = require("../../../utils/index.js");
|
|
28
|
+
var import_fetchRedstonePayloads = require("./fetchRedstonePayloads.js");
|
|
32
29
|
var import_PriceUpdatesCache = require("./PriceUpdatesCache.js");
|
|
33
30
|
var import_PriceUpdateTx = require("./PriceUpdateTx.js");
|
|
34
31
|
class RedstoneUpdateTx extends import_PriceUpdateTx.PriceUpdateTx {
|
|
@@ -244,102 +241,18 @@ class RedstoneUpdater extends import_base.SDKConstruct {
|
|
|
244
241
|
this.logger?.debug(
|
|
245
242
|
`fetching redstone payloads for ${dataFeedsIds.size} data feeds in ${dataServiceId} with ${uniqueSignersCount} signers: ${dataPackagesIds.join(", ")}${tsStr}`
|
|
246
243
|
);
|
|
247
|
-
|
|
244
|
+
return (0, import_fetchRedstonePayloads.fetchRedstonePayloads)({
|
|
248
245
|
dataServiceId,
|
|
249
|
-
|
|
246
|
+
dataFeedsIds,
|
|
250
247
|
uniqueSignersCount,
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
enableEnhancedLogs: this.#enableLogging
|
|
248
|
+
historicalTimestampMs: this.#historicalTimestampMs,
|
|
249
|
+
gateways: this.#gateways,
|
|
250
|
+
ignoreMissingFeeds: this.#ignoreMissingFeeds,
|
|
251
|
+
enableLogging: this.#enableLogging,
|
|
252
|
+
logger: this.logger,
|
|
253
|
+
metadataTimestampMs: Number(this.sdk.timestamp) * 1e3
|
|
258
254
|
});
|
|
259
|
-
wrapper.setMetadataTimestamp(
|
|
260
|
-
this.#historicalTimestampMs ?? Number(this.sdk.timestamp) * 1e3
|
|
261
|
-
);
|
|
262
|
-
const dataPayload = await (0, import_utils.retry)(
|
|
263
|
-
() => wrapper.prepareRedstonePayload(true),
|
|
264
|
-
{ attempts: 5, interval: this.#historicalTimestampMs ? 30500 : 250 }
|
|
265
|
-
);
|
|
266
|
-
const parsed = import_protocol.RedstonePayload.parse((0, import_viem.toBytes)(`0x${dataPayload}`));
|
|
267
|
-
const packagesByDataFeedId = groupDataPackages(parsed.signedDataPackages);
|
|
268
|
-
const result = [];
|
|
269
|
-
for (const dataFeedId of dataFeedsIds) {
|
|
270
|
-
const signedDataPackages = packagesByDataFeedId[dataFeedId];
|
|
271
|
-
if (!signedDataPackages) {
|
|
272
|
-
if (this.#ignoreMissingFeeds) {
|
|
273
|
-
this.logger?.warn(`cannot find data packages for ${dataFeedId}`);
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
throw new Error(`cannot find data packages for ${dataFeedId}`);
|
|
277
|
-
}
|
|
278
|
-
if (signedDataPackages.length !== uniqueSignersCount) {
|
|
279
|
-
if (this.#ignoreMissingFeeds) {
|
|
280
|
-
this.logger?.warn(
|
|
281
|
-
`got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
|
|
282
|
-
);
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
throw new Error(
|
|
286
|
-
`got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
result.push(
|
|
290
|
-
getCalldataWithTimestamp(
|
|
291
|
-
dataFeedId,
|
|
292
|
-
signedDataPackages,
|
|
293
|
-
wrapper.getUnsignedMetadata()
|
|
294
|
-
)
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
return result;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
function groupDataPackages(signedDataPackages) {
|
|
301
|
-
const packagesByDataFeedId = {};
|
|
302
|
-
for (const p of signedDataPackages) {
|
|
303
|
-
const { dataPoints } = p.dataPackage;
|
|
304
|
-
const dataFeedId0 = dataPoints[0].dataFeedId;
|
|
305
|
-
for (const dp of dataPoints) {
|
|
306
|
-
if (dp.dataFeedId !== dataFeedId0) {
|
|
307
|
-
throw new Error(
|
|
308
|
-
`data package contains data points with different dataFeedIds: ${dp.dataFeedId} and ${dataFeedId0}`
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
if (!packagesByDataFeedId[dataFeedId0]) {
|
|
313
|
-
packagesByDataFeedId[dataFeedId0] = [];
|
|
314
|
-
}
|
|
315
|
-
packagesByDataFeedId[dataFeedId0].push(p);
|
|
316
|
-
}
|
|
317
|
-
return packagesByDataFeedId;
|
|
318
|
-
}
|
|
319
|
-
function getCalldataWithTimestamp(dataFeedId, packages, unsignedMetadata) {
|
|
320
|
-
const originalPayload = import_protocol.RedstonePayload.prepare(packages, unsignedMetadata);
|
|
321
|
-
const originalPayloadLength = originalPayload.length / 2;
|
|
322
|
-
const bytesToAdd = 32 - originalPayloadLength % 32;
|
|
323
|
-
const newUnsignedMetadata = unsignedMetadata + "_".repeat(bytesToAdd);
|
|
324
|
-
const payload = import_protocol.RedstonePayload.prepare(packages, newUnsignedMetadata);
|
|
325
|
-
let timestamp = 0;
|
|
326
|
-
for (const p of packages) {
|
|
327
|
-
const newTimestamp = p.dataPackage.timestampMilliseconds / 1e3;
|
|
328
|
-
if (timestamp === 0) {
|
|
329
|
-
timestamp = newTimestamp;
|
|
330
|
-
} else if (timestamp !== newTimestamp) {
|
|
331
|
-
throw new Error("Timestamps are not equal");
|
|
332
|
-
}
|
|
333
255
|
}
|
|
334
|
-
return {
|
|
335
|
-
dataFeedId,
|
|
336
|
-
data: (0, import_viem.encodeAbiParameters)(
|
|
337
|
-
[{ type: "uint256" }, { type: "bytes" }],
|
|
338
|
-
[BigInt(timestamp), `0x${payload}`]
|
|
339
|
-
),
|
|
340
|
-
timestamp,
|
|
341
|
-
cached: false
|
|
342
|
-
};
|
|
343
256
|
}
|
|
344
257
|
function isRedstone(pf) {
|
|
345
258
|
return pf.contractType === "PRICE_FEED::REDSTONE";
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
} = options;
|
|
36
|
+
const ids = Array.from(new Set(dataFeedsIds));
|
|
37
|
+
if (ids.length === 0) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
let api = apiProxy ?? "https://hermes.pyth.network/v2/updates/price/";
|
|
41
|
+
api = api.endsWith("/") ? api : `${api}/`;
|
|
42
|
+
const url = new URL(api + (historicalTimestampSeconds ?? "latest"));
|
|
43
|
+
url.searchParams.append("parsed", "false");
|
|
44
|
+
if (ignoreMissingFeeds) {
|
|
45
|
+
url.searchParams.append("ignore_invalid_price_ids", "true");
|
|
46
|
+
}
|
|
47
|
+
for (const id of dataFeedsIds) {
|
|
48
|
+
url.searchParams.append("ids[]", id);
|
|
49
|
+
}
|
|
50
|
+
const resp = await (0, import_utils.retry)(
|
|
51
|
+
async () => {
|
|
52
|
+
const resp2 = await fetch(url.toString());
|
|
53
|
+
if (!resp2.ok) {
|
|
54
|
+
const body = await resp2.text();
|
|
55
|
+
throw new Error(
|
|
56
|
+
`failed to fetch pyth payloads: ${resp2.statusText}: ${body}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const data = await resp2.json();
|
|
60
|
+
return data;
|
|
61
|
+
},
|
|
62
|
+
{ attempts: 3, exponent: 2, interval: 200 }
|
|
63
|
+
);
|
|
64
|
+
const result = respToCalldata(resp);
|
|
65
|
+
if (result.length !== ids.length) {
|
|
66
|
+
if (ignoreMissingFeeds) {
|
|
67
|
+
logger?.warn(`expected ${ids.length} price feeds, got ${result.length}`);
|
|
68
|
+
} else {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`expected ${ids.length} price feeds, got ${result.length}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function respToCalldata(resp) {
|
|
77
|
+
if (resp.binary.data.length === 0) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const updates = splitAccumulatorUpdates(resp.binary.data[0]);
|
|
81
|
+
return updates.map(({ data, dataFeedId, timestamp }) => {
|
|
82
|
+
return {
|
|
83
|
+
dataFeedId,
|
|
84
|
+
data: (0, import_viem.encodeAbiParameters)(
|
|
85
|
+
[{ type: "uint256" }, { type: "bytes[]" }],
|
|
86
|
+
[BigInt(timestamp), [data]]
|
|
87
|
+
),
|
|
88
|
+
timestamp,
|
|
89
|
+
cached: false
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function splitAccumulatorUpdates(binary) {
|
|
94
|
+
const data = import_buffer.Buffer.from(binary, "hex");
|
|
95
|
+
const parsed = (0, import_PythAccumulatorUpdateData.parseAccumulatorUpdateData)(data);
|
|
96
|
+
const results = [];
|
|
97
|
+
for (let i = 0; i < parsed.updates.length; i++) {
|
|
98
|
+
const upd = parsed.updates[i].message;
|
|
99
|
+
const msg = (0, import_PythAccumulatorUpdateData.parsePriceFeedMessage)(upd);
|
|
100
|
+
results.push({
|
|
101
|
+
dataFeedId: (0, import_viem.toHex)(msg.feedId),
|
|
102
|
+
timestamp: msg.publishTime.toNumber(),
|
|
103
|
+
data: (0, import_viem.toHex)((0, import_PythAccumulatorUpdateData.sliceAccumulatorUpdateData)(data, i, i + 1))
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
108
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
109
|
+
0 && (module.exports = {
|
|
110
|
+
fetchPythPayloads
|
|
111
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
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 fetchRedstonePayloads_exports = {};
|
|
20
|
+
__export(fetchRedstonePayloads_exports, {
|
|
21
|
+
fetchRedstonePayloads: () => fetchRedstonePayloads
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(fetchRedstonePayloads_exports);
|
|
24
|
+
var import_evm_connector = require("@redstone-finance/evm-connector");
|
|
25
|
+
var import_protocol = require("@redstone-finance/protocol");
|
|
26
|
+
var import_sdk = require("@redstone-finance/sdk");
|
|
27
|
+
var import_viem = require("viem");
|
|
28
|
+
var import_utils = require("../../../utils/index.js");
|
|
29
|
+
async function fetchRedstonePayloads(options) {
|
|
30
|
+
const {
|
|
31
|
+
dataServiceId,
|
|
32
|
+
dataFeedsIds,
|
|
33
|
+
uniqueSignersCount,
|
|
34
|
+
historicalTimestampMs,
|
|
35
|
+
gateways,
|
|
36
|
+
ignoreMissingFeeds,
|
|
37
|
+
enableLogging,
|
|
38
|
+
logger
|
|
39
|
+
} = options;
|
|
40
|
+
const metadataTimestampMs = historicalTimestampMs ?? options.metadataTimestampMs;
|
|
41
|
+
const dataPackagesIds = Array.from(new Set(dataFeedsIds));
|
|
42
|
+
if (dataPackagesIds.length === 0) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
const wrapper = new import_evm_connector.DataServiceWrapper({
|
|
46
|
+
dataServiceId,
|
|
47
|
+
dataPackagesIds,
|
|
48
|
+
uniqueSignersCount,
|
|
49
|
+
authorizedSigners: (0, import_sdk.getSignersForDataServiceId)(
|
|
50
|
+
dataServiceId
|
|
51
|
+
),
|
|
52
|
+
historicalTimestamp: historicalTimestampMs,
|
|
53
|
+
urls: gateways,
|
|
54
|
+
ignoreMissingFeed: ignoreMissingFeeds,
|
|
55
|
+
enableEnhancedLogs: enableLogging
|
|
56
|
+
});
|
|
57
|
+
if (metadataTimestampMs) {
|
|
58
|
+
wrapper.setMetadataTimestamp(metadataTimestampMs);
|
|
59
|
+
}
|
|
60
|
+
const dataPayload = await (0, import_utils.retry)(() => wrapper.prepareRedstonePayload(true), {
|
|
61
|
+
attempts: 5,
|
|
62
|
+
interval: historicalTimestampMs ? 30500 : 250
|
|
63
|
+
});
|
|
64
|
+
const parsed = import_protocol.RedstonePayload.parse((0, import_viem.toBytes)(`0x${dataPayload}`));
|
|
65
|
+
const packagesByDataFeedId = groupDataPackages(parsed.signedDataPackages);
|
|
66
|
+
const result = [];
|
|
67
|
+
for (const dataFeedId of dataFeedsIds) {
|
|
68
|
+
const signedDataPackages = packagesByDataFeedId[dataFeedId];
|
|
69
|
+
if (!signedDataPackages) {
|
|
70
|
+
if (ignoreMissingFeeds) {
|
|
71
|
+
logger?.warn(`cannot find data packages for ${dataFeedId}`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`cannot find data packages for ${dataFeedId}`);
|
|
75
|
+
}
|
|
76
|
+
if (signedDataPackages.length !== uniqueSignersCount) {
|
|
77
|
+
if (ignoreMissingFeeds) {
|
|
78
|
+
logger?.warn(
|
|
79
|
+
`got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
|
|
80
|
+
);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
throw new Error(
|
|
84
|
+
`got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
result.push(
|
|
88
|
+
getCalldataWithTimestamp(
|
|
89
|
+
dataFeedId,
|
|
90
|
+
signedDataPackages,
|
|
91
|
+
wrapper.getUnsignedMetadata()
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
function groupDataPackages(signedDataPackages) {
|
|
98
|
+
const packagesByDataFeedId = {};
|
|
99
|
+
for (const p of signedDataPackages) {
|
|
100
|
+
const { dataPoints } = p.dataPackage;
|
|
101
|
+
const dataFeedId0 = dataPoints[0].dataFeedId;
|
|
102
|
+
for (const dp of dataPoints) {
|
|
103
|
+
if (dp.dataFeedId !== dataFeedId0) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`data package contains data points with different dataFeedIds: ${dp.dataFeedId} and ${dataFeedId0}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!packagesByDataFeedId[dataFeedId0]) {
|
|
110
|
+
packagesByDataFeedId[dataFeedId0] = [];
|
|
111
|
+
}
|
|
112
|
+
packagesByDataFeedId[dataFeedId0].push(p);
|
|
113
|
+
}
|
|
114
|
+
return packagesByDataFeedId;
|
|
115
|
+
}
|
|
116
|
+
function getCalldataWithTimestamp(dataFeedId, packages, unsignedMetadata) {
|
|
117
|
+
const originalPayload = import_protocol.RedstonePayload.prepare(packages, unsignedMetadata);
|
|
118
|
+
const originalPayloadLength = originalPayload.length / 2;
|
|
119
|
+
const bytesToAdd = 32 - originalPayloadLength % 32;
|
|
120
|
+
const newUnsignedMetadata = unsignedMetadata + "_".repeat(bytesToAdd);
|
|
121
|
+
const payload = import_protocol.RedstonePayload.prepare(packages, newUnsignedMetadata);
|
|
122
|
+
let timestamp = 0;
|
|
123
|
+
for (const p of packages) {
|
|
124
|
+
const newTimestamp = p.dataPackage.timestampMilliseconds / 1e3;
|
|
125
|
+
if (timestamp === 0) {
|
|
126
|
+
timestamp = newTimestamp;
|
|
127
|
+
} else if (timestamp !== newTimestamp) {
|
|
128
|
+
throw new Error("Timestamps are not equal");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
dataFeedId,
|
|
133
|
+
data: (0, import_viem.encodeAbiParameters)(
|
|
134
|
+
[{ type: "uint256" }, { type: "bytes" }],
|
|
135
|
+
[BigInt(timestamp), `0x${payload}`]
|
|
136
|
+
),
|
|
137
|
+
timestamp,
|
|
138
|
+
cached: false
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
142
|
+
0 && (module.exports = {
|
|
143
|
+
fetchRedstonePayloads
|
|
144
|
+
});
|
|
@@ -15,6 +15,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
18
19
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
20
|
var updates_exports = {};
|
|
20
21
|
__export(updates_exports, {
|
|
@@ -24,6 +25,8 @@ __export(updates_exports, {
|
|
|
24
25
|
RedstoneUpdater: () => import_RedstoneUpdater.RedstoneUpdater
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(updates_exports);
|
|
28
|
+
__reExport(updates_exports, require("./fetchPythPayloads.js"), module.exports);
|
|
29
|
+
__reExport(updates_exports, require("./fetchRedstonePayloads.js"), module.exports);
|
|
27
30
|
var import_PythUpdater = require("./PythUpdater.js");
|
|
28
31
|
var import_RedstoneUpdater = require("./RedstoneUpdater.js");
|
|
29
32
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -31,5 +34,7 @@ var import_RedstoneUpdater = require("./RedstoneUpdater.js");
|
|
|
31
34
|
PythOptions,
|
|
32
35
|
PythUpdater,
|
|
33
36
|
RedstoneOptions,
|
|
34
|
-
RedstoneUpdater
|
|
37
|
+
RedstoneUpdater,
|
|
38
|
+
...require("./fetchPythPayloads.js"),
|
|
39
|
+
...require("./fetchRedstonePayloads.js")
|
|
35
40
|
});
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import { Buffer } from "buffer";
|
|
2
|
-
import { encodeAbiParameters, toHex } from "viem";
|
|
3
1
|
import { z } from "zod/v4";
|
|
4
2
|
import { SDKConstruct } from "../../../base/index.js";
|
|
5
|
-
import {
|
|
3
|
+
import { fetchPythPayloads } from "./fetchPythPayloads.js";
|
|
6
4
|
import { PriceUpdatesCache } from "./PriceUpdatesCache.js";
|
|
7
5
|
import { PriceUpdateTx } from "./PriceUpdateTx.js";
|
|
8
|
-
import {
|
|
9
|
-
parseAccumulatorUpdateData,
|
|
10
|
-
parsePriceFeedMessage,
|
|
11
|
-
sliceAccumulatorUpdateData
|
|
12
|
-
} from "./PythAccumulatorUpdateData.js";
|
|
13
6
|
class PythUpdateTx extends PriceUpdateTx {
|
|
14
7
|
name = "pyth";
|
|
15
8
|
}
|
|
@@ -38,16 +31,15 @@ const PythOptions = z.object({
|
|
|
38
31
|
class PythUpdater extends SDKConstruct {
|
|
39
32
|
#cache;
|
|
40
33
|
#historicalTimestamp;
|
|
41
|
-
#
|
|
34
|
+
#apiProxy;
|
|
42
35
|
#ignoreMissingFeeds;
|
|
43
36
|
constructor(sdk, opts = {}) {
|
|
44
37
|
super(sdk);
|
|
45
|
-
|
|
46
|
-
this.#
|
|
47
|
-
this.#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.#historicalTimestamp = ts === true ? Number(this.sdk.timestamp) : ts;
|
|
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;
|
|
51
43
|
this.logger?.debug(
|
|
52
44
|
`using historical timestamp ${this.#historicalTimestamp}`
|
|
53
45
|
);
|
|
@@ -56,8 +48,8 @@ class PythUpdater extends SDKConstruct {
|
|
|
56
48
|
// currently staleness period is 240 seconds on all networks, add some buffer
|
|
57
49
|
// this period of 4 minutes is selected based on time that is required for user to sign transaction with wallet
|
|
58
50
|
// so it's unlikely to decrease
|
|
59
|
-
ttl:
|
|
60
|
-
historical: !!
|
|
51
|
+
ttl: cacheTTL ?? 225 * 1e3,
|
|
52
|
+
historical: !!historicTimestamp
|
|
61
53
|
});
|
|
62
54
|
}
|
|
63
55
|
async getUpdateTxs(feeds) {
|
|
@@ -148,72 +140,18 @@ class PythUpdater extends SDKConstruct {
|
|
|
148
140
|
this.logger?.debug(
|
|
149
141
|
`fetching pyth payloads for ${dataFeedsIds.size} price feeds: ${ids.join(", ")}${tsStr}`
|
|
150
142
|
);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
const resp = await retry(
|
|
160
|
-
async () => {
|
|
161
|
-
const resp2 = await fetch(url.toString());
|
|
162
|
-
if (!resp2.ok) {
|
|
163
|
-
const body = await resp2.text();
|
|
164
|
-
throw new Error(
|
|
165
|
-
`failed to fetch pyth payloads: ${resp2.statusText}: ${body}`
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
const data = await resp2.json();
|
|
169
|
-
return data;
|
|
170
|
-
},
|
|
171
|
-
{ attempts: 3, exponent: 2, interval: 200 }
|
|
172
|
-
);
|
|
173
|
-
const result = respToCalldata(resp);
|
|
174
|
-
if (!this.#ignoreMissingFeeds && result.length !== dataFeedsIds.size) {
|
|
175
|
-
throw new Error(
|
|
176
|
-
`expected ${dataFeedsIds.size} price feeds, got ${result.length}`
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
return result;
|
|
143
|
+
return fetchPythPayloads({
|
|
144
|
+
dataFeedsIds,
|
|
145
|
+
historicalTimestampSeconds: this.#historicalTimestamp,
|
|
146
|
+
apiProxy: this.#apiProxy,
|
|
147
|
+
ignoreMissingFeeds: this.#ignoreMissingFeeds,
|
|
148
|
+
logger: this.logger
|
|
149
|
+
});
|
|
180
150
|
}
|
|
181
151
|
}
|
|
182
152
|
function isPyth(pf) {
|
|
183
153
|
return pf.contractType === "PRICE_FEED::PYTH";
|
|
184
154
|
}
|
|
185
|
-
function respToCalldata(resp) {
|
|
186
|
-
if (resp.binary.data.length === 0) {
|
|
187
|
-
return [];
|
|
188
|
-
}
|
|
189
|
-
const updates = splitAccumulatorUpdates(resp.binary.data[0]);
|
|
190
|
-
return updates.map(({ data, dataFeedId, timestamp }) => {
|
|
191
|
-
return {
|
|
192
|
-
dataFeedId,
|
|
193
|
-
data: encodeAbiParameters(
|
|
194
|
-
[{ type: "uint256" }, { type: "bytes[]" }],
|
|
195
|
-
[BigInt(timestamp), [data]]
|
|
196
|
-
),
|
|
197
|
-
timestamp,
|
|
198
|
-
cached: false
|
|
199
|
-
};
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
function splitAccumulatorUpdates(binary) {
|
|
203
|
-
const data = Buffer.from(binary, "hex");
|
|
204
|
-
const parsed = parseAccumulatorUpdateData(data);
|
|
205
|
-
const results = [];
|
|
206
|
-
for (let i = 0; i < parsed.updates.length; i++) {
|
|
207
|
-
const upd = parsed.updates[i].message;
|
|
208
|
-
const msg = parsePriceFeedMessage(upd);
|
|
209
|
-
results.push({
|
|
210
|
-
dataFeedId: toHex(msg.feedId),
|
|
211
|
-
timestamp: msg.publishTime.toNumber(),
|
|
212
|
-
data: toHex(sliceAccumulatorUpdateData(data, i, i + 1))
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
return results;
|
|
216
|
-
}
|
|
217
155
|
export {
|
|
218
156
|
PythOptions,
|
|
219
157
|
PythUpdater
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import { DataServiceWrapper } from "@redstone-finance/evm-connector";
|
|
2
|
-
import { RedstonePayload } from "@redstone-finance/protocol";
|
|
3
|
-
import {
|
|
4
|
-
getSignersForDataServiceId
|
|
5
|
-
} from "@redstone-finance/sdk";
|
|
6
|
-
import { encodeAbiParameters, toBytes } from "viem";
|
|
7
1
|
import { z } from "zod/v4";
|
|
8
2
|
import { SDKConstruct } from "../../../base/index.js";
|
|
9
|
-
import { AddressMap
|
|
3
|
+
import { AddressMap } from "../../../utils/index.js";
|
|
4
|
+
import { fetchRedstonePayloads } from "./fetchRedstonePayloads.js";
|
|
10
5
|
import { PriceUpdatesCache } from "./PriceUpdatesCache.js";
|
|
11
6
|
import { PriceUpdateTx } from "./PriceUpdateTx.js";
|
|
12
7
|
class RedstoneUpdateTx extends PriceUpdateTx {
|
|
@@ -222,102 +217,18 @@ class RedstoneUpdater extends SDKConstruct {
|
|
|
222
217
|
this.logger?.debug(
|
|
223
218
|
`fetching redstone payloads for ${dataFeedsIds.size} data feeds in ${dataServiceId} with ${uniqueSignersCount} signers: ${dataPackagesIds.join(", ")}${tsStr}`
|
|
224
219
|
);
|
|
225
|
-
|
|
220
|
+
return fetchRedstonePayloads({
|
|
226
221
|
dataServiceId,
|
|
227
|
-
|
|
222
|
+
dataFeedsIds,
|
|
228
223
|
uniqueSignersCount,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
enableEnhancedLogs: this.#enableLogging
|
|
224
|
+
historicalTimestampMs: this.#historicalTimestampMs,
|
|
225
|
+
gateways: this.#gateways,
|
|
226
|
+
ignoreMissingFeeds: this.#ignoreMissingFeeds,
|
|
227
|
+
enableLogging: this.#enableLogging,
|
|
228
|
+
logger: this.logger,
|
|
229
|
+
metadataTimestampMs: Number(this.sdk.timestamp) * 1e3
|
|
236
230
|
});
|
|
237
|
-
wrapper.setMetadataTimestamp(
|
|
238
|
-
this.#historicalTimestampMs ?? Number(this.sdk.timestamp) * 1e3
|
|
239
|
-
);
|
|
240
|
-
const dataPayload = await retry(
|
|
241
|
-
() => wrapper.prepareRedstonePayload(true),
|
|
242
|
-
{ attempts: 5, interval: this.#historicalTimestampMs ? 30500 : 250 }
|
|
243
|
-
);
|
|
244
|
-
const parsed = RedstonePayload.parse(toBytes(`0x${dataPayload}`));
|
|
245
|
-
const packagesByDataFeedId = groupDataPackages(parsed.signedDataPackages);
|
|
246
|
-
const result = [];
|
|
247
|
-
for (const dataFeedId of dataFeedsIds) {
|
|
248
|
-
const signedDataPackages = packagesByDataFeedId[dataFeedId];
|
|
249
|
-
if (!signedDataPackages) {
|
|
250
|
-
if (this.#ignoreMissingFeeds) {
|
|
251
|
-
this.logger?.warn(`cannot find data packages for ${dataFeedId}`);
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
throw new Error(`cannot find data packages for ${dataFeedId}`);
|
|
255
|
-
}
|
|
256
|
-
if (signedDataPackages.length !== uniqueSignersCount) {
|
|
257
|
-
if (this.#ignoreMissingFeeds) {
|
|
258
|
-
this.logger?.warn(
|
|
259
|
-
`got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
|
|
260
|
-
);
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
throw new Error(
|
|
264
|
-
`got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
result.push(
|
|
268
|
-
getCalldataWithTimestamp(
|
|
269
|
-
dataFeedId,
|
|
270
|
-
signedDataPackages,
|
|
271
|
-
wrapper.getUnsignedMetadata()
|
|
272
|
-
)
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
return result;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
function groupDataPackages(signedDataPackages) {
|
|
279
|
-
const packagesByDataFeedId = {};
|
|
280
|
-
for (const p of signedDataPackages) {
|
|
281
|
-
const { dataPoints } = p.dataPackage;
|
|
282
|
-
const dataFeedId0 = dataPoints[0].dataFeedId;
|
|
283
|
-
for (const dp of dataPoints) {
|
|
284
|
-
if (dp.dataFeedId !== dataFeedId0) {
|
|
285
|
-
throw new Error(
|
|
286
|
-
`data package contains data points with different dataFeedIds: ${dp.dataFeedId} and ${dataFeedId0}`
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
if (!packagesByDataFeedId[dataFeedId0]) {
|
|
291
|
-
packagesByDataFeedId[dataFeedId0] = [];
|
|
292
|
-
}
|
|
293
|
-
packagesByDataFeedId[dataFeedId0].push(p);
|
|
294
|
-
}
|
|
295
|
-
return packagesByDataFeedId;
|
|
296
|
-
}
|
|
297
|
-
function getCalldataWithTimestamp(dataFeedId, packages, unsignedMetadata) {
|
|
298
|
-
const originalPayload = RedstonePayload.prepare(packages, unsignedMetadata);
|
|
299
|
-
const originalPayloadLength = originalPayload.length / 2;
|
|
300
|
-
const bytesToAdd = 32 - originalPayloadLength % 32;
|
|
301
|
-
const newUnsignedMetadata = unsignedMetadata + "_".repeat(bytesToAdd);
|
|
302
|
-
const payload = RedstonePayload.prepare(packages, newUnsignedMetadata);
|
|
303
|
-
let timestamp = 0;
|
|
304
|
-
for (const p of packages) {
|
|
305
|
-
const newTimestamp = p.dataPackage.timestampMilliseconds / 1e3;
|
|
306
|
-
if (timestamp === 0) {
|
|
307
|
-
timestamp = newTimestamp;
|
|
308
|
-
} else if (timestamp !== newTimestamp) {
|
|
309
|
-
throw new Error("Timestamps are not equal");
|
|
310
|
-
}
|
|
311
231
|
}
|
|
312
|
-
return {
|
|
313
|
-
dataFeedId,
|
|
314
|
-
data: encodeAbiParameters(
|
|
315
|
-
[{ type: "uint256" }, { type: "bytes" }],
|
|
316
|
-
[BigInt(timestamp), `0x${payload}`]
|
|
317
|
-
),
|
|
318
|
-
timestamp,
|
|
319
|
-
cached: false
|
|
320
|
-
};
|
|
321
232
|
}
|
|
322
233
|
function isRedstone(pf) {
|
|
323
234
|
return pf.contractType === "PRICE_FEED::REDSTONE";
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
} = options;
|
|
17
|
+
const ids = Array.from(new Set(dataFeedsIds));
|
|
18
|
+
if (ids.length === 0) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
let api = apiProxy ?? "https://hermes.pyth.network/v2/updates/price/";
|
|
22
|
+
api = api.endsWith("/") ? api : `${api}/`;
|
|
23
|
+
const url = new URL(api + (historicalTimestampSeconds ?? "latest"));
|
|
24
|
+
url.searchParams.append("parsed", "false");
|
|
25
|
+
if (ignoreMissingFeeds) {
|
|
26
|
+
url.searchParams.append("ignore_invalid_price_ids", "true");
|
|
27
|
+
}
|
|
28
|
+
for (const id of dataFeedsIds) {
|
|
29
|
+
url.searchParams.append("ids[]", id);
|
|
30
|
+
}
|
|
31
|
+
const resp = await retry(
|
|
32
|
+
async () => {
|
|
33
|
+
const resp2 = await fetch(url.toString());
|
|
34
|
+
if (!resp2.ok) {
|
|
35
|
+
const body = await resp2.text();
|
|
36
|
+
throw new Error(
|
|
37
|
+
`failed to fetch pyth payloads: ${resp2.statusText}: ${body}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const data = await resp2.json();
|
|
41
|
+
return data;
|
|
42
|
+
},
|
|
43
|
+
{ attempts: 3, exponent: 2, interval: 200 }
|
|
44
|
+
);
|
|
45
|
+
const result = respToCalldata(resp);
|
|
46
|
+
if (result.length !== ids.length) {
|
|
47
|
+
if (ignoreMissingFeeds) {
|
|
48
|
+
logger?.warn(`expected ${ids.length} price feeds, got ${result.length}`);
|
|
49
|
+
} else {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`expected ${ids.length} price feeds, got ${result.length}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
function respToCalldata(resp) {
|
|
58
|
+
if (resp.binary.data.length === 0) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const updates = splitAccumulatorUpdates(resp.binary.data[0]);
|
|
62
|
+
return updates.map(({ data, dataFeedId, timestamp }) => {
|
|
63
|
+
return {
|
|
64
|
+
dataFeedId,
|
|
65
|
+
data: encodeAbiParameters(
|
|
66
|
+
[{ type: "uint256" }, { type: "bytes[]" }],
|
|
67
|
+
[BigInt(timestamp), [data]]
|
|
68
|
+
),
|
|
69
|
+
timestamp,
|
|
70
|
+
cached: false
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function splitAccumulatorUpdates(binary) {
|
|
75
|
+
const data = Buffer.from(binary, "hex");
|
|
76
|
+
const parsed = parseAccumulatorUpdateData(data);
|
|
77
|
+
const results = [];
|
|
78
|
+
for (let i = 0; i < parsed.updates.length; i++) {
|
|
79
|
+
const upd = parsed.updates[i].message;
|
|
80
|
+
const msg = parsePriceFeedMessage(upd);
|
|
81
|
+
results.push({
|
|
82
|
+
dataFeedId: toHex(msg.feedId),
|
|
83
|
+
timestamp: msg.publishTime.toNumber(),
|
|
84
|
+
data: toHex(sliceAccumulatorUpdateData(data, i, i + 1))
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return results;
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
fetchPythPayloads
|
|
91
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { DataServiceWrapper } from "@redstone-finance/evm-connector";
|
|
2
|
+
import {
|
|
3
|
+
RedstonePayload
|
|
4
|
+
} from "@redstone-finance/protocol";
|
|
5
|
+
import {
|
|
6
|
+
getSignersForDataServiceId
|
|
7
|
+
} from "@redstone-finance/sdk";
|
|
8
|
+
import { encodeAbiParameters, toBytes } from "viem";
|
|
9
|
+
import { retry } from "../../../utils/index.js";
|
|
10
|
+
async function fetchRedstonePayloads(options) {
|
|
11
|
+
const {
|
|
12
|
+
dataServiceId,
|
|
13
|
+
dataFeedsIds,
|
|
14
|
+
uniqueSignersCount,
|
|
15
|
+
historicalTimestampMs,
|
|
16
|
+
gateways,
|
|
17
|
+
ignoreMissingFeeds,
|
|
18
|
+
enableLogging,
|
|
19
|
+
logger
|
|
20
|
+
} = options;
|
|
21
|
+
const metadataTimestampMs = historicalTimestampMs ?? options.metadataTimestampMs;
|
|
22
|
+
const dataPackagesIds = Array.from(new Set(dataFeedsIds));
|
|
23
|
+
if (dataPackagesIds.length === 0) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const wrapper = new DataServiceWrapper({
|
|
27
|
+
dataServiceId,
|
|
28
|
+
dataPackagesIds,
|
|
29
|
+
uniqueSignersCount,
|
|
30
|
+
authorizedSigners: getSignersForDataServiceId(
|
|
31
|
+
dataServiceId
|
|
32
|
+
),
|
|
33
|
+
historicalTimestamp: historicalTimestampMs,
|
|
34
|
+
urls: gateways,
|
|
35
|
+
ignoreMissingFeed: ignoreMissingFeeds,
|
|
36
|
+
enableEnhancedLogs: enableLogging
|
|
37
|
+
});
|
|
38
|
+
if (metadataTimestampMs) {
|
|
39
|
+
wrapper.setMetadataTimestamp(metadataTimestampMs);
|
|
40
|
+
}
|
|
41
|
+
const dataPayload = await retry(() => wrapper.prepareRedstonePayload(true), {
|
|
42
|
+
attempts: 5,
|
|
43
|
+
interval: historicalTimestampMs ? 30500 : 250
|
|
44
|
+
});
|
|
45
|
+
const parsed = RedstonePayload.parse(toBytes(`0x${dataPayload}`));
|
|
46
|
+
const packagesByDataFeedId = groupDataPackages(parsed.signedDataPackages);
|
|
47
|
+
const result = [];
|
|
48
|
+
for (const dataFeedId of dataFeedsIds) {
|
|
49
|
+
const signedDataPackages = packagesByDataFeedId[dataFeedId];
|
|
50
|
+
if (!signedDataPackages) {
|
|
51
|
+
if (ignoreMissingFeeds) {
|
|
52
|
+
logger?.warn(`cannot find data packages for ${dataFeedId}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`cannot find data packages for ${dataFeedId}`);
|
|
56
|
+
}
|
|
57
|
+
if (signedDataPackages.length !== uniqueSignersCount) {
|
|
58
|
+
if (ignoreMissingFeeds) {
|
|
59
|
+
logger?.warn(
|
|
60
|
+
`got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
|
|
61
|
+
);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
throw new Error(
|
|
65
|
+
`got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
result.push(
|
|
69
|
+
getCalldataWithTimestamp(
|
|
70
|
+
dataFeedId,
|
|
71
|
+
signedDataPackages,
|
|
72
|
+
wrapper.getUnsignedMetadata()
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
function groupDataPackages(signedDataPackages) {
|
|
79
|
+
const packagesByDataFeedId = {};
|
|
80
|
+
for (const p of signedDataPackages) {
|
|
81
|
+
const { dataPoints } = p.dataPackage;
|
|
82
|
+
const dataFeedId0 = dataPoints[0].dataFeedId;
|
|
83
|
+
for (const dp of dataPoints) {
|
|
84
|
+
if (dp.dataFeedId !== dataFeedId0) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`data package contains data points with different dataFeedIds: ${dp.dataFeedId} and ${dataFeedId0}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!packagesByDataFeedId[dataFeedId0]) {
|
|
91
|
+
packagesByDataFeedId[dataFeedId0] = [];
|
|
92
|
+
}
|
|
93
|
+
packagesByDataFeedId[dataFeedId0].push(p);
|
|
94
|
+
}
|
|
95
|
+
return packagesByDataFeedId;
|
|
96
|
+
}
|
|
97
|
+
function getCalldataWithTimestamp(dataFeedId, packages, unsignedMetadata) {
|
|
98
|
+
const originalPayload = RedstonePayload.prepare(packages, unsignedMetadata);
|
|
99
|
+
const originalPayloadLength = originalPayload.length / 2;
|
|
100
|
+
const bytesToAdd = 32 - originalPayloadLength % 32;
|
|
101
|
+
const newUnsignedMetadata = unsignedMetadata + "_".repeat(bytesToAdd);
|
|
102
|
+
const payload = RedstonePayload.prepare(packages, newUnsignedMetadata);
|
|
103
|
+
let timestamp = 0;
|
|
104
|
+
for (const p of packages) {
|
|
105
|
+
const newTimestamp = p.dataPackage.timestampMilliseconds / 1e3;
|
|
106
|
+
if (timestamp === 0) {
|
|
107
|
+
timestamp = newTimestamp;
|
|
108
|
+
} else if (timestamp !== newTimestamp) {
|
|
109
|
+
throw new Error("Timestamps are not equal");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
dataFeedId,
|
|
114
|
+
data: encodeAbiParameters(
|
|
115
|
+
[{ type: "uint256" }, { type: "bytes" }],
|
|
116
|
+
[BigInt(timestamp), `0x${payload}`]
|
|
117
|
+
),
|
|
118
|
+
timestamp,
|
|
119
|
+
cached: false
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
fetchRedstonePayloads
|
|
124
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ILogger } from "../../../types/logger.js";
|
|
2
|
+
import type { TimestampedCalldata } from "./types.js";
|
|
3
|
+
export interface FetchPythPayloadsOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Feeds to fetch
|
|
6
|
+
*/
|
|
7
|
+
dataFeedsIds: Iterable<string>;
|
|
8
|
+
/**
|
|
9
|
+
* When true, will not throw an error if pyth is unable to fetch data for some feeds
|
|
10
|
+
*/
|
|
11
|
+
ignoreMissingFeeds?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Historical timestamp in seconds
|
|
14
|
+
*/
|
|
15
|
+
historicalTimestampSeconds?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Override Hermes API with this proxy. Can be used to set caching proxies, to avoid rate limiting
|
|
18
|
+
*/
|
|
19
|
+
apiProxy?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Logger to use
|
|
22
|
+
*/
|
|
23
|
+
logger?: ILogger;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fetches pyth payloads from Hermes API
|
|
27
|
+
* @param dataFeedsIds
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
export declare function fetchPythPayloads(options: FetchPythPayloadsOptions): Promise<TimestampedCalldata[]>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ILogger } from "../../../types/logger.js";
|
|
2
|
+
import type { TimestampedCalldata } from "./types.js";
|
|
3
|
+
export interface FetchRedstonePayloadsOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Redstone data service id
|
|
6
|
+
* Most likely "redstone-primary-prod"
|
|
7
|
+
*/
|
|
8
|
+
dataServiceId: string;
|
|
9
|
+
/**
|
|
10
|
+
* Feeds to fetch
|
|
11
|
+
*/
|
|
12
|
+
dataFeedsIds: Iterable<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Ensure minimum number of signers for each feed
|
|
15
|
+
*/
|
|
16
|
+
uniqueSignersCount: number;
|
|
17
|
+
/**
|
|
18
|
+
* Historical timestamp in milliseconds
|
|
19
|
+
* Must be rounded to the nearest minute
|
|
20
|
+
*/
|
|
21
|
+
historicalTimestampMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Metadata timestamp in milliseconds, if undefined, will be set to historical timestamp
|
|
24
|
+
* Can be used to set deterministic metadata timestamp, so requests sent by two services at the same block are identical
|
|
25
|
+
*/
|
|
26
|
+
metadataTimestampMs?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Redstone gateways (put caching proxies here to avoid rate limiting)
|
|
29
|
+
*/
|
|
30
|
+
gateways?: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Enable redstone internal logging
|
|
33
|
+
*/
|
|
34
|
+
enableLogging?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* When true, will not throw an error if redstone is unable to fetch data for some feeds
|
|
37
|
+
*/
|
|
38
|
+
ignoreMissingFeeds?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Logger to use
|
|
41
|
+
*/
|
|
42
|
+
logger?: ILogger;
|
|
43
|
+
}
|
|
44
|
+
export declare function fetchRedstonePayloads(options: FetchRedstonePayloadsOptions): Promise<TimestampedCalldata[]>;
|