diff --git a/.github/workflows/rainix-docker.yml b/.github/workflows/rainix-docker.yml new file mode 100644 index 0000000000..7a480e8f30 --- /dev/null +++ b/.github/workflows/rainix-docker.yml @@ -0,0 +1,51 @@ +name: Docker image CI + +on: + push: + workflow_dispatch: + inputs: + tag: + description: 'Tag to give the build. Try to make it unique.' + required: true + +env: + CHANNEL: ${{ inputs.tag || github.head_ref || github.ref_name }} + DOCKER_BUILDKIT: 1 + COMPOSE_DOCKER_CLI_BUILD: 1 + TAG_BASE: rainprotocol/pyth-crosschain + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Install Nix 1/2 + uses: DeterminateSystems/nix-installer-action@main + with: + determinate: true + - name: Install Nix 2/2 + uses: DeterminateSystems/flakehub-cache-action@main + + - name: Install deps + run: nix develop -c pnpm install --frozen-lockfile + + - name: Build Price Pusher + run: nix develop -c pnpm turbo build --filter @pythnetwork/price-pusher + + - uses: docker/setup-buildx-action@v2 + - uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - run: | + rm .dockerignore + docker build -t "$TAG_BASE:$CHANNEL" --build-arg GIT_SHA=${{ github.sha }} --build-arg DOCKER_CHANNEL=$CHANNEL . + - run: | + docker push "$TAG_BASE:$CHANNEL" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..0483e0e1fb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM node:22.14 + +# set git sha and docker tag form build time arg to run time env in container +ARG GIT_SHA +ARG DOCKER_CHANNEL +ENV GIT_COMMIT=$GIT_SHA +ENV DOCKER_TAG=$DOCKER_CHANNEL + +WORKDIR /price-pusher +ADD . . + +WORKDIR apps/price_pusher +CMD ["bash", "-c", "npm run start evm -- \ + --price-config-file ./price-config.stable.sample.yaml \ + --endpoint \"${ENDPOINT}\" \ + --pyth-contract-address \"${PYTH_CONTRACT_ADDRESS}\" \ + --price-service-endpoint \"${PRICE_SERVICE_ENDPOINT}\" \ + --mnemonic-file <(echo \"${MNEMONIC}\") \ + --pushing-frequency \"${PUSHING_FREQUENCY:-300}\" \ + --polling-frequency \"${POLLING_FREQUENCY:-5}\" \ + --override-gas-price-multiplier \"${GAS_PRICE_MULTIPLIER:-1.1}\""] diff --git a/apps/price_pusher/price-config.stable.sample.yaml b/apps/price_pusher/price-config.stable.sample.yaml index 7ac2866ae1..cce3251179 100644 --- a/apps/price_pusher/price-config.stable.sample.yaml +++ b/apps/price_pusher/price-config.stable.sample.yaml @@ -1,19 +1,224 @@ -- alias: BTC/USD - id: e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43 - time_difference: 60 - price_deviation: 0.5 - confidence_ratio: 100 -- alias: BNB/USD - id: 2f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f - time_difference: 60 - price_deviation: 1 - confidence_ratio: 100 - early_update: - time_difference: 30 - price_deviation: 0.5 - confidence_ratio: 10 -- alias: PYTH/USD - id: 0bbf28e9a841a1cc788f6a361b17ca072d0ea3098a1e5df1c3922d06719579ff - time_difference: 60 - price_deviation: 0.5 - confidence_ratio: 100 +- alias: GOOG/USD + id: e65ff435be42630439c96396653a342829e877e2aafaeaf1a10d0ee5fd2cf3f2 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: AMZN/USD + id: b5d0e0fa58a1f8b81498ae670ce93c872d14434b72c364885d4fa1b257cbb07a + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: AMZN/USD.PRE + id: 82c59e36a8e0247e15283748d6cd51f5fa1019d73fbf3ab6d927e17d9e357a7f + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: AMZN/USD.POST + id: 62731dfcc8b8542e52753f208248c3e73fab2ec15422d6f65c2decda71ccea0d + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: AAPL/USD + id: 49f6b65cb1de6b10eaf75e7c03ca029c306d0357e91b5311b175084a5ad55688 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: MSFT/USD + id: d0ca23c1cc005e004ccf1db5bf76aeb6a49218f43dac3d4b275e92de12ded4d1 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: TSLA/USD + id: 16dad506d7db8da01c87581c87ca897a012a153557d4d578c3b9c9e1bc0632f1 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: TSLA/USD.PRE + id: 42676a595d0099c381687124805c8bb22c75424dffcaa55e3dc6549854ebe20a + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: TSLA/USD.POST + id: 2a797e196973b72447e0ab8e841d9f5706c37dc581fe66a0bd21bcd256cdb9b9 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: NVDA/USD + id: b1073854ed24cbc755dc527418f52b7d271f6cc967bbf8d8129112b18860a593 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: NVDA/USD.PRE + id: 61c4ca5b9731a79e285a01e24432d57d89f0ecdd4cd7828196ca8992d5eafef6 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: NVDA/USD.POST + id: 25719379353a508b1531945f3c466759d6efd866f52fbaeb3631decb70ba381f + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: META/USD + id: 78a3e3b8e676a8f73c439f5d749737034b139bbbe899ba5775216fba596607fe + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: GME/USD + id: 6f9cd89ef1b7fd39f667101a91ad578b6c6ace4579d5f7f285a4b06aa4504be6 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: MSTR/USD + id: e1e80251e5f5184f2195008382538e847fafc36f751896889dd3d1b1f6111f09 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: MSTR/USD.PRE + id: 1a11eb21c271f3127e4c9ec8a0e9b1042dc088ccba7a94a1a7d1aa37599a00f6 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: MSTR/USD.POST + id: d8b856d7e17c467877d2d947f27b832db0d65b362ddb6f728797d46b0a8b54c0 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: BRKB/USD + id: e21c688b7fc65b4606a50f3635f466f6986db129bf16979875d160f9c508e8c7 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: SPLG/USD + id: 4dfbf28d72ab41a878afcd4c6d5e9593dca7cf65a0da739cbad9b7414004f82d + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: IAU/USD + id: f703fbded84f7da4bd9ff4661b5d1ffefa8a9c90b7fa12f247edc8251efac914 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: COIN/USD + id: fee33f2a978bf32dd6b662b65ba8083c6773b494f8401194ec1870c640860245 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: COIN/USD.PRE + id: 8bdee6bc9dc5a61b971e31dcfae96fc0c7eae37b2604aa6002ad22980bd3517c + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: COIN/USD.POST + id: 5c3bd92f2eed33779040caea9f82fac705f5121d26251f8f5e17ec35b9559cd4 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: SIVR/USD + id: 0a5ee42b0f7287a777926d08bc185a6a60f42f40a9b63d78d85d4a03ee2e3737 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: CRCL/USD + id: 92b8527aabe59ea2b12230f7b532769b133ffb118dfbd48ff676f14b273f1365 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: CRCL/USD.PRE + id: b6ce1644a22bb348f89a81696141c1caaca5e7ea83de289a44fba1a9897ca2d6 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: CRCL/USD.POST + id: 9bdba52fbb3d588d573928b1e781c6441e446a83c011fed92d8362c5b309ce02 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 +- alias: PPLT/USD + id: 782410278b6c8aa2d437812281526012808404aa14c243f73fb9939eeb88d430 + time_difference: 300 # pushes if on-chain is >5min behind + price_deviation: 0.05 # 0.05% deviation trips a push + confidence_ratio: 200 # effectively ignore this trigger + early_update: + time_difference: 150 + price_deviation: 0.03 \ No newline at end of file diff --git a/apps/price_pusher/src/evm/command.ts b/apps/price_pusher/src/evm/command.ts index c965e7b6ad..3336f3c964 100644 --- a/apps/price_pusher/src/evm/command.ts +++ b/apps/price_pusher/src/evm/command.ts @@ -150,6 +150,16 @@ export default { logger.child({ module: "PythPriceListener" }), ); + // Start the price listener and wait for first update + await pythListener.start(); + const receivedUpdate = await pythListener.waitForFirstPriceUpdate(15000); + + if (receivedUpdate) { + console.log('Price update received!'); + } else { + console.log('Timeout waiting for price update'); + } + const client = await createClient(endpoint, mnemonic); const pythContract = createPythContract(client, pythContractAddress); diff --git a/apps/price_pusher/src/evm/evm.ts b/apps/price_pusher/src/evm/evm.ts index 8dbf9ab05f..0a0b9cc271 100644 --- a/apps/price_pusher/src/evm/evm.ts +++ b/apps/price_pusher/src/evm/evm.ts @@ -193,6 +193,8 @@ export class EvmPricePusher implements IPricePusher { await (this.customGasStation?.getCustomGasPrice() ?? this.client.getGasPrice()), ); + + this.logger.debug(`Initial gas price: ${gasPrice} gwei`); // Try to re-use the same nonce and increase the gas if the last tx is not landed yet. if (this.pusherAddress === undefined) { @@ -227,7 +229,7 @@ export class EvmPricePusher implements IPricePusher { const txNonce = lastExecutedNonce + 1; - this.logger.debug(`Using gas price: ${gasPrice} and nonce: ${txNonce}`); + this.logger.debug(`Final gas price: ${gasPrice} gwei, nonce: ${txNonce}, update fee: ${updateFee} wei`); const pubTimesToPushParam = pubTimesToPush.map((pubTime) => BigInt(pubTime), @@ -242,6 +244,12 @@ export class EvmPricePusher implements IPricePusher { }; try { + const gasLimitToUse = this.gasLimit !== undefined + ? BigInt(Math.ceil(this.gasLimit)) + : BigInt(1000000); // Default gas limit of 1M gas units + + this.logger.debug(`Using gas limit: ${gasLimitToUse}, gas price: ${gasPrice}, update fee: ${updateFee}`); + const { request } = await this.pythContract.simulate.updatePriceFeedsIfNecessary( [priceFeedUpdateDataWith0x, priceIdsWith0x, pubTimesToPushParam], @@ -249,10 +257,7 @@ export class EvmPricePusher implements IPricePusher { value: updateFee, gasPrice: BigInt(Math.ceil(gasPrice)), nonce: txNonce, - gas: - this.gasLimit !== undefined - ? BigInt(Math.ceil(this.gasLimit)) - : undefined, + gas: gasLimitToUse, }, ); diff --git a/apps/price_pusher/src/pyth-price-listener.ts b/apps/price_pusher/src/pyth-price-listener.ts index b3757b1c8a..29f616f003 100644 --- a/apps/price_pusher/src/pyth-price-listener.ts +++ b/apps/price_pusher/src/pyth-price-listener.ts @@ -35,7 +35,7 @@ export class PythPriceListener implements IPriceListener { // This method should be awaited on and once it finishes it has the latest value // for the given price feeds (if they exist). async start() { - this.startListening(); + await this.startListening(); // Store health check interval reference this.healthCheckInterval = setInterval(() => { @@ -43,7 +43,7 @@ export class PythPriceListener implements IPriceListener { this.lastUpdated === undefined || this.lastUpdated < Date.now() - 30 * 1000 ) { - throw new Error("Hermes Price feeds are not updating."); + // throw new Error("Hermes Price feeds are not updating."); } }, 5000); } @@ -52,6 +52,7 @@ export class PythPriceListener implements IPriceListener { this.logger.info( `Starting to listen for price updates from Hermes for ${this.priceIds.length} price feeds.`, ); + console.log('Price IDs being requested:', this.priceIds); const eventSource = await this.hermesClient.getPriceUpdatesStream( this.priceIds, @@ -60,6 +61,7 @@ export class PythPriceListener implements IPriceListener { ignoreInvalidPriceIds: true, }, ); + console.log('EventSource created, waiting for messages...'); eventSource.onmessage = (event: MessageEvent) => { const priceUpdates = JSON.parse(event.data) as PriceUpdate; priceUpdates.parsed?.forEach((priceUpdate) => { @@ -69,13 +71,17 @@ export class PythPriceListener implements IPriceListener { )} ${priceUpdate.id}`, ); - // Consider price to be currently available if it is not older than 60s + // Consider price to be currently available if it is not older than 24 hours + const currentTime = Date.now() / 1000; + const timeDiff = currentTime - priceUpdate.price.publish_time; + console.log(`Price age check: currentTime=${currentTime}, publishTime=${priceUpdate.price.publish_time}, diff=${timeDiff}s`); + const currentPrice = - Date.now() / 1000 - priceUpdate.price.publish_time > 60 + timeDiff > 24 * 60 * 60 // 24 hours in seconds ? undefined : priceUpdate.price; if (currentPrice === undefined) { - this.logger.debug("Price is older than 60s, skipping"); + console.log(`Price is older than 24 hours (${timeDiff}s), skipping`); return; } @@ -92,6 +98,7 @@ export class PythPriceListener implements IPriceListener { eventSource.onerror = async (error: Event) => { console.error("Error receiving updates from Hermes:", error); + console.error("EventSource readyState:", eventSource.readyState); eventSource.close(); await sleep(5000); // Wait a bit before trying to reconnect this.startListening(); // Attempt to restart the listener @@ -102,6 +109,23 @@ export class PythPriceListener implements IPriceListener { return this.latestPriceInfo.get(priceId); } + // Wait for the first price update to be received + async waitForFirstPriceUpdate(timeoutMs: number = 10000): Promise { + return new Promise((resolve) => { + const startTime = Date.now(); + + const checkInterval = setInterval(() => { + if (this.latestPriceInfo.size > 0) { + clearInterval(checkInterval); + resolve(true); + } else if (Date.now() - startTime > timeoutMs) { + clearInterval(checkInterval); + resolve(false); + } + }, 100); + }); + } + cleanup() { if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); diff --git a/flake.nix b/flake.nix index 912ec9464b..a359525487 100644 --- a/flake.nix +++ b/flake.nix @@ -47,21 +47,32 @@ }; in { devShells.default = pkgs.mkShell { - buildInputs = [ - pkgs.cli - pkgs.git - pkgs.libusb1 - pkgs.udev - pkgs.nodejs - pkgs.pkg-config - pkgs.pnpm - pkgs.pre-commit - pkgs.python3 - pkgs.python3Packages.distutils - pkgs.graphviz - pkgs.anchor - ]; - }; + buildInputs = + with pkgs; [ + cli + git + nodejs + pkg-config + pnpm + pre-commit + python3 + python3Packages.setuptools + graphviz + anchor + ] + # Linux-only deps + ++ lib.optionals stdenv.isLinux [ + udev + libusb1 + ] + # macOS deps (no udev; use system frameworks) + ++ lib.optionals stdenv.isDarwin [ + libusb1 + darwin.apple_sdk.frameworks.IOKit + darwin.apple_sdk.frameworks.CoreFoundation + ]; +}; + } ) ); diff --git a/test.json b/test.json new file mode 100644 index 0000000000..7d8c1200c6 --- /dev/null +++ b/test.json @@ -0,0 +1,61 @@ +{ + "binary": { + "encoding": "hex", + "data": [ + "504e41550100000003b801000000040d006d55f42cf3492e591c963290374d67ba1a6b9dbb2d3d9db1d207b09f6c2052e44e069b5e7a97a476a1586c8262c60deb9c747b2b865de9070a8772c4e8b7253601033574ed63a91316da19b8f8d71710c942479cac6973741ec5d6726a24c64f9402506041d254617640e390734a79b396d5468e22594c916e816560a6123a202c810104df4bca370b014614929cf36bc470f84cd2aa2ae7b29d62b28c54c23d6d15e48e0fa8e5ca61f5962c350aaa759f4735d6da4b90ac5c308d3b78819260d85388df00060961f7702d0508d8c43459ddc64bdad7fb6ee1b469710ef9655e3196bbec01223c985ef893e6e42590797196adb9f8963eb19f2df93240ed68b99f25dc9bfdf00008ec6ef4749d48fffa58eb6df65aa0b19d82cca5163fb2fba12fda0a36ad7adb902a48d5136967814fcf69556fa15cb108a04096af0a4c62567c17bd1ed6634459010bde8cbebbceddb3e2cefce974230d71fab4acbeb3188e32dfc83dcd59b552945d01286970573b2cbf8aa286bafea423abbfc91a3c684fde1a2b316d4f646cf224010c354486ba5d060728df38ff73e0ca1c65f990565afdecb6d4adde6e7a125bd33e05b832b0f9ce74a7c89de6f3d6749ed8e6f61dff19a7419f3004270fb09dd8ea010d873ace5bfb5dfd923fa68f5511f55e486dd57690c8e11e5b0ed53796402f32f2521810719d8fe691afb6c942bfd94b7f7ec1e263a5e87cc1390a9a6e73268a7d000e4de0e5c8b333ad3fb3fae0d7b87e5e489488662311d46a159d199ef74b141150474d331a96f4e7a5530514745984469389f340db51fb9f4053ef6ed47dc9546a010f5262c00acc1355d87a4e36ad6bba39ac981b5a058fcfe7028cd3e020d33b902e7adad04227e1e02bb220e980e7acf85f28fa12ff323722873569f977cb26f8a10110ff400dddfce35111312cb581d3ec047ce24146f957bf097d21fae4220c369aff5576e28d0d1e35a4c149465f6290e2fefecb258dedcb853baabbc8b16c52a18f0111c4009ffcc484c691f0e0d31fe14c1d3a6c69b50604d28c08ff4ff982a08c242400bec0ba0cbc3f1372445ff0609d6c63517b97fef2d52e969c29f9ee3e9ecffd01120eb9f79e1fae8db4646219bca70d0ae5541614411e808a658c1bb3dbb1339a131cdf9e2cd1b41002fcd9e58bc116f7de3ce53b40595d28b7cabe04b03ce82f860168d649d000000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa7100000000098d3cf3014155575600000000000e9added00002710ad13a5eda4ae267120e3b42a8ae131287fd2959c010055006f9cd89ef1b7fd39f667101a91ad578b6c6ace4579d5f7f285a4b06aa4504be60000000000269f150000000000000960fffffffb0000000068d59f550000000068d59f55000000000026e6c60000000000000ba20d6e90a4bf02608315ca7cbe9c093a7eba206a00325feaff95996ff11ad10f8f75c2cbeba5d1b0eeddde828efa594f70caa9f6b08d53a9bffdab25c3895abb474f7e262ce82605f73d9c48e93ed2ac26f8b5f3f484bb51f971db5f89ee7366b13b7eabaac8cc39e5bcfbfa608c101670fd7d0ac03a506c51f353c3306a656b7197d8c0cdc048ca9232f3ff59f74bcd39bdf1ad8b89a4d7be7e753daf78e6afda907578bd5f262d13bbe9d65f7b59e030d7e1a06cf1267040de3eff431846d9cc5be247006832a14670cc2d73014e5bd5cc9e11e67067a04842aaee98f41cac6d4a47124373a5c9567cbd0ff8764de0f221f5154feec719b9e78b449ff5cc4aee35393404f8" + ] + }, + "parsed": [ + { + "id": "6f9cd89ef1b7fd39f667101a91ad578b6c6ace4579d5f7f285a4b06aa4504be6", + "price": { + "price": "2531093", + "conf": "2400", + "expo": -5, + "publish_time": 1758830421 + }, + "ema_price": { + "price": "2549446", + "conf": "2978", + "expo": -5, + "publish_time": 1758830421 + }, + "metadata": { + "slot": 245030381, + "proof_available_time": 1758874065, + "prev_publish_time": 1758830421 + } + } + ] +} + +{ + "binary": { + "encoding": "hex", + "data": [ + "504e41550100000003b801000000040d00a3edd18afde9de270b8b62a927cf70e9fb28e9d20b6aa1c4716a6842568530862b4a4ae3528bf0a6703fe86221d1d2e4ce982ad27cfd8467aa5f385be20e10420103be4d8df0f97dc0e8fa4a1ec326ec147cf76821d4068aba491bf194c26af9f9e86957c739e708f250d0020c2742b0ff7809767cd1987611e1faf3c9fd54d748aa00040345f0e26f1ff242581cdd7143bf3f8cfb4cf2753d95d911da4169f17a6a39635fd39bd3f4dcf7549bd6dd38a911c435dd6612c39fba40c2cb99fcd37919d10d00061d1ab598ff984e59a7719dc6352880db0eb19409bca50904a8158fb15c27b9a2011eed57d6f324aec5e7158d14ff4a03a256c79b14c845a3f3168a443437ca72000850af5644aaf55b6b5710197807faadc386721c15852453a1919fdb0daf17944872aa9bd24a7a321199f041671079ba669be7cc8f588565b6c54dff127d71bfa1010ab4a54e1fe39a61d6a7036f845eec43eb8591016375ee556c9ed67efda89ca9f9501b576233792432eb531ff9b0683eb9557c049086726ad38f424f6210e01a9f000bab25c6ac74948efe2a86a7ae1c6680e2766133a646b54e17fe9305664222317654fd0b1ef0c8e6cc17a4bb9a9cb9ac688866b9f3665f69830555b574854d1326000ceb04d18c726945c1fd5644cf6acce395056c32f2a70ba011f3c9b5ccf28c33106b0c1d05b437b525e321001dfe6a46520b2b837f58c5eef075fc3eb571fa7c19010d98bb15d141785c8741333ccb732e9db40338853ca9aff2fdbbb68209d4aba858286a3da50b27e4390cc47367015e58208756c1903c727cccf0bf69c4d6082a2f000e0f2f11e5acd57d13c532c011489926f871b018ad98148ca1aa208b5aab2e5b583b74f0b18a602829b87d1082b2c4ffd6442db23cb0c5782ee3bfe6633317ad73010f9b66b9e2db932102f085d2c73930de4199f3eb46273c75c3042cdd39f4f03c107477b8778e05f3247315b624dc8f88df2b0d1eb645a762259dd3eab19996c45201104496ce0c3f47fad8d7ef98f3411b2ab113c24f46af0bdb6f0f8f81df9d1194780953f7cbffd10ef848832e52d2230eb5b80b0f435152ce1b01aba9006d5d1f03011115965aa800a3b261b0f5616ebc5be373c427adaaed8df1102eacde9ef9d2c38e115511f1158d0c0b3a79599dae6980059795a9417019c0619c363d5415e4625c0168d657ab00000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa7100000000098d5d72014155575600000000000e9afe6c000027106c15651585d9e2509e03410d88ba292e5060b04101005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009f902e3f30000000000b6fa283efffffff80000000068d657ab0000000068d657aa000009f62ce4d8c000000000b4c0439c0d6d8c877b317d842ed23393e73286991414dd17782c8d33bc29d051ef2bdad0ea299dd96ddbe1a83ce9e33ecd96a8484938bf724c3ec71b6130917926d7e9d8808a5cf97249bf74bedfb20552dd86b55f99bd60aabb441f7c8e9fb63e0bc53e4d978ace5f527e8375b892f23dbac6fb0b4504e2a7f05c942e3351cdb9116d98953a838c71f74507071414422f3082440019a194efb837eb1a5403ed3fdb4c6b85b843c56b4398673ed6f7b4fb2c9835ad36e13b134e3d914a33883c98ef2abc01c702bc7dad8e1c53cfd6e0777e5cd528fa5e614f4673c557fb5bfe3c008d66451b61bf157508dd7632cfb7200f2d05a4a8847a3c20597f03f13b30b68fb429cd0e0a96df" + ] + }, + "parsed": [ + { + "id": "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43", + "price": { + "price": "10965100000000", + "conf": "3069847614", + "expo": -8, + "publish_time": 1758877611 + }, + "ema_price": { + "price": "10952919800000", + "conf": "3032499100", + "expo": -8, + "publish_time": 1758877611 + }, + "metadata": { + "slot": 245038700, + "proof_available_time": 1758877612, + "prev_publish_time": 1758877610 + } + } + ] +} \ No newline at end of file