@gearbox-protocol/sdk 14.11.19 → 14.11.21
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/permissionless/bindings/instance-manager.js +1 -1
- package/dist/cjs/permissionless/bindings/market-configurator.js +2 -1
- package/dist/cjs/permissionless/utils/price-update/get-updatable-feeds.js +2 -23
- package/dist/cjs/sdk/OnchainSDK.js +0 -15
- package/dist/cjs/sdk/market/pricefeeds/PriceFeedsRegister.js +4 -6
- package/dist/cjs/sdk/market/pricefeeds/PythPriceFeed.js +1 -0
- package/dist/cjs/sdk/market/pricefeeds/RedstonePriceFeed.js +1 -0
- package/dist/cjs/sdk/market/pricefeeds/updates/PythUpdater.js +4 -122
- package/dist/cjs/sdk/market/pricefeeds/updates/RedstoneUpdater.js +4 -196
- package/dist/cjs/sdk/market/pricefeeds/updates/fetchPythPayloads.js +3 -105
- package/dist/cjs/sdk/market/pricefeeds/updates/fetchRedstonePayloads.js +4 -126
- package/dist/esm/permissionless/bindings/instance-manager.js +1 -1
- package/dist/esm/permissionless/bindings/market-configurator.js +2 -1
- package/dist/esm/permissionless/utils/price-update/get-updatable-feeds.js +2 -27
- package/dist/esm/sdk/OnchainSDK.js +0 -15
- package/dist/esm/sdk/market/pricefeeds/PriceFeedsRegister.js +4 -6
- package/dist/esm/sdk/market/pricefeeds/PythPriceFeed.js +1 -0
- package/dist/esm/sdk/market/pricefeeds/RedstonePriceFeed.js +1 -0
- package/dist/esm/sdk/market/pricefeeds/updates/PythUpdater.js +4 -122
- package/dist/esm/sdk/market/pricefeeds/updates/RedstoneUpdater.js +4 -196
- package/dist/esm/sdk/market/pricefeeds/updates/fetchPythPayloads.js +3 -109
- package/dist/esm/sdk/market/pricefeeds/updates/fetchRedstonePayloads.js +4 -130
- package/dist/types/permissionless/utils/price-update/get-updatable-feeds.d.ts +4 -3
- package/dist/types/sdk/market/pricefeeds/PriceFeedsRegister.d.ts +4 -5
- package/dist/types/sdk/market/pricefeeds/updates/PythUpdater.d.ts +5 -7
- package/dist/types/sdk/market/pricefeeds/updates/RedstoneUpdater.d.ts +5 -8
- package/dist/types/sdk/market/pricefeeds/updates/fetchPythPayloads.d.ts +2 -6
- package/dist/types/sdk/market/pricefeeds/updates/fetchRedstonePayloads.d.ts +2 -6
- package/package.json +1 -5
|
@@ -21,135 +21,13 @@ __export(fetchRedstonePayloads_exports, {
|
|
|
21
21
|
fetchRedstonePayloads: () => fetchRedstonePayloads
|
|
22
22
|
});
|
|
23
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_utils = require("@redstone-finance/utils");
|
|
28
|
-
var import_viem = require("viem");
|
|
29
|
-
var import_utils2 = require("../../../utils/index.js");
|
|
30
24
|
async function fetchRedstonePayloads(options) {
|
|
31
|
-
const {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
uniqueSignersCount,
|
|
35
|
-
historicalTimestampMs,
|
|
36
|
-
gateways,
|
|
37
|
-
failOnMissingFeeds,
|
|
38
|
-
enableLogging,
|
|
39
|
-
logger,
|
|
40
|
-
returnPrices
|
|
41
|
-
} = options;
|
|
42
|
-
const metadataTimestampMs = historicalTimestampMs ?? options.metadataTimestampMs;
|
|
43
|
-
const dataPackagesIds = Array.from(new Set(dataFeedsIds));
|
|
44
|
-
if (dataPackagesIds.length === 0) {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
const wrapper = new import_evm_connector.DataServiceWrapper({
|
|
48
|
-
dataServiceId,
|
|
49
|
-
dataPackagesIds,
|
|
50
|
-
uniqueSignersCount,
|
|
51
|
-
authorizedSigners: (0, import_sdk.getSignersForDataServiceId)(
|
|
52
|
-
dataServiceId
|
|
53
|
-
),
|
|
54
|
-
historicalTimestamp: historicalTimestampMs,
|
|
55
|
-
urls: gateways,
|
|
56
|
-
ignoreMissingFeed: !failOnMissingFeeds,
|
|
57
|
-
enableEnhancedLogs: enableLogging
|
|
58
|
-
});
|
|
59
|
-
if (metadataTimestampMs) {
|
|
60
|
-
wrapper.setMetadataTimestamp(metadataTimestampMs);
|
|
61
|
-
}
|
|
62
|
-
const dataPayload = await (0, import_utils2.retry)(() => wrapper.prepareRedstonePayload(true), {
|
|
63
|
-
attempts: 5,
|
|
64
|
-
interval: historicalTimestampMs ? 30500 : 250
|
|
65
|
-
});
|
|
66
|
-
const parsed = import_protocol.RedstonePayload.parse((0, import_viem.toBytes)(`0x${dataPayload}`));
|
|
67
|
-
const packagesByDataFeedId = groupDataPackages(parsed.signedDataPackages);
|
|
68
|
-
const result = [];
|
|
69
|
-
for (const dataFeedId of dataFeedsIds) {
|
|
70
|
-
const signedDataPackages = packagesByDataFeedId[dataFeedId];
|
|
71
|
-
if (!signedDataPackages) {
|
|
72
|
-
if (failOnMissingFeeds) {
|
|
73
|
-
throw new Error(`cannot find data packages for ${dataFeedId}`);
|
|
74
|
-
}
|
|
75
|
-
logger?.warn(`cannot find data packages for ${dataFeedId}`);
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (signedDataPackages.length !== uniqueSignersCount) {
|
|
79
|
-
if (failOnMissingFeeds) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
`got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
logger?.warn(
|
|
85
|
-
`got ${signedDataPackages.length} data packages for ${dataFeedId}, but expected ${uniqueSignersCount}`
|
|
86
|
-
);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
result.push(
|
|
90
|
-
getCalldataWithTimestamp(
|
|
91
|
-
dataFeedId,
|
|
92
|
-
signedDataPackages,
|
|
93
|
-
wrapper.getUnsignedMetadata(),
|
|
94
|
-
returnPrices
|
|
95
|
-
)
|
|
25
|
+
for (const _feed of options.dataFeedsIds) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"RedStone price updates are no longer supported by this SDK"
|
|
96
28
|
);
|
|
97
29
|
}
|
|
98
|
-
return
|
|
99
|
-
}
|
|
100
|
-
function groupDataPackages(signedDataPackages) {
|
|
101
|
-
const packagesByDataFeedId = {};
|
|
102
|
-
for (const p of signedDataPackages) {
|
|
103
|
-
const { dataPoints } = p.dataPackage;
|
|
104
|
-
const dataFeedId0 = dataPoints[0].dataFeedId;
|
|
105
|
-
for (const dp of dataPoints) {
|
|
106
|
-
if (dp.dataFeedId !== dataFeedId0) {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`data package contains data points with different dataFeedIds: ${dp.dataFeedId} and ${dataFeedId0}`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (!packagesByDataFeedId[dataFeedId0]) {
|
|
113
|
-
packagesByDataFeedId[dataFeedId0] = [];
|
|
114
|
-
}
|
|
115
|
-
packagesByDataFeedId[dataFeedId0].push(p);
|
|
116
|
-
}
|
|
117
|
-
return packagesByDataFeedId;
|
|
118
|
-
}
|
|
119
|
-
function getCalldataWithTimestamp(dataFeedId, packages, unsignedMetadata, returnPrices) {
|
|
120
|
-
const originalPayload = import_protocol.RedstonePayload.prepare(packages, unsignedMetadata);
|
|
121
|
-
const originalPayloadLength = originalPayload.length / 2;
|
|
122
|
-
const bytesToAdd = 32 - originalPayloadLength % 32;
|
|
123
|
-
const newUnsignedMetadata = unsignedMetadata + "_".repeat(bytesToAdd);
|
|
124
|
-
const payload = import_protocol.RedstonePayload.prepare(packages, newUnsignedMetadata);
|
|
125
|
-
let timestamp = 0;
|
|
126
|
-
for (const p of packages) {
|
|
127
|
-
const newTimestamp = p.dataPackage.timestampMilliseconds / 1e3;
|
|
128
|
-
if (timestamp === 0) {
|
|
129
|
-
timestamp = newTimestamp;
|
|
130
|
-
} else if (timestamp !== newTimestamp) {
|
|
131
|
-
throw new Error("Timestamps are not equal");
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const base = {
|
|
135
|
-
dataFeedId,
|
|
136
|
-
data: (0, import_viem.encodeAbiParameters)(
|
|
137
|
-
[{ type: "uint256" }, { type: "bytes" }],
|
|
138
|
-
[BigInt(timestamp), `0x${payload}`]
|
|
139
|
-
),
|
|
140
|
-
timestamp,
|
|
141
|
-
cached: false
|
|
142
|
-
};
|
|
143
|
-
if (returnPrices) {
|
|
144
|
-
const prices = packages.flatMap((p) => p.dataPackage.dataPoints).map((dp) => BigInt(`0x${Buffer.from(dp.value).toString("hex")}`));
|
|
145
|
-
const medianPrice = BigInt(import_utils.MathUtils.getMedianOfBigNumbers(prices));
|
|
146
|
-
return {
|
|
147
|
-
...base,
|
|
148
|
-
price: medianPrice,
|
|
149
|
-
decimals: 8
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
return base;
|
|
30
|
+
return [];
|
|
153
31
|
}
|
|
154
32
|
// Annotate the CommonJS export names for ESM import in node:
|
|
155
33
|
0 && (module.exports = {
|
|
@@ -16,11 +16,11 @@ import {
|
|
|
16
16
|
json_stringify
|
|
17
17
|
} from "../../sdk/index.js";
|
|
18
18
|
import { Addresses } from "../deployment/addresses.js";
|
|
19
|
+
import { BytecodeRepositoryContract } from "./bytecode-repository.js";
|
|
19
20
|
import { WithdrawalCompressorContract } from "./compressors/index.js";
|
|
20
21
|
import { PriceFeedStoreContract } from "./price-feed-store.js";
|
|
21
22
|
import { RoutingManagerContract } from "./router/index.js";
|
|
22
23
|
import { TreasurySplitterContract } from "./treasury-splitter.js";
|
|
23
|
-
import { BytecodeRepositoryContract } from "./bytecode-repository.js";
|
|
24
24
|
const abi = instanceManagerAbi;
|
|
25
25
|
class InstanceManagerContract extends BaseContract {
|
|
26
26
|
constructor(addr, register) {
|
|
@@ -1,30 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
bytes32ToString,
|
|
5
|
-
PythPriceFeed,
|
|
6
|
-
RedstonePriceFeedContract
|
|
7
|
-
} from "../../../sdk/index.js";
|
|
8
|
-
async function getUpdatablePriceFeeds(args) {
|
|
9
|
-
const { sdk, client, pfCompressor, priceFeeds } = args;
|
|
10
|
-
const priceFeedCompressor = getContract({
|
|
11
|
-
address: pfCompressor,
|
|
12
|
-
abi: priceFeedCompressorAbi,
|
|
13
|
-
client
|
|
14
|
-
});
|
|
15
|
-
const updatablePriceFeeds = (await priceFeedCompressor.read.loadPriceFeedTree([
|
|
16
|
-
priceFeeds
|
|
17
|
-
])).filter(
|
|
18
|
-
(data) => ["PRICE_FEED::PYTH", "PRICE_FEED::REDSTONE"].includes(
|
|
19
|
-
bytes32ToString(data.baseParams.contractType)
|
|
20
|
-
)
|
|
21
|
-
).map((data) => {
|
|
22
|
-
if (bytes32ToString(data.baseParams.contractType) === "PRICE_FEED::PYTH") {
|
|
23
|
-
return new PythPriceFeed(sdk, data);
|
|
24
|
-
}
|
|
25
|
-
return new RedstonePriceFeedContract(sdk, data);
|
|
26
|
-
});
|
|
27
|
-
return updatablePriceFeeds;
|
|
1
|
+
async function getUpdatablePriceFeeds(_args) {
|
|
2
|
+
return [];
|
|
28
3
|
}
|
|
29
4
|
export {
|
|
30
5
|
getUpdatablePriceFeeds
|
|
@@ -149,16 +149,6 @@ class OnchainSDK extends ChainContractsRegister {
|
|
|
149
149
|
);
|
|
150
150
|
this.#currentBlock = block.number;
|
|
151
151
|
this.#timestamp = block.timestamp;
|
|
152
|
-
if (blockNumber && !redstone?.historicTimestamp && time - Number(block.timestamp) * 1e3 > 60 * 1e3) {
|
|
153
|
-
this.logger?.warn(
|
|
154
|
-
"attaching to fixed block number, but redstone historicTimestamp is not set. price updates might fail"
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
if (blockNumber && !pyth?.historicTimestamp && time - Number(block.timestamp) * 1e3 > 60 * 1e3) {
|
|
158
|
-
this.logger?.warn(
|
|
159
|
-
"attaching to fixed block number, but pyth historicTimestamp is not set. price updates might fail"
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
152
|
this.#priceFeeds = new PriceFeedRegister(this, { redstone, pyth });
|
|
163
153
|
this.logger?.debug(
|
|
164
154
|
`attach block number ${this.currentBlock} timestamp ${this.timestamp}`
|
|
@@ -330,11 +320,6 @@ class OnchainSDK extends ChainContractsRegister {
|
|
|
330
320
|
*/
|
|
331
321
|
async syncState(opts) {
|
|
332
322
|
let { blockNumber, timestamp, ignoreUpdateablePrices } = opts ?? {};
|
|
333
|
-
if (this.priceFeeds.historical && !ignoreUpdateablePrices) {
|
|
334
|
-
this.logger?.warn(
|
|
335
|
-
"syncState is not supported with redstone or pyth historicTimestamp"
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
323
|
if (!blockNumber || !timestamp) {
|
|
339
324
|
const block = await this.client.getBlock({
|
|
340
325
|
blockTag: "latest"
|
|
@@ -27,7 +27,6 @@ import { MellowLRTPriceFeedContract } from "./MellowLRTPriceFeed.js";
|
|
|
27
27
|
import { PendleTWAPPTPriceFeed } from "./PendleTWAPPTPriceFeed.js";
|
|
28
28
|
import { PythPriceFeed } from "./PythPriceFeed.js";
|
|
29
29
|
import { RedstonePriceFeedContract } from "./RedstonePriceFeed.js";
|
|
30
|
-
import { PythUpdater, RedstoneUpdater } from "./updates/index.js";
|
|
31
30
|
import { WstETHPriceFeedContract } from "./WstETHPriceFeed.js";
|
|
32
31
|
import { YearnPriceFeedContract } from "./YearnPriceFeed.js";
|
|
33
32
|
import { ZeroPriceFeedContract } from "./ZeroPriceFeed.js";
|
|
@@ -36,12 +35,9 @@ class PriceFeedRegister extends SDKConstruct {
|
|
|
36
35
|
#updaters;
|
|
37
36
|
#feeds = new AddressMap(void 0, "priceFeeds");
|
|
38
37
|
#latestUpdate;
|
|
39
|
-
constructor(sdk,
|
|
38
|
+
constructor(sdk, _opts = {}) {
|
|
40
39
|
super(sdk);
|
|
41
|
-
this.#updaters = [
|
|
42
|
-
new PythUpdater(sdk, opts?.pyth),
|
|
43
|
-
new RedstoneUpdater(sdk, opts?.redstone)
|
|
44
|
-
];
|
|
40
|
+
this.#updaters = [];
|
|
45
41
|
}
|
|
46
42
|
/**
|
|
47
43
|
* @internal
|
|
@@ -151,6 +147,7 @@ class PriceFeedRegister extends SDKConstruct {
|
|
|
151
147
|
* @returns
|
|
152
148
|
*/
|
|
153
149
|
async generateExternalPriceFeedsUpdateTxs(feeds, block) {
|
|
150
|
+
if (this.#updaters.length === 0) return { txs: [], timestamp: 0 };
|
|
154
151
|
const [priceFeedCompressorAddress] = this.sdk.addressProvider.mustGetLatest(
|
|
155
152
|
AP_PRICE_FEED_COMPRESSOR,
|
|
156
153
|
VERSION_RANGE_310
|
|
@@ -225,6 +222,7 @@ class PriceFeedRegister extends SDKConstruct {
|
|
|
225
222
|
* Discovered price feeds are not saved anywhere in PriceFeedRegister, and can later be used to load price feed updates
|
|
226
223
|
*/
|
|
227
224
|
async getPartialUpdatablePriceFeeds(configurators) {
|
|
225
|
+
if (this.#updaters.length === 0) return [];
|
|
228
226
|
const [priceFeedCompressorAddress] = this.sdk.addressProvider.mustGetLatest(
|
|
229
227
|
AP_PRICE_FEED_COMPRESSOR,
|
|
230
228
|
VERSION_RANGE_310
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
import { SDKConstruct } from "../../../base/index.js";
|
|
3
|
-
import { fetchPythPayloads } from "./fetchPythPayloads.js";
|
|
4
|
-
import { PriceUpdatesCache } from "./PriceUpdatesCache.js";
|
|
5
3
|
import { PriceUpdateTx } from "./PriceUpdateTx.js";
|
|
6
4
|
class PythUpdateTx extends PriceUpdateTx {
|
|
7
5
|
name = "pyth";
|
|
@@ -29,131 +27,15 @@ const PythOptions = z.object({
|
|
|
29
27
|
failOnMissingFeeds: z.boolean().optional()
|
|
30
28
|
});
|
|
31
29
|
class PythUpdater extends SDKConstruct {
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Fixed pyth historic timestamp in seconds
|
|
35
|
-
*/
|
|
36
|
-
#historicalTimestamp;
|
|
37
|
-
#apiProxy;
|
|
38
|
-
#failOnMissingFeeds;
|
|
39
|
-
constructor(sdk, opts = {}) {
|
|
30
|
+
constructor(sdk, _opts = {}) {
|
|
40
31
|
super(sdk);
|
|
41
|
-
const { apiProxy, cacheTTL, failOnMissingFeeds, historicTimestamp } = opts;
|
|
42
|
-
this.#failOnMissingFeeds = failOnMissingFeeds;
|
|
43
|
-
this.#apiProxy = apiProxy;
|
|
44
|
-
if (historicTimestamp) {
|
|
45
|
-
this.#historicalTimestamp = historicTimestamp === true ? Number(this.sdk.timestamp) : historicTimestamp;
|
|
46
|
-
this.logger?.debug(
|
|
47
|
-
`using historical timestamp ${this.#historicalTimestamp}`
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
this.#cache = opts.cache ?? new PriceUpdatesCache({
|
|
51
|
-
ttl: cacheTTL ?? 225 * 1e3,
|
|
52
|
-
historical: !!historicTimestamp
|
|
53
|
-
});
|
|
54
32
|
}
|
|
55
|
-
async getUpdateTxs(
|
|
56
|
-
|
|
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;
|
|
33
|
+
async getUpdateTxs(_feeds) {
|
|
34
|
+
return [];
|
|
98
35
|
}
|
|
99
36
|
get historical() {
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Gets pyth payloads
|
|
104
|
-
* @param dataFeedsIds
|
|
105
|
-
* @returns
|
|
106
|
-
*/
|
|
107
|
-
async #getPayloads(dataFeedsIds) {
|
|
108
|
-
this.logger?.debug(
|
|
109
|
-
`getting pyth payloads for ${dataFeedsIds.size} price feeds: ${Array.from(dataFeedsIds).join(", ")}`
|
|
110
|
-
);
|
|
111
|
-
const fromCache = [];
|
|
112
|
-
const uncached = [];
|
|
113
|
-
for (const priceFeedsId of dataFeedsIds) {
|
|
114
|
-
const cached = this.#cache.get(priceFeedsId);
|
|
115
|
-
if (cached) {
|
|
116
|
-
fromCache.push({ ...cached, cached: true });
|
|
117
|
-
} else {
|
|
118
|
-
uncached.push(priceFeedsId);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
const fromPyth = await this.#fetchPayloads(
|
|
122
|
-
new Set(uncached)
|
|
123
|
-
);
|
|
124
|
-
for (const resp of fromPyth) {
|
|
125
|
-
this.#cache.set(resp, resp.dataFeedId);
|
|
126
|
-
}
|
|
127
|
-
this.logger?.debug(
|
|
128
|
-
`got ${fromPyth.length} new pyth updates and ${fromCache.length} from cache`
|
|
129
|
-
);
|
|
130
|
-
return [...fromCache, ...fromPyth];
|
|
37
|
+
return false;
|
|
131
38
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Fetches pyth payloads from Hermes API
|
|
134
|
-
* @param dataFeedsIds
|
|
135
|
-
* @returns
|
|
136
|
-
*/
|
|
137
|
-
async #fetchPayloads(dataFeedsIds) {
|
|
138
|
-
if (dataFeedsIds.size === 0) {
|
|
139
|
-
return [];
|
|
140
|
-
}
|
|
141
|
-
const ids = Array.from(dataFeedsIds);
|
|
142
|
-
const tsStr = this.#historicalTimestamp ? ` with historical timestamp ${this.#historicalTimestamp}` : "";
|
|
143
|
-
this.logger?.debug(
|
|
144
|
-
`fetching pyth payloads for ${dataFeedsIds.size} price feeds: ${ids.join(", ")}${tsStr}`
|
|
145
|
-
);
|
|
146
|
-
return fetchPythPayloads({
|
|
147
|
-
dataFeedsIds,
|
|
148
|
-
historicalTimestampSeconds: this.#historicalTimestamp,
|
|
149
|
-
apiProxy: this.#apiProxy,
|
|
150
|
-
failOnMissingFeeds: this.#failOnMissingFeeds,
|
|
151
|
-
logger: this.logger
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
function isPyth(pf) {
|
|
156
|
-
return pf.contractType === "PRICE_FEED::PYTH";
|
|
157
39
|
}
|
|
158
40
|
export {
|
|
159
41
|
PythOptions,
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
import { SDKConstruct } from "../../../base/index.js";
|
|
3
|
-
import { AddressMap } from "../../../utils/index.js";
|
|
4
|
-
import { fetchRedstonePayloads } from "./fetchRedstonePayloads.js";
|
|
5
|
-
import { PriceUpdatesCache } from "./PriceUpdatesCache.js";
|
|
6
3
|
import { PriceUpdateTx } from "./PriceUpdateTx.js";
|
|
7
4
|
class RedstoneUpdateTx extends PriceUpdateTx {
|
|
8
5
|
name = "redstone";
|
|
@@ -34,204 +31,15 @@ const RedstoneOptions = z.object({
|
|
|
34
31
|
enableLogging: z.boolean().optional()
|
|
35
32
|
});
|
|
36
33
|
class RedstoneUpdater extends SDKConstruct {
|
|
37
|
-
|
|
38
|
-
#historicalTimestampMs;
|
|
39
|
-
#gateways;
|
|
40
|
-
#failOnMissingFeeds;
|
|
41
|
-
#enableLogging;
|
|
42
|
-
constructor(sdk, opts = {}) {
|
|
34
|
+
constructor(sdk, _opts = {}) {
|
|
43
35
|
super(sdk);
|
|
44
|
-
this.#failOnMissingFeeds = opts.failOnMissingFeeds;
|
|
45
|
-
this.#enableLogging = opts.enableLogging;
|
|
46
|
-
this.#gateways = opts.gateways?.length ? opts.gateways : void 0;
|
|
47
|
-
let ts = opts.historicTimestamp;
|
|
48
|
-
if (ts) {
|
|
49
|
-
ts = ts === true ? Number(this.sdk.timestamp) * 1e3 : ts;
|
|
50
|
-
this.#historicalTimestampMs = 6e4 * Math.floor(ts / 6e4);
|
|
51
|
-
this.logger?.debug(
|
|
52
|
-
`using historical timestamp ${this.#historicalTimestampMs}`
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
this.#cache = opts.cache ?? new PriceUpdatesCache({
|
|
56
|
-
ttl: opts.cacheTTL ?? 225 * 1e3,
|
|
57
|
-
historical: !!ts
|
|
58
|
-
});
|
|
59
36
|
}
|
|
60
|
-
async getUpdateTxs(
|
|
61
|
-
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
this.logger?.debug(
|
|
65
|
-
`generating update transactions for ${feeds.length} redstone price feeds`
|
|
66
|
-
);
|
|
67
|
-
const groupedFeeds = {};
|
|
68
|
-
const priceFeeds = /* @__PURE__ */ new Map();
|
|
69
|
-
for (const feed of feeds) {
|
|
70
|
-
if (!isRedstone(feed)) {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const key = `${feed.dataServiceId}:${feed.signersThreshold}`;
|
|
74
|
-
if (!groupedFeeds[key]) {
|
|
75
|
-
groupedFeeds[key] = /* @__PURE__ */ new Set();
|
|
76
|
-
}
|
|
77
|
-
groupedFeeds[key].add(feed.dataId);
|
|
78
|
-
const pfsForDataId = priceFeeds.get(feed.dataId) ?? new AddressMap();
|
|
79
|
-
pfsForDataId.upsert(feed.address, feed);
|
|
80
|
-
priceFeeds.set(feed.dataId, pfsForDataId);
|
|
81
|
-
}
|
|
82
|
-
const results = [];
|
|
83
|
-
let [minTimestamp, maxTimestamp] = [Number.POSITIVE_INFINITY, 0];
|
|
84
|
-
for (const [key, group] of Object.entries(groupedFeeds)) {
|
|
85
|
-
const [dataServiceId, signersStr] = key.split(":");
|
|
86
|
-
const uniqueSignersCount = parseInt(signersStr, 10);
|
|
87
|
-
const payloads = await this.#safeGetPayloads(
|
|
88
|
-
dataServiceId,
|
|
89
|
-
group,
|
|
90
|
-
uniqueSignersCount
|
|
91
|
-
);
|
|
92
|
-
for (const { dataFeedId, data, timestamp, cached } of payloads) {
|
|
93
|
-
const pfsForDataId = priceFeeds.get(dataFeedId);
|
|
94
|
-
if (!pfsForDataId) {
|
|
95
|
-
throw new Error(`cannot get price feed addresses for ${dataFeedId}`);
|
|
96
|
-
}
|
|
97
|
-
for (const priceFeed of pfsForDataId.values()) {
|
|
98
|
-
minTimestamp = Math.min(minTimestamp, timestamp);
|
|
99
|
-
maxTimestamp = Math.max(maxTimestamp, timestamp);
|
|
100
|
-
results.push(
|
|
101
|
-
new RedstoneUpdateTx(priceFeed.createPriceUpdateTx(data), {
|
|
102
|
-
dataFeedId,
|
|
103
|
-
dataServiceId,
|
|
104
|
-
priceFeed: priceFeed.address,
|
|
105
|
-
timestamp,
|
|
106
|
-
cached
|
|
107
|
-
})
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
let tsRange = "";
|
|
113
|
-
if (results.length) {
|
|
114
|
-
const minDelta = BigInt(minTimestamp) - this.sdk.timestamp;
|
|
115
|
-
tsRange = `, timestamps ${minTimestamp} (${minDelta})`;
|
|
116
|
-
if (minTimestamp !== maxTimestamp) {
|
|
117
|
-
const maxDelta = BigInt(maxTimestamp) - this.sdk.timestamp;
|
|
118
|
-
tsRange = `${tsRange} - ${maxTimestamp} (${maxDelta})`;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
this.logger?.debug(
|
|
122
|
-
`generated ${results.length} update transactions for redstone price feeds: ${Array.from(priceFeeds.keys()).join(", ")}${tsRange}`
|
|
123
|
-
);
|
|
124
|
-
return results;
|
|
37
|
+
async getUpdateTxs(_feeds) {
|
|
38
|
+
return [];
|
|
125
39
|
}
|
|
126
40
|
get historical() {
|
|
127
|
-
return
|
|
41
|
+
return false;
|
|
128
42
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Gets payloads, retries once if it has expired while inflight
|
|
131
|
-
* @param dataServiceId
|
|
132
|
-
* @param dataFeedsIds
|
|
133
|
-
* @param uniqueSignersCount
|
|
134
|
-
* @returns
|
|
135
|
-
*/
|
|
136
|
-
async #safeGetPayloads(dataServiceId, dataFeedsIds, uniqueSignersCount) {
|
|
137
|
-
let result = await this.#getPayloads(
|
|
138
|
-
dataServiceId,
|
|
139
|
-
dataFeedsIds,
|
|
140
|
-
uniqueSignersCount
|
|
141
|
-
);
|
|
142
|
-
if (this.#historicalTimestampMs) {
|
|
143
|
-
return result;
|
|
144
|
-
}
|
|
145
|
-
let expired = false;
|
|
146
|
-
for (const { timestamp, dataFeedId } of result) {
|
|
147
|
-
const delta = Number(this.sdk.timestamp) - timestamp;
|
|
148
|
-
if (delta >= 240 && delta < 255) {
|
|
149
|
-
this.logger?.warn(
|
|
150
|
-
`payload for ${dataFeedId} has expired by ${delta} seconds`
|
|
151
|
-
);
|
|
152
|
-
expired = true;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (expired) {
|
|
156
|
-
result = await this.#getPayloads(
|
|
157
|
-
dataServiceId,
|
|
158
|
-
dataFeedsIds,
|
|
159
|
-
uniqueSignersCount
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
return result;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Gets redstone payloads in one request for multiple feeds with the same dataServiceId and uniqueSignersCount
|
|
166
|
-
* If historicalTimestamp is set, responses will be cached
|
|
167
|
-
* @param dataServiceId
|
|
168
|
-
* @param dataFeedsIds
|
|
169
|
-
* @param uniqueSignersCount
|
|
170
|
-
* @returns
|
|
171
|
-
*/
|
|
172
|
-
async #getPayloads(dataServiceId, dataFeedsIds, uniqueSignersCount) {
|
|
173
|
-
this.logger?.debug(
|
|
174
|
-
`getting redstone payloads for ${dataFeedsIds.size} data feeds in ${dataServiceId} with ${uniqueSignersCount} signers: ${Array.from(dataFeedsIds).join(", ")}`
|
|
175
|
-
);
|
|
176
|
-
const fromCache = [];
|
|
177
|
-
const uncached = [];
|
|
178
|
-
for (const dataFeedId of dataFeedsIds) {
|
|
179
|
-
const cached = this.#cache.get(
|
|
180
|
-
dataServiceId,
|
|
181
|
-
dataFeedId,
|
|
182
|
-
uniqueSignersCount
|
|
183
|
-
);
|
|
184
|
-
if (cached) {
|
|
185
|
-
fromCache.push({ ...cached, cached: true });
|
|
186
|
-
} else {
|
|
187
|
-
uncached.push(dataFeedId);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
const fromRedstone = await this.#fetchPayloads(
|
|
191
|
-
dataServiceId,
|
|
192
|
-
new Set(uncached),
|
|
193
|
-
uniqueSignersCount
|
|
194
|
-
);
|
|
195
|
-
for (const resp of fromRedstone) {
|
|
196
|
-
this.#cache.set(resp, dataServiceId, resp.dataFeedId, uniqueSignersCount);
|
|
197
|
-
}
|
|
198
|
-
this.logger?.debug(
|
|
199
|
-
`got ${fromRedstone.length} new redstone updates and ${fromCache.length} from cache`
|
|
200
|
-
);
|
|
201
|
-
return [...fromCache, ...fromRedstone];
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Fetches redstone payloads in one request for multiple feeds with the same dataServiceId and uniqueSignersCount
|
|
205
|
-
* Payloads are loaded in one request to avoid redstone rate limit
|
|
206
|
-
* @param dataServiceId
|
|
207
|
-
* @param dataFeedsIds
|
|
208
|
-
* @param uniqueSignersCount
|
|
209
|
-
* @returns
|
|
210
|
-
*/
|
|
211
|
-
async #fetchPayloads(dataServiceId, dataFeedsIds, uniqueSignersCount) {
|
|
212
|
-
if (dataFeedsIds.size === 0) {
|
|
213
|
-
return [];
|
|
214
|
-
}
|
|
215
|
-
const dataPackagesIds = Array.from(dataFeedsIds);
|
|
216
|
-
const tsStr = this.#historicalTimestampMs ? ` with historical timestamp ${this.#historicalTimestampMs}` : "";
|
|
217
|
-
this.logger?.debug(
|
|
218
|
-
`fetching redstone payloads for ${dataFeedsIds.size} data feeds in ${dataServiceId} with ${uniqueSignersCount} signers: ${dataPackagesIds.join(", ")}${tsStr}`
|
|
219
|
-
);
|
|
220
|
-
return fetchRedstonePayloads({
|
|
221
|
-
dataServiceId,
|
|
222
|
-
dataFeedsIds,
|
|
223
|
-
uniqueSignersCount,
|
|
224
|
-
historicalTimestampMs: this.#historicalTimestampMs,
|
|
225
|
-
gateways: this.#gateways,
|
|
226
|
-
failOnMissingFeeds: this.#failOnMissingFeeds,
|
|
227
|
-
enableLogging: this.#enableLogging,
|
|
228
|
-
logger: this.logger,
|
|
229
|
-
metadataTimestampMs: Number(this.sdk.timestamp) * 1e3
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
function isRedstone(pf) {
|
|
234
|
-
return pf.contractType === "PRICE_FEED::REDSTONE";
|
|
235
43
|
}
|
|
236
44
|
export {
|
|
237
45
|
RedstoneOptions,
|