Forráskód Böngészése

feat(apps/price_pusher)!: switch to structured logger via pino (#1714)

* feat(apps/price_pusher)!: switch to structured logger via pino

This change switches from `console` to `pino` for logging that is a
structured logger and allows us to have json formatted loggers. In
adition to just that change, pino does an amazing job at logging the
errors by including all their data + the stacktrace.

This change does minimal change to the log level and the `console.log`
is changed to info or debug level based on my judgement.

This change is marked as breaking because it breaks any reliability/ops
based on the logs.

* fix: address review comments

* fix: add @solana/web3.js deps as its used

* refactor: make it more configurable
Ali Behjati 1 éve
szülő
commit
2faaa4366c

+ 25 - 13
apps/price_pusher/README.md

@@ -83,14 +83,14 @@ To run the price pusher, please run the following commands, replacing the comman
 
 ```sh
 # Please run the two following commands once from the root of the repo to build the code.
-npm install
+pnpm install
 pnpm exec lerna run build --scope @pythnetwork/price-pusher --include-dependencies
 
 # Navigate to the price_pusher folder
 cd apps/price_pusher
 
 # For EVM
-npm run start -- evm --endpoint wss://example-rpc.com \
+pnpm run start evm --endpoint wss://example-rpc.com \
     --pyth-contract-address 0xff1a0f4744e8582DF...... \
     --price-service-endpoint https://example-hermes-rpc.com \
     --price-config-file "path/to/price-config.beta.sample.yaml" \
@@ -100,7 +100,7 @@ npm run start -- evm --endpoint wss://example-rpc.com \
     [--override-gas-price-multiplier 1.1]
 
 # For Injective
-npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \
+pnpm run start injective --grpc-endpoint https://grpc-endpoint.com \
     --pyth-contract-address inj1z60tg0... --price-service-endpoint "https://example-hermes-rpc.com" \
     --price-config-file "path/to/price-config.beta.sample.yaml" \
     --mnemonic-file "path/to/mnemonic.txt" \
@@ -110,7 +110,7 @@ npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \
     [--polling-frequency 5]
 
 # For Aptos
-npm run start -- aptos --endpoint https://fullnode.testnet.aptoslabs.com/v1 \
+pnpm run start aptos --endpoint https://fullnode.testnet.aptoslabs.com/v1 \
     --pyth-contract-address 0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387 \
     --price-service-endpoint "https://example-hermes-rpc.com" \
     --price-config-file "path/to/price-config.beta.sample.yaml" \
@@ -119,7 +119,7 @@ npm run start -- aptos --endpoint https://fullnode.testnet.aptoslabs.com/v1 \
     [--polling-frequency 5]
 
 # For Sui
-npm run start -- sui \
+pnpm run start sui \
   --endpoint https://sui-testnet-rpc.allthatnode.com \
   --pyth-package-id 0x975e063f398f720af4f33ec06a927f14ea76ca24f7f8dd544aa62ab9d5d15f44 \
   --pyth-state-id 0xd8afde3a48b4ff7212bd6829a150f43f59043221200d63504d981f62bff2e27a \
@@ -134,7 +134,7 @@ npm run start -- sui \
   [--num-gas-objects 30]
 
 # For Near
-npm run start -- near \
+pnpm run start near \
   --node-url https://rpc.testnet.near.org \
   --network testnet \
   --account-id payer.testnet \
@@ -146,7 +146,7 @@ npm run start -- near \
   [--polling-frequency 5]
 
 # For Solana, using Jito (recommended)
-npm run start -- solana \
+pnpm run start solana \
   --endpoint https://api.mainnet-beta.solana.com \
   --keypair-file ./id.json \
   --shard-id 1 \
@@ -161,7 +161,7 @@ npm run start -- solana \
   [--polling-frequency 5]
 
 # For Solana, using Solana RPC
-npm run start -- solana \
+pnpm run start solana \
   --endpoint https://api.devnet.solana.com \
   --keypair-file ./id.json \
   --shard-id 1 \
@@ -184,23 +184,35 @@ docker run public.ecr.aws/pyth-network/xc-price-pusher:v<version> -- <above-argu
 To know more about the arguments the price-pusher accepts. You can run:
 
 ```sh
-npm run start -- --help
+pnpm run start --help
 
 # for specific network run
-npm run start -- {network} --help
+pnpm run start {network} --help
 ```
 
+### Logging
+
+By default, the logging is set to `info`. You can change the logging level by passing the argument `--log-level` with the desired level.
+The available levels are `error`, `warn`, `info`, `debug`, and `trace`. Also, the logs have JSON format. If you wish to run the code with
+human-readable logs, you can pipe the output of the program to `pino-pretty`. See the example below for more information on how to do this.
+
+You can configure the log level of some of the modules of the price pusher as well. The available modules are PriceServiceConnection, which
+is responsible for connecting to the Hermes price service, and Controller, which is responsible for checking the prices from the Hermes
+and the on-chain Pyth contract and deciding whether to push a new price. You can configure the log level of these modules by passing the
+`--price-service-connection-log-level` and `--controller-log-level` arguments, respectively.
+
 ### Example
 
 For example, to push `BTC/USD` and `BNB/USD` prices on Fantom testnet, run the following command:
 
 ```sh
-npm run dev -- evm \
+pnpm run dev evm \
   --endpoint https://endpoints.omniatech.io/v1/fantom/testnet/public \
   --pyth-contract-address 0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb \
   --price-service-endpoint https://hermes.pyth.network \
   --mnemonic-file "./mnemonic" \
-  --price-config-file "./price-config.stable.sample.yaml"
+  --price-config-file "./price-config.stable.sample.yaml" \
+  | pnpm exec pino-pretty # Make logs human-readable
 ```
 
 [`price-config.stable.sample.yaml`](./price-config.stable.sample.yaml) contains configuration for `BTC/USD`
@@ -210,7 +222,7 @@ contains the same configuration for `BTC/USD` and `BNB/USD` on Pyth beta data so
 You can also provide a config file instead of providing command line options, run the following command:
 
 ```sh
-npm run start -- injective --config "./config.injective.sample.json"
+pnpm run start injective --config "./config.injective.sample.json"
 ```
 
 [`config.injective.sample.json`](./config.injective.sample.json) contains configuration to publish on Injective testnet.

+ 5 - 1
apps/price_pusher/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@pythnetwork/price-pusher",
-  "version": "6.8.0",
+  "version": "7.0.0-alpha",
   "description": "Pyth Price Pusher",
   "homepage": "https://pyth.network",
   "main": "lib/index.js",
@@ -46,6 +46,7 @@
     "@typescript-eslint/parser": "^6.0.0",
     "eslint": "^8.13.0",
     "jest": "^29.7.0",
+    "pino-pretty": "^11.2.1",
     "prettier": "^2.6.2",
     "ts-jest": "^29.1.1",
     "ts-node": "^10.9.1",
@@ -62,11 +63,14 @@
     "@pythnetwork/pyth-solana-receiver": "workspace:*",
     "@pythnetwork/pyth-sui-js": "workspace:*",
     "@pythnetwork/solana-utils": "workspace:*",
+    "@solana/web3.js": "^1.93.0",
     "@truffle/hdwallet-provider": "^2.1.3",
+    "@types/pino": "^7.0.5",
     "aptos": "^1.8.5",
     "jito-ts": "^3.0.1",
     "joi": "^17.6.0",
     "near-api-js": "^3.0.2",
+    "pino": "^9.2.0",
     "web3": "^1.8.1",
     "web3-core": "^1.8.1",
     "web3-eth-contract": "^1.8.1",

+ 24 - 18
apps/price_pusher/src/aptos/aptos.ts

@@ -7,17 +7,19 @@ import {
 import { AptosAccount, AptosClient } from "aptos";
 import { DurationInSeconds } from "../utils";
 import { PriceServiceConnection } from "@pythnetwork/price-service-client";
+import { Logger } from "pino";
 
 export class AptosPriceListener extends ChainPriceListener {
   constructor(
     private pythModule: string,
     private endpoint: string,
     priceItems: PriceItem[],
+    private logger: Logger,
     config: {
       pollingFrequency: DurationInSeconds;
     }
   ) {
-    super("aptos", config.pollingFrequency, priceItems);
+    super(config.pollingFrequency, priceItems);
   }
 
   async getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined> {
@@ -46,7 +48,7 @@ export class AptosPriceListener extends ChainPriceListener {
       const price =
         multiplier * Number(priceItemRes.price_feed.price.price.magnitude);
 
-      console.log(
+      this.logger.debug(
         `Polled an Aptos on-chain price for feed ${this.priceIdToAlias.get(
           priceId
         )} (${priceId}).`
@@ -57,11 +59,11 @@ export class AptosPriceListener extends ChainPriceListener {
         conf: priceItemRes.price_feed.price.conf,
         publishTime: Number(priceItemRes.price_feed.price.timestamp),
       };
-    } catch (e) {
-      console.error(
-        `Polling Aptos on-chain price for ${priceId} failed. Error:`
+    } catch (err) {
+      this.logger.error(
+        err,
+        `Polling Aptos on-chain price for ${priceId} failed.`
       );
-      console.error(e);
       return undefined;
     }
   }
@@ -88,6 +90,7 @@ export class AptosPricePusher implements IPricePusher {
 
   constructor(
     private priceServiceConnection: PriceServiceConnection,
+    private logger: Logger,
     private pythContractAddress: string,
     private endpoint: string,
     private mnemonic: string,
@@ -126,9 +129,8 @@ export class AptosPricePusher implements IPricePusher {
     try {
       // get the latest VAAs for updatePriceFeed and then push them
       priceFeedUpdateData = await this.getPriceFeedsUpdateData(priceIds);
-    } catch (e) {
-      console.error("Error fetching the latest vaas to push");
-      console.error(e);
+    } catch (err) {
+      this.logger.error(err, "Error fetching the latest vaas to push.");
       return;
     }
 
@@ -158,7 +160,10 @@ export class AptosPricePusher implements IPricePusher {
       const signedTx = await client.signTransaction(account, rawTx);
       const pendingTx = await client.submitTransaction(signedTx);
 
-      console.log("Successfully broadcasted txHash:", pendingTx.hash);
+      this.logger.debug(
+        { hash: pendingTx.hash },
+        "Successfully broadcasted tx."
+      );
 
       // Sometimes broadcasted txs don't make it on-chain and they cause our sequence number
       // to go out of sync. Missing transactions are rare and we don't want this check to block
@@ -167,9 +172,8 @@ export class AptosPricePusher implements IPricePusher {
       this.waitForTransactionConfirmation(client, pendingTx.hash);
 
       return;
-    } catch (e: any) {
-      console.error("Error executing messages");
-      console.error(e);
+    } catch (err: any) {
+      this.logger.error(err, "Error executing messages");
 
       // Reset the sequence number to re-sync it (in case that was the issue)
       this.lastSequenceNumber = undefined;
@@ -189,10 +193,12 @@ export class AptosPricePusher implements IPricePusher {
         timeoutSecs: 10,
       });
 
-      console.log(`Transaction with txHash "${txHash}" confirmed.`);
-    } catch (e) {
-      console.error(`Transaction with txHash "${txHash}" failed to confirm.`);
-      console.error(e);
+      this.logger.info({ hash: txHash }, `Transaction confirmed.`);
+    } catch (err) {
+      this.logger.error(
+        { err, hash: txHash },
+        `Transaction failed to confirm.`
+      );
 
       this.lastSequenceNumber = undefined;
     }
@@ -218,7 +224,7 @@ export class AptosPricePusher implements IPricePusher {
           this.lastSequenceNumber = Number(
             (await client.getAccount(account.address())).sequence_number
           );
-          console.log(
+          this.logger.debug(
             `Fetched account sequence number: ${this.lastSequenceNumber}`
           );
           return this.lastSequenceNumber;

+ 21 - 10
apps/price_pusher/src/aptos/command.ts

@@ -11,6 +11,7 @@ import {
   APTOS_ACCOUNT_HD_PATH,
 } from "./aptos";
 import { AptosAccount } from "aptos";
+import pino from "pino";
 
 export default {
   command: "aptos",
@@ -37,6 +38,9 @@ export default {
     ...options.pythContractAddress,
     ...options.pollingFrequency,
     ...options.pushingFrequency,
+    ...options.logLevel,
+    ...options.priceServiceConnectionLogLevel,
+    ...options.controllerLogLevel,
   },
   handler: function (argv: any) {
     // FIXME: type checks for this
@@ -49,44 +53,50 @@ export default {
       pushingFrequency,
       pollingFrequency,
       overrideGasPriceMultiplier,
+      logLevel,
+      priceServiceConnectionLogLevel,
+      controllerLogLevel,
     } = argv;
 
+    const logger = pino({ level: logLevel });
+
     const priceConfigs = readPriceConfigFile(priceConfigFile);
     const priceServiceConnection = new PriceServiceConnection(
       priceServiceEndpoint,
       {
-        logger: {
-          // Log only warnings and errors from the price service client
-          info: () => undefined,
-          warn: console.warn,
-          error: console.error,
-          debug: () => undefined,
-          trace: () => undefined,
-        },
+        logger: logger.child(
+          { module: "PriceServiceConnection" },
+          { level: priceServiceConnectionLogLevel }
+        ),
       }
     );
+
     const mnemonic = fs.readFileSync(mnemonicFile, "utf-8").trim();
     const account = AptosAccount.fromDerivePath(
       APTOS_ACCOUNT_HD_PATH,
       mnemonic
     );
-    console.log(`Pushing from account address: ${account.address()}`);
+    logger.info(`Pushing from account address: ${account.address()}`);
 
     const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
 
     const pythListener = new PythPriceListener(
       priceServiceConnection,
-      priceItems
+      priceItems,
+      logger.child({ module: "PythPriceListener" })
     );
 
     const aptosListener = new AptosPriceListener(
       pythContractAddress,
       endpoint,
       priceItems,
+      logger.child({ module: "AptosPriceListener" }),
       { pollingFrequency }
     );
+
     const aptosPusher = new AptosPricePusher(
       priceServiceConnection,
+      logger.child({ module: "AptosPricePusher" }),
       pythContractAddress,
       endpoint,
       mnemonic,
@@ -98,6 +108,7 @@ export default {
       pythListener,
       aptosListener,
       aptosPusher,
+      logger.child({ module: "Controller" }, { level: controllerLogLevel }),
       { pushingFrequency }
     );
 

+ 13 - 6
apps/price_pusher/src/controller.ts

@@ -2,6 +2,7 @@ import { UnixTimestamp } from "@pythnetwork/price-service-client";
 import { DurationInSeconds, sleep } from "./utils";
 import { IPriceListener, IPricePusher } from "./interface";
 import { PriceConfig, shouldUpdate, UpdateCondition } from "./price-config";
+import { Logger } from "pino";
 
 export class Controller {
   private pushingFrequency: DurationInSeconds;
@@ -10,6 +11,7 @@ export class Controller {
     private sourcePriceListener: IPriceListener,
     private targetPriceListener: IPriceListener,
     private targetChainPricePusher: IPricePusher,
+    private logger: Logger,
     config: {
       pushingFrequency: DurationInSeconds;
     }
@@ -45,7 +47,8 @@ export class Controller {
         const priceShouldUpdate = shouldUpdate(
           priceConfig,
           sourceLatestPrice,
-          targetLatestPrice
+          targetLatestPrice,
+          this.logger
         );
         if (priceShouldUpdate == UpdateCondition.YES) {
           pushThresholdMet = true;
@@ -60,17 +63,21 @@ export class Controller {
         }
       }
       if (pushThresholdMet) {
-        console.log(
-          "Some of the above values passed the threshold. Will push the price."
+        this.logger.info(
+          {
+            priceIds: pricesToPush.map((priceConfig) => ({
+              id: priceConfig.id,
+              alias: priceConfig.alias,
+            })),
+          },
+          "Some of the checks triggered pushing update. Will push the updates for some feeds."
         );
 
         // note that the priceIds are without leading "0x"
         const priceIds = pricesToPush.map((priceConfig) => priceConfig.id);
         this.targetChainPricePusher.updatePriceFeed(priceIds, pubTimesToPush);
       } else {
-        console.log(
-          "None of the above values passed the threshold. No push needed."
-        );
+        this.logger.info("None of the checks were triggered. No push needed.");
       }
 
       await sleep(this.pushingFrequency * 1000);

+ 34 - 16
apps/price_pusher/src/evm/command.ts

@@ -1,12 +1,13 @@
 import { PriceServiceConnection } from "@pythnetwork/price-service-client";
+import fs from "fs";
+import { Options } from "yargs";
 import * as options from "../options";
 import { readPriceConfigFile } from "../price-config";
-import fs from "fs";
 import { PythPriceListener } from "../pyth-price-listener";
 import { Controller } from "../controller";
-import { Options } from "yargs";
 import { EvmPriceListener, EvmPricePusher, PythContractFactory } from "./evm";
 import { getCustomGasStation } from "./custom-gas-station";
+import pino from "pino";
 
 export default {
   command: "evm",
@@ -72,6 +73,9 @@ export default {
     ...options.pythContractAddress,
     ...options.pollingFrequency,
     ...options.pushingFrequency,
+    ...options.logLevel,
+    ...options.priceServiceConnectionLogLevel,
+    ...options.controllerLogLevel,
   },
   handler: function (argv: any) {
     // FIXME: type checks for this
@@ -89,29 +93,32 @@ export default {
       overrideGasPriceMultiplierCap,
       gasLimit,
       updateFeeMultiplier,
+      logLevel,
+      priceServiceConnectionLogLevel,
+      controllerLogLevel,
     } = argv;
 
+    const logger = pino({ level: logLevel });
+
     const priceConfigs = readPriceConfigFile(priceConfigFile);
     const priceServiceConnection = new PriceServiceConnection(
       priceServiceEndpoint,
       {
-        logger: {
-          // Log only warnings and errors from the price service client
-          info: () => undefined,
-          warn: console.warn,
-          error: console.error,
-          debug: () => undefined,
-          trace: () => undefined,
-        },
+        logger: logger.child(
+          { module: "PriceServiceConnection" },
+          { level: priceServiceConnectionLogLevel }
+        ),
       }
     );
+
     const mnemonic = fs.readFileSync(mnemonicFile, "utf-8").trim();
 
     const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
 
     const pythListener = new PythPriceListener(
       priceServiceConnection,
-      priceItems
+      priceItems,
+      logger.child({ module: "PythPriceListener" })
     );
 
     const pythContractFactory = new PythContractFactory(
@@ -119,20 +126,30 @@ export default {
       mnemonic,
       pythContractAddress
     );
-    console.log(
+    logger.info(
       `Pushing updates from wallet address: ${pythContractFactory
         .createWeb3PayerProvider()
         .getAddress()}`
     );
 
-    const evmListener = new EvmPriceListener(pythContractFactory, priceItems, {
-      pollingFrequency,
-    });
+    const evmListener = new EvmPriceListener(
+      pythContractFactory,
+      priceItems,
+      logger.child({ module: "EvmPriceListener" }),
+      {
+        pollingFrequency,
+      }
+    );
 
-    const gasStation = getCustomGasStation(customGasStation, txSpeed);
+    const gasStation = getCustomGasStation(
+      logger.child({ module: "CustomGasStation" }),
+      customGasStation,
+      txSpeed
+    );
     const evmPusher = new EvmPricePusher(
       priceServiceConnection,
       pythContractFactory,
+      logger.child({ module: "EvmPricePusher" }),
       overrideGasPriceMultiplier,
       overrideGasPriceMultiplierCap,
       updateFeeMultiplier,
@@ -145,6 +162,7 @@ export default {
       pythListener,
       evmListener,
       evmPusher,
+      logger.child({ module: "Controller" }, { level: controllerLogLevel }),
       { pushingFrequency }
     );
 

+ 9 - 5
apps/price_pusher/src/evm/custom-gas-station.ts

@@ -6,6 +6,7 @@ import {
   txSpeeds,
   customGasChainIds,
 } from "../utils";
+import { Logger } from "pino";
 
 type chainMethods = Record<CustomGasChainId, () => Promise<string | undefined>>;
 
@@ -15,7 +16,9 @@ export class CustomGasStation {
   private chainMethods: chainMethods = {
     137: this.fetchMaticMainnetGasPrice.bind(this),
   };
-  constructor(chain: number, speed: string) {
+  private logger: Logger;
+  constructor(logger: Logger, chain: number, speed: string) {
+    this.logger = logger;
     this.speed = verifyValidOption(speed, txSpeeds);
     this.chain = verifyValidOption(chain, customGasChainIds);
   }
@@ -31,21 +34,22 @@ export class CustomGasStation {
       const gasPrice = jsonRes[this.speed].maxFee;
       const gweiGasPrice = Web3.utils.toWei(gasPrice.toFixed(2), "Gwei");
       return gweiGasPrice.toString();
-    } catch (e) {
-      console.error(
+    } catch (err) {
+      this.logger.error(
+        err,
         "Failed to fetch gas price from Matic mainnet. Returning undefined"
       );
-      console.error(e);
       return undefined;
     }
   }
 }
 
 export function getCustomGasStation(
+  logger: Logger,
   customGasStation?: number,
   txSpeed?: string
 ) {
   if (customGasStation && txSpeed) {
-    return new CustomGasStation(customGasStation, txSpeed);
+    return new CustomGasStation(logger, customGasStation, txSpeed);
   }
 }

+ 42 - 24
apps/price_pusher/src/evm/evm.ts

@@ -11,6 +11,7 @@ import AbstractPythAbi from "@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.js
 import HDWalletProvider from "@truffle/hdwallet-provider";
 import Web3 from "web3";
 import { HttpProvider, WebsocketProvider } from "web3-core";
+import { Logger } from "pino";
 import { isWsEndpoint } from "../utils";
 import {
   PriceServiceConnection,
@@ -24,28 +25,33 @@ import { ProviderOrUrl } from "@truffle/hdwallet-provider/dist/constructor/types
 export class EvmPriceListener extends ChainPriceListener {
   private pythContractFactory: PythContractFactory;
   private pythContract: Contract;
+  private logger: Logger;
 
   constructor(
     pythContractFactory: PythContractFactory,
     priceItems: PriceItem[],
+    logger: Logger,
     config: {
       pollingFrequency: DurationInSeconds;
     }
   ) {
-    super("Evm", config.pollingFrequency, priceItems);
+    super(config.pollingFrequency, priceItems);
 
     this.pythContractFactory = pythContractFactory;
     this.pythContract = this.pythContractFactory.createPythContract();
+    this.logger = logger;
   }
 
   // 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() {
     if (this.pythContractFactory.hasWebsocketProvider()) {
-      console.log("Subscribing to the target network pyth contract events...");
+      this.logger.info(
+        "Subscribing to the target network pyth contract events..."
+      );
       this.startSubscription();
     } else {
-      console.log(
+      this.logger.info(
         "The target network RPC endpoint is not Websocket. " +
           "Listening for updates only via polling...."
       );
@@ -71,12 +77,15 @@ export class EvmPriceListener extends ChainPriceListener {
 
   private onPriceFeedUpdate(err: Error | null, event: EventData) {
     if (err !== null) {
-      console.error("PriceFeedUpdate EventEmitter received an error..");
+      this.logger.error(
+        err,
+        "PriceFeedUpdate EventEmitter received an error.."
+      );
       throw err;
     }
 
     const priceId = removeLeading0x(event.returnValues.id);
-    console.log(
+    this.logger.debug(
       `Received a new Evm PriceFeedUpdate event for price feed ${this.priceIdToAlias.get(
         priceId
       )} (${priceId}).`
@@ -99,13 +108,12 @@ export class EvmPriceListener extends ChainPriceListener {
       priceRaw = await this.pythContract.methods
         .getPriceUnsafe(addLeading0x(priceId))
         .call();
-    } catch (e) {
-      console.error(`Polling on-chain price for ${priceId} failed. Error:`);
-      console.error(e);
+    } catch (err) {
+      this.logger.error(err, `Polling on-chain price for ${priceId} failed.`);
       return undefined;
     }
 
-    console.log(
+    this.logger.debug(
       `Polled an EVM on chain price for feed ${this.priceIdToAlias.get(
         priceId
       )} (${priceId}).`
@@ -129,6 +137,7 @@ export class EvmPricePusher implements IPricePusher {
   constructor(
     private connection: PriceServiceConnection,
     pythContractFactory: PythContractFactory,
+    private logger: Logger,
     private overrideGasPriceMultiplier: number,
     private overrideGasPriceMultiplierCap: number,
     private updateFeeMultiplier: number,
@@ -163,8 +172,6 @@ export class EvmPricePusher implements IPricePusher {
       priceIdsWith0x
     );
 
-    console.log("Pushing ", priceIdsWith0x);
-
     let updateFee;
 
     try {
@@ -172,10 +179,11 @@ export class EvmPricePusher implements IPricePusher {
         .getUpdateFee(priceFeedUpdateData)
         .call();
       updateFee = Number(updateFee) * (this.updateFeeMultiplier || 1);
-      console.log(`Update fee: ${updateFee}`);
+      this.logger.debug(`Update fee: ${updateFee}`);
     } catch (e: any) {
-      console.error(
-        "An unidentified error has occured when getting the update fee:"
+      this.logger.error(
+        e,
+        "An unidentified error has occured when getting the update fee."
       );
       throw e;
     }
@@ -213,7 +221,7 @@ export class EvmPricePusher implements IPricePusher {
 
     const txNonce = lastExecutedNonce + 1;
 
-    console.log(`Using gas price: ${gasPrice} and nonce: ${txNonce}`);
+    this.logger.debug(`Using gas price: ${gasPrice} and nonce: ${txNonce}`);
 
     this.pythContract.methods
       .updatePriceFeedsIfNecessary(
@@ -228,7 +236,7 @@ export class EvmPricePusher implements IPricePusher {
         gasLimit: this.gasLimit,
       })
       .on("transactionHash", (hash: string) => {
-        console.log(`Successful. Tx hash: ${hash}`);
+        this.logger.info({ hash }, "Price update successful");
       })
       .on("error", (err: Error, receipt?: TransactionReceipt) => {
         if (err.message.includes("revert")) {
@@ -236,7 +244,8 @@ export class EvmPricePusher implements IPricePusher {
           // doesn't return any information why the call has reverted. Assuming that
           // the update data is valid there is no possible rejection cause other than
           // the target chain price being already updated.
-          console.log(
+          this.logger.info(
+            { err, receipt },
             "Execution reverted. With high probability, the target chain price " +
               "has already updated, Skipping this push."
           );
@@ -248,7 +257,8 @@ export class EvmPricePusher implements IPricePusher {
           err.message.includes("nonce too low") ||
           err.message.includes("invalid nonce")
         ) {
-          console.log(
+          this.logger.info(
+            { err, receipt },
             "The nonce is incorrect (are multiple users using this account?). Skipping this push."
           );
           return;
@@ -259,7 +269,8 @@ export class EvmPricePusher implements IPricePusher {
           // LastPushAttempt was stored with the class
           // Next time the update will be executing, it will check the last attempt
           // and increase the gas price accordingly.
-          console.log(
+          this.logger.info(
+            { err, receipt },
             "The transaction failed with error: max fee per gas less than block base fee "
           );
           return;
@@ -268,12 +279,16 @@ export class EvmPricePusher implements IPricePusher {
         if (
           err.message.includes("sender doesn't have enough funds to send tx.")
         ) {
-          console.error("Payer is out of balance, please top it up.");
+          this.logger.error(
+            { err, receipt },
+            "Payer is out of balance, please top it up."
+          );
           throw err;
         }
 
         if (err.message.includes("transaction underpriced")) {
-          console.error(
+          this.logger.error(
+            { err, receipt },
             "The gas price of the transaction is too low. Skipping this push. " +
               "You might want to use a custom gas station or increase the override gas price " +
               "multiplier to increase the likelihood of the transaction landing on-chain."
@@ -282,14 +297,17 @@ export class EvmPricePusher implements IPricePusher {
         }
 
         if (err.message.includes("could not replace existing tx")) {
-          console.log(
+          this.logger.error(
+            { err, receipt },
             "A transaction with the same nonce has been mined and this one is no longer needed."
           );
           return;
         }
 
-        console.error("An unidentified error has occured:");
-        console.error(receipt);
+        this.logger.error(
+          { err, receipt },
+          "An unidentified error has occured."
+        );
         throw err;
       });
 

+ 18 - 9
apps/price_pusher/src/injective/command.ts

@@ -7,6 +7,7 @@ import { PythPriceListener } from "../pyth-price-listener";
 import { Controller } from "../controller";
 import { Options } from "yargs";
 import { getNetworkInfo } from "@injectivelabs/networks";
+import pino from "pino";
 
 export default {
   command: "injective",
@@ -35,6 +36,9 @@ export default {
     ...options.pythContractAddress,
     ...options.pollingFrequency,
     ...options.pushingFrequency,
+    ...options.logLevel,
+    ...options.priceServiceConnectionLogLevel,
+    ...options.controllerLogLevel,
   },
   handler: function (argv: any) {
     // FIXME: type checks for this
@@ -48,8 +52,13 @@ export default {
       pushingFrequency,
       pollingFrequency,
       network,
+      logLevel,
+      priceServiceConnectionLogLevel,
+      controllerLogLevel,
     } = argv;
 
+    const logger = pino({ level: logLevel });
+
     if (network !== "testnet" && network !== "mainnet") {
       throw new Error("Please specify network. One of [testnet, mainnet]");
     }
@@ -58,14 +67,10 @@ export default {
     const priceServiceConnection = new PriceServiceConnection(
       priceServiceEndpoint,
       {
-        logger: {
-          // Log only warnings and errors from the price service client
-          info: () => undefined,
-          warn: console.warn,
-          error: console.error,
-          debug: () => undefined,
-          trace: () => undefined,
-        },
+        logger: logger.child(
+          { module: "PriceServiceConnection" },
+          { level: priceServiceConnectionLogLevel }
+        ),
       }
     );
     const mnemonic = fs.readFileSync(mnemonicFile, "utf-8").trim();
@@ -74,13 +79,15 @@ export default {
 
     const pythListener = new PythPriceListener(
       priceServiceConnection,
-      priceItems
+      priceItems,
+      logger.child({ module: "PythPriceListener" })
     );
 
     const injectiveListener = new InjectivePriceListener(
       pythContractAddress,
       grpcEndpoint,
       priceItems,
+      logger.child({ module: "InjectivePriceListener" }),
       {
         pollingFrequency,
       }
@@ -89,6 +96,7 @@ export default {
       priceServiceConnection,
       pythContractAddress,
       grpcEndpoint,
+      logger.child({ module: "InjectivePricePusher" }),
       mnemonic,
       {
         chainId: getNetworkInfo(network).chainId,
@@ -101,6 +109,7 @@ export default {
       pythListener,
       injectiveListener,
       injectivePusher,
+      logger.child({ module: "Controller" }, { level: controllerLogLevel }),
       { pushingFrequency }
     );
 

+ 19 - 20
apps/price_pusher/src/injective/injective.ts

@@ -19,6 +19,7 @@ import {
   TxResponse,
   createTransactionFromMsg,
 } from "@injectivelabs/sdk-ts";
+import { Logger } from "pino";
 import { Account } from "@injectivelabs/sdk-ts/dist/cjs/client/chain/types/auth";
 
 const DEFAULT_GAS_PRICE = 500000000;
@@ -46,11 +47,12 @@ export class InjectivePriceListener extends ChainPriceListener {
     private pythContractAddress: string,
     private grpcEndpoint: string,
     priceItems: PriceItem[],
+    private logger: Logger,
     config: {
       pollingFrequency: DurationInSeconds;
     }
   ) {
-    super("Injective", config.pollingFrequency, priceItems);
+    super(config.pollingFrequency, priceItems);
   }
 
   async getOnChainPriceInfo(
@@ -66,13 +68,12 @@ export class InjectivePriceListener extends ChainPriceListener {
 
       const json = Buffer.from(data).toString();
       priceQueryResponse = JSON.parse(json);
-    } catch (e) {
-      console.error(`Polling on-chain price for ${priceId} failed. Error:`);
-      console.error(e);
+    } catch (err) {
+      this.logger.error(err, `Polling on-chain price for ${priceId} failed.`);
       return undefined;
     }
 
-    console.log(
+    this.logger.debug(
       `Polled an Injective on chain price for feed ${this.priceIdToAlias.get(
         priceId
       )} (${priceId}).`
@@ -100,6 +101,7 @@ export class InjectivePricePusher implements IPricePusher {
     private priceServiceConnection: PriceServiceConnection,
     private pythContractAddress: string,
     private grpcEndpoint: string,
+    private logger: Logger,
     mnemonic: string,
     chainConfig?: Partial<InjectiveConfig>
   ) {
@@ -207,9 +209,8 @@ export class InjectivePricePusher implements IPricePusher {
     try {
       // get the latest VAAs for updatePriceFeed and then push them
       priceFeedUpdateObject = await this.getPriceFeedUpdateObject(priceIds);
-    } catch (e) {
-      console.error("Error fetching the latest vaas to push");
-      console.error(e);
+    } catch (err) {
+      this.logger.error(err, "Error fetching the latest vaas to push");
       return;
     }
 
@@ -229,9 +230,8 @@ export class InjectivePricePusher implements IPricePusher {
 
       const json = Buffer.from(data).toString();
       updateFeeQueryResponse = JSON.parse(json);
-    } catch (e) {
-      console.error("Error fetching update fee");
-      console.error(e);
+    } catch (err) {
+      this.logger.error(err, "Error fetching update fee");
       return;
     }
 
@@ -244,22 +244,21 @@ export class InjectivePricePusher implements IPricePusher {
       });
 
       const rs = await this.signAndBroadcastMsg(executeMsg);
-      console.log("Succesfully broadcasted txHash:", rs.txHash);
-    } catch (e: any) {
-      if (e.message.match(/account inj[a-zA-Z0-9]+ not found/) !== null) {
-        console.error(e);
+      this.logger.info({ hash: rs.txHash }, "Succesfully broadcasted txHash");
+    } catch (err: any) {
+      if (err.message.match(/account inj[a-zA-Z0-9]+ not found/) !== null) {
+        this.logger.error(err, "Account not found");
         throw new Error("Please check the mnemonic");
       }
 
       if (
-        e.message.match(/insufficient/) !== null &&
-        e.message.match(/funds/) !== null
+        err.message.match(/insufficient/) !== null &&
+        err.message.match(/funds/) !== null
       ) {
-        console.error(e);
+        this.logger.error(err, "Insufficient funds");
         throw new Error("Insufficient funds");
       }
-      console.error("Error executing messages");
-      console.log(e);
+      this.logger.error(err, "Error executing messages");
     }
   }
 }

+ 0 - 4
apps/price_pusher/src/interface.ts

@@ -23,7 +23,6 @@ export abstract class ChainPriceListener implements IPriceListener {
   protected priceIdToAlias: Map<HexString, string>;
 
   constructor(
-    private chain: string,
     private pollingFrequency: DurationInSeconds,
     protected priceItems: PriceItem[]
   ) {
@@ -34,9 +33,6 @@ export abstract class ChainPriceListener implements IPriceListener {
   }
 
   async start() {
-    console.log(
-      `Polling the prices on ${this.chain} every ${this.pollingFrequency} seconds...`
-    );
     setInterval(this.pollPrices.bind(this), this.pollingFrequency * 1000);
 
     await this.pollPrices();

+ 29 - 13
apps/price_pusher/src/near/command.ts

@@ -5,6 +5,7 @@ import { PythPriceListener } from "../pyth-price-listener";
 import { Controller } from "../controller";
 import { Options } from "yargs";
 import { NearAccount, NearPriceListener, NearPricePusher } from "./near";
+import pino from "pino";
 
 export default {
   command: "near",
@@ -36,6 +37,9 @@ export default {
     ...options.pythContractAddress,
     ...options.pollingFrequency,
     ...options.pushingFrequency,
+    ...options.logLevel,
+    ...options.priceServiceConnectionLogLevel,
+    ...options.controllerLogLevel,
   },
   handler: function (argv: any) {
     // FIXME: type checks for this
@@ -49,20 +53,21 @@ export default {
       pythContractAddress,
       pushingFrequency,
       pollingFrequency,
+      logLevel,
+      priceServiceConnectionLogLevel,
+      controllerLogLevel,
     } = argv;
 
+    const logger = pino({ level: logLevel });
+
     const priceConfigs = readPriceConfigFile(priceConfigFile);
     const priceServiceConnection = new PriceServiceConnection(
       priceServiceEndpoint,
       {
-        logger: {
-          // Log only warnings and errors from the price service client
-          info: () => undefined,
-          warn: console.warn,
-          error: console.error,
-          debug: () => undefined,
-          trace: () => undefined,
-        },
+        logger: logger.child(
+          { module: "PriceServiceConnection" },
+          { level: priceServiceConnectionLogLevel }
+        ),
       }
     );
 
@@ -70,7 +75,8 @@ export default {
 
     const pythListener = new PythPriceListener(
       priceServiceConnection,
-      priceItems
+      priceItems,
+      logger
     );
 
     const nearAccount = new NearAccount(
@@ -81,17 +87,27 @@ export default {
       pythContractAddress
     );
 
-    const nearListener = new NearPriceListener(nearAccount, priceItems, {
-      pollingFrequency,
-    });
+    const nearListener = new NearPriceListener(
+      nearAccount,
+      priceItems,
+      logger.child({ module: "NearPriceListener" }),
+      {
+        pollingFrequency,
+      }
+    );
 
-    const nearPusher = new NearPricePusher(nearAccount, priceServiceConnection);
+    const nearPusher = new NearPricePusher(
+      nearAccount,
+      priceServiceConnection,
+      logger.child({ module: "NearPricePusher" })
+    );
 
     const controller = new Controller(
       priceConfigs,
       pythListener,
       nearListener,
       nearPusher,
+      logger.child({ module: "Controller" }, { level: controllerLogLevel }),
       { pushingFrequency }
     );
 

+ 19 - 24
apps/price_pusher/src/near/near.ts

@@ -21,23 +21,25 @@ import {
   FinalExecutionOutcome,
 } from "near-api-js/lib/providers/provider";
 import { InMemoryKeyStore } from "near-api-js/lib/key_stores";
+import { Logger } from "pino";
 
 export class NearPriceListener extends ChainPriceListener {
   constructor(
     private account: NearAccount,
     priceItems: PriceItem[],
+    private logger: Logger,
     config: {
       pollingFrequency: DurationInSeconds;
     }
   ) {
-    super("near", config.pollingFrequency, priceItems);
+    super(config.pollingFrequency, priceItems);
   }
 
   async getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined> {
     try {
       const priceRaw = await this.account.getPriceUnsafe(priceId);
 
-      console.log(
+      this.logger.debug(
         `Polled a NEAR on chain price for feed ${this.priceIdToAlias.get(
           priceId
         )} (${priceId}) ${JSON.stringify(priceRaw)}.`
@@ -52,9 +54,8 @@ export class NearPriceListener extends ChainPriceListener {
       } else {
         return undefined;
       }
-    } catch (e) {
-      console.error(`Polling on-chain price for ${priceId} failed. Error:`);
-      console.error(e);
+    } catch (err) {
+      this.logger.error(err, `Polling on-chain price for ${priceId} failed.:`);
       return undefined;
     }
   }
@@ -63,7 +64,8 @@ export class NearPriceListener extends ChainPriceListener {
 export class NearPricePusher implements IPricePusher {
   constructor(
     private account: NearAccount,
-    private connection: PriceServiceConnection
+    private connection: PriceServiceConnection,
+    private logger: Logger
   ) {}
 
   async updatePriceFeed(
@@ -80,20 +82,18 @@ export class NearPricePusher implements IPricePusher {
     let priceFeedUpdateData;
     try {
       priceFeedUpdateData = await this.getPriceFeedsUpdateData(priceIds);
-    } catch (e: any) {
-      console.error(new Date(), "getPriceFeedsUpdateData failed:", e);
+    } catch (err: any) {
+      this.logger.error(err, "getPriceFeedsUpdateData failed");
       return;
     }
 
-    console.log("Pushing ", priceIds);
-
     for (const data of priceFeedUpdateData) {
       let updateFee;
       try {
         updateFee = await this.account.getUpdateFeeEstimate(data);
-        console.log(`Update fee: ${updateFee}`);
-      } catch (e: any) {
-        console.error(new Date(), "getUpdateFeeEstimate failed:", e);
+        this.logger.debug(`Update fee: ${updateFee}`);
+      } catch (err: any) {
+        this.logger.error(err, "getUpdateFeeEstimate failed");
         continue;
       }
 
@@ -116,20 +116,15 @@ export class NearPricePusher implements IPricePusher {
           true
         );
         if (is_success) {
-          console.log(
-            new Date(),
-            "updatePriceFeeds successful. Tx hash: ",
-            outcome["transaction"]["hash"]
+          this.logger.info(
+            { hash: outcome["transaction"]["hash"] },
+            "updatePriceFeeds successful."
           );
         } else {
-          console.error(
-            new Date(),
-            "updatePriceFeeds failed:",
-            JSON.stringify(failureMessages, undefined, 2)
-          );
+          this.logger.error({ failureMessages }, "updatePriceFeeds failed");
         }
-      } catch (e: any) {
-        console.error(new Date(), "updatePriceFeeds failed:", e);
+      } catch (err: any) {
+        this.logger.error(err, "updatePriceFeeds failed");
       }
     }
   }

+ 30 - 0
apps/price_pusher/src/options.ts

@@ -56,3 +56,33 @@ export const mnemonicFile = {
     required: true,
   } as Options,
 };
+
+export const logLevel = {
+  "log-level": {
+    description: "Log level",
+    type: "string",
+    required: false,
+    default: "info",
+    choices: ["trace", "debug", "info", "warn", "error"],
+  } as Options,
+};
+
+export const priceServiceConnectionLogLevel = {
+  "price-service-connection-log-level": {
+    description: "Log level for the price service connection.",
+    type: "string",
+    required: false,
+    default: "warn",
+    choices: ["trace", "debug", "info", "warn", "error"],
+  } as Options,
+};
+
+export const controllerLogLevel = {
+  "controller-log-level": {
+    description: "Log level for the controller.",
+    type: "string",
+    required: false,
+    default: "info",
+    choices: ["trace", "debug", "info", "warn", "error"],
+  } as Options,
+};

+ 8 - 9
apps/price_pusher/src/price-config.ts

@@ -2,6 +2,7 @@ import { HexString } from "@pythnetwork/price-service-client";
 import Joi from "joi";
 import YAML from "yaml";
 import fs from "fs";
+import { Logger } from "pino";
 import { DurationInSeconds, PctNumber, removeLeading0x } from "./utils";
 import { PriceInfo } from "./interface";
 
@@ -90,7 +91,8 @@ export enum UpdateCondition {
 export function shouldUpdate(
   priceConfig: PriceConfig,
   sourceLatestPrice: PriceInfo | undefined,
-  targetLatestPrice: PriceInfo | undefined
+  targetLatestPrice: PriceInfo | undefined,
+  logger: Logger
 ): UpdateCondition {
   const priceId = priceConfig.id;
 
@@ -101,7 +103,7 @@ export function shouldUpdate(
 
   // It means that price never existed there. So we should push the latest price feed.
   if (targetLatestPrice === undefined) {
-    console.log(
+    logger.info(
       `${priceConfig.alias} (${priceId}) is not available on the target network. Pushing the price.`
     );
     return UpdateCondition.YES;
@@ -125,13 +127,10 @@ export function shouldUpdate(
     (Number(sourceLatestPrice.conf) / Number(sourceLatestPrice.price)) * 100
   );
 
-  console.log(`Analyzing price ${priceConfig.alias} (${priceId})`);
-
-  console.log("Source latest price: ", sourceLatestPrice);
-  console.log("Target latest price: ", targetLatestPrice);
-
-  console.log(
-    `Time difference: ${timeDifference} (< ${priceConfig.timeDifference}? / early: < ${priceConfig.earlyUpdateTimeDifference}) OR ` +
+  logger.info(
+    { sourcePrice: sourceLatestPrice, targetPrice: targetLatestPrice },
+    `Analyzing price ${priceConfig.alias} (${priceId}). ` +
+      `Time difference: ${timeDifference} (< ${priceConfig.timeDifference}? / early: < ${priceConfig.earlyUpdateTimeDifference}) OR ` +
       `Price deviation: ${priceDeviationPct.toFixed(5)}% (< ${
         priceConfig.priceDeviation
       }%? / early: < ${priceConfig.earlyUpdatePriceDeviation}%?) OR ` +

+ 9 - 2
apps/price_pusher/src/pyth-price-listener.ts

@@ -4,20 +4,27 @@ import {
   PriceServiceConnection,
 } from "@pythnetwork/price-service-client";
 import { PriceInfo, IPriceListener, PriceItem } from "./interface";
+import { Logger } from "pino";
 
 export class PythPriceListener implements IPriceListener {
   private connection: PriceServiceConnection;
   private priceIds: HexString[];
   private priceIdToAlias: Map<HexString, string>;
   private latestPriceInfo: Map<HexString, PriceInfo>;
+  private logger: Logger;
 
-  constructor(connection: PriceServiceConnection, priceItems: PriceItem[]) {
+  constructor(
+    connection: PriceServiceConnection,
+    priceItems: PriceItem[],
+    logger: Logger
+  ) {
     this.connection = connection;
     this.priceIds = priceItems.map((priceItem) => priceItem.id);
     this.priceIdToAlias = new Map(
       priceItems.map((priceItem) => [priceItem.id, priceItem.alias])
     );
     this.latestPriceInfo = new Map();
+    this.logger = logger;
   }
 
   // This method should be awaited on and once it finishes it has the latest value
@@ -42,7 +49,7 @@ export class PythPriceListener implements IPriceListener {
   }
 
   private onNewPriceFeed(priceFeed: PriceFeed) {
-    console.log(
+    this.logger.debug(
       `Received new price feed update from Pyth price service: ${this.priceIdToAlias.get(
         priceFeed.id
       )} ${priceFeed.id}`

+ 24 - 13
apps/price_pusher/src/solana/command.ts

@@ -18,6 +18,8 @@ import {
   SearcherClient,
   searcherClient,
 } from "jito-ts/dist/sdk/block-engine/searcher";
+import pino from "pino";
+import { Logger } from "pino";
 
 export default {
   command: "solana",
@@ -69,6 +71,9 @@ export default {
     ...options.pythContractAddress,
     ...options.pollingFrequency,
     ...options.pushingFrequency,
+    ...options.logLevel,
+    ...options.priceServiceConnectionLogLevel,
+    ...options.controllerLogLevel,
   },
   handler: function (argv: any) {
     const {
@@ -85,21 +90,22 @@ export default {
       jitoKeypairFile,
       jitoTipLamports,
       jitoBundleSize,
+      logLevel,
+      priceServiceConnectionLogLevel,
+      controllerLogLevel,
     } = argv;
 
+    const logger = pino({ level: logLevel });
+
     const priceConfigs = readPriceConfigFile(priceConfigFile);
 
     const priceServiceConnection = new PriceServiceConnection(
       priceServiceEndpoint,
       {
-        logger: {
-          // Log only warnings and errors from the price service client
-          info: () => undefined,
-          warn: console.warn,
-          error: console.error,
-          debug: () => undefined,
-          trace: () => undefined,
-        },
+        logger: logger.child(
+          { module: "PriceServiceConnection" },
+          { level: priceServiceConnectionLogLevel }
+        ),
       }
     );
 
@@ -107,7 +113,8 @@ export default {
 
     const pythListener = new PythPriceListener(
       priceServiceConnection,
-      priceItems
+      priceItems,
+      logger.child({ module: "PythPriceListener" })
     );
 
     const wallet = new NodeWallet(
@@ -132,17 +139,19 @@ export default {
       solanaPricePusher = new SolanaPricePusherJito(
         pythSolanaReceiver,
         priceServiceConnection,
+        logger.child({ module: "SolanaPricePusherJito" }),
         shardId,
         jitoTipLamports,
         jitoClient,
         jitoBundleSize
       );
 
-      onBundleResult(jitoClient);
+      onBundleResult(jitoClient, logger.child({ module: "JitoClient" }));
     } else {
       solanaPricePusher = new SolanaPricePusher(
         pythSolanaReceiver,
         priceServiceConnection,
+        logger.child({ module: "SolanaPricePusher" }),
         shardId,
         computeUnitPriceMicroLamports
       );
@@ -152,6 +161,7 @@ export default {
       pythSolanaReceiver,
       shardId,
       priceItems,
+      logger.child({ module: "SolanaPriceListener" }),
       { pollingFrequency }
     );
 
@@ -160,6 +170,7 @@ export default {
       pythListener,
       solanaPriceListener,
       solanaPricePusher,
+      logger.child({ module: "Controller" }, { level: controllerLogLevel }),
       { pushingFrequency }
     );
 
@@ -167,11 +178,11 @@ export default {
   },
 };
 
-export const onBundleResult = (c: SearcherClient) => {
+export const onBundleResult = (c: SearcherClient, logger: Logger) => {
   c.onBundleResult(
     () => undefined,
-    (e) => {
-      console.log("Error in bundle result: ", e);
+    (err) => {
+      logger.error(err, "Error in bundle result");
     }
   );
 };

+ 16 - 13
apps/price_pusher/src/solana/solana.ts

@@ -13,17 +13,19 @@ import {
 } from "@pythnetwork/solana-utils";
 import { SearcherClient } from "jito-ts/dist/sdk/block-engine/searcher";
 import { sliceAccumulatorUpdateData } from "@pythnetwork/price-service-sdk";
+import { Logger } from "pino";
 
 export class SolanaPriceListener extends ChainPriceListener {
   constructor(
     private pythSolanaReceiver: PythSolanaReceiver,
     private shardId: number,
     priceItems: PriceItem[],
+    private logger: Logger,
     config: {
       pollingFrequency: DurationInSeconds;
     }
   ) {
-    super("solana", config.pollingFrequency, priceItems);
+    super(config.pollingFrequency, priceItems);
   }
 
   async getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined> {
@@ -33,7 +35,7 @@ export class SolanaPriceListener extends ChainPriceListener {
           this.shardId,
           Buffer.from(priceId, "hex")
         );
-      console.log(
+      this.logger.debug(
         `Polled a Solana on chain price for feed ${this.priceIdToAlias.get(
           priceId
         )} (${priceId}).`
@@ -47,9 +49,8 @@ export class SolanaPriceListener extends ChainPriceListener {
       } else {
         return undefined;
       }
-    } catch (e) {
-      console.error(`Polling on-chain price for ${priceId} failed. Error:`);
-      console.error(e);
+    } catch (err) {
+      this.logger.error({ err, priceId }, `Polling on-chain price failed.`);
       return undefined;
     }
   }
@@ -59,6 +60,7 @@ export class SolanaPricePusher implements IPricePusher {
   constructor(
     private pythSolanaReceiver: PythSolanaReceiver,
     private priceServiceConnection: PriceServiceConnection,
+    private logger: Logger,
     private shardId: number,
     private computeUnitPriceMicroLamports: number
   ) {}
@@ -77,8 +79,8 @@ export class SolanaPricePusher implements IPricePusher {
       priceFeedUpdateData = await this.priceServiceConnection.getLatestVaas(
         priceIds
       );
-    } catch (e: any) {
-      console.error(new Date(), "getPriceFeedsUpdateData failed:", e);
+    } catch (err: any) {
+      this.logger.error(err, "getPriceFeedsUpdateData failed:");
       return;
     }
 
@@ -96,14 +98,14 @@ export class SolanaPricePusher implements IPricePusher {
     });
 
     try {
-      await sendTransactions(
+      const signatures = await sendTransactions(
         transactions,
         this.pythSolanaReceiver.connection,
         this.pythSolanaReceiver.wallet
       );
-      console.log(new Date(), "updatePriceFeed successful");
-    } catch (e: any) {
-      console.error(new Date(), "updatePriceFeed failed", e);
+      this.logger.info({ signatures }, "updatePriceFeed successful");
+    } catch (err: any) {
+      this.logger.error(err, "updatePriceFeed failed");
       return;
     }
   }
@@ -115,6 +117,7 @@ export class SolanaPricePusherJito implements IPricePusher {
   constructor(
     private pythSolanaReceiver: PythSolanaReceiver,
     private priceServiceConnection: PriceServiceConnection,
+    private logger: Logger,
     private shardId: number,
     private jitoTipLamports: number,
     private searcherClient: SearcherClient,
@@ -131,8 +134,8 @@ export class SolanaPricePusherJito implements IPricePusher {
       priceFeedUpdateData = await this.priceServiceConnection.getLatestVaas(
         priceIds
       );
-    } catch (e: any) {
-      console.error(new Date(), "getPriceFeedsUpdateData failed:", e);
+    } catch (err: any) {
+      this.logger.error(err, "getPriceFeedsUpdateData failed");
       return;
     }
 

+ 19 - 10
apps/price_pusher/src/sui/command.ts

@@ -7,6 +7,7 @@ import { Controller } from "../controller";
 import { Options } from "yargs";
 import { SuiPriceListener, SuiPricePusher } from "./sui";
 import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
+import pino from "pino";
 
 export default {
   command: "sui",
@@ -68,6 +69,9 @@ export default {
     ...options.mnemonicFile,
     ...options.pollingFrequency,
     ...options.pushingFrequency,
+    ...options.logLevel,
+    ...options.priceServiceConnectionLogLevel,
+    ...options.controllerLogLevel,
   },
   handler: async function (argv: any) {
     const {
@@ -83,20 +87,21 @@ export default {
       ignoreGasObjects,
       gasBudget,
       accountIndex,
+      logLevel,
+      priceServiceConnectionLogLevel,
+      controllerLogLevel,
     } = argv;
 
+    const logger = pino({ level: logLevel });
+
     const priceConfigs = readPriceConfigFile(priceConfigFile);
     const priceServiceConnection = new PriceServiceConnection(
       priceServiceEndpoint,
       {
-        logger: {
-          // Log only warnings and errors from the price service client
-          info: () => undefined,
-          warn: console.warn,
-          error: console.error,
-          debug: () => undefined,
-          trace: () => undefined,
-        },
+        logger: logger.child(
+          { module: "PriceServiceConnection" },
+          { level: priceServiceConnectionLogLevel }
+        ),
         priceFeedRequestConfig: {
           binary: true,
         },
@@ -107,7 +112,7 @@ export default {
       mnemonic,
       `m/44'/784'/${accountIndex}'/0'/0'`
     );
-    console.log(
+    logger.info(
       `Pushing updates from wallet address: ${keypair
         .getPublicKey()
         .toSuiAddress()}`
@@ -117,7 +122,8 @@ export default {
 
     const pythListener = new PythPriceListener(
       priceServiceConnection,
-      priceItems
+      priceItems,
+      logger.child({ module: "PythPriceListener" })
     );
 
     const suiListener = new SuiPriceListener(
@@ -125,10 +131,12 @@ export default {
       wormholeStateId,
       endpoint,
       priceItems,
+      logger.child({ module: "SuiPriceListener" }),
       { pollingFrequency }
     );
     const suiPusher = await SuiPricePusher.createWithAutomaticGasPool(
       priceServiceConnection,
+      logger.child({ module: "SuiPricePusher" }),
       pythStateId,
       wormholeStateId,
       endpoint,
@@ -143,6 +151,7 @@ export default {
       pythListener,
       suiListener,
       suiPusher,
+      logger.child({ module: "Controller" }, { level: controllerLogLevel }),
       { pushingFrequency }
     );
 

+ 46 - 35
apps/price_pusher/src/sui/sui.ts

@@ -10,6 +10,7 @@ import { SuiPythClient } from "@pythnetwork/pyth-sui-js";
 import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
 import { TransactionBlock } from "@mysten/sui.js/transactions";
 import { SuiClient, SuiObjectRef, PaginatedCoins } from "@mysten/sui.js/client";
+import { Logger } from "pino";
 
 const GAS_FEE_FOR_SPLIT = 2_000_000_000;
 // TODO: read this from on chain config
@@ -22,23 +23,26 @@ type SuiAddress = string;
 export class SuiPriceListener extends ChainPriceListener {
   private pythClient: SuiPythClient;
   private provider: SuiClient;
+  private logger: Logger;
 
   constructor(
     pythStateId: ObjectId,
     wormholeStateId: ObjectId,
     endpoint: string,
     priceItems: PriceItem[],
+    logger: Logger,
     config: {
       pollingFrequency: DurationInSeconds;
     }
   ) {
-    super("sui", config.pollingFrequency, priceItems);
+    super(config.pollingFrequency, priceItems);
     this.provider = new SuiClient({ url: endpoint });
     this.pythClient = new SuiPythClient(
       this.provider,
       pythStateId,
       wormholeStateId
     );
+    this.logger = logger;
   }
 
   async getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined> {
@@ -78,9 +82,11 @@ export class SuiPriceListener extends ChainPriceListener {
         conf,
         publishTime: Number(timestamp),
       };
-    } catch (e) {
-      console.error(`Polling Sui on-chain price for ${priceId} failed. Error:`);
-      console.error(e);
+    } catch (err) {
+      this.logger.error(
+        err,
+        `Polling Sui on-chain price for ${priceId} failed.`
+      );
       return undefined;
     }
   }
@@ -104,6 +110,7 @@ export class SuiPricePusher implements IPricePusher {
   constructor(
     private readonly signer: Ed25519Keypair,
     private readonly provider: SuiClient,
+    private logger: Logger,
     private priceServiceConnection: PriceServiceConnection,
     private pythPackageId: string,
     private pythStateId: string,
@@ -157,6 +164,7 @@ export class SuiPricePusher implements IPricePusher {
    */
   static async createWithAutomaticGasPool(
     priceServiceConnection: PriceServiceConnection,
+    logger: Logger,
     pythStateId: string,
     wormholeStateId: string,
     endpoint: string,
@@ -185,7 +193,8 @@ export class SuiPricePusher implements IPricePusher {
       keypair,
       provider,
       numGasObjects,
-      ignoreGasObjects
+      ignoreGasObjects,
+      logger
     );
 
     const pythClient = new SuiPythClient(
@@ -197,6 +206,7 @@ export class SuiPricePusher implements IPricePusher {
     return new SuiPricePusher(
       keypair,
       provider,
+      logger,
       priceServiceConnection,
       pythPackageId,
       pythStateId,
@@ -222,7 +232,7 @@ export class SuiPricePusher implements IPricePusher {
       throw new Error("Invalid arguments");
 
     if (this.gasPool.length === 0) {
-      console.warn("Skipping update: no available gas coin.");
+      this.logger.warn("Skipping update: no available gas coin.");
       return;
     }
 
@@ -266,7 +276,7 @@ export class SuiPricePusher implements IPricePusher {
   private async sendTransactionBlock(tx: TransactionBlock): Promise<void> {
     const gasObject = this.gasPool.shift();
     if (gasObject === undefined) {
-      console.warn("No available gas coin. Skipping push.");
+      this.logger.warn("No available gas coin. Skipping push.");
       return;
     }
 
@@ -286,31 +296,29 @@ export class SuiPricePusher implements IPricePusher {
         ?.map((obj) => obj.reference)
         .find((ref) => ref.objectId === gasObject.objectId);
 
-      console.log(
-        "Successfully updated price with transaction digest ",
-        result.digest
+      this.logger.info(
+        { hash: result.digest },
+        "Successfully updated price with transaction digest"
       );
-    } catch (e: any) {
-      console.log("Error when signAndExecuteTransactionBlock");
+    } catch (err: any) {
       if (
-        String(e).includes("Balance of gas object") ||
-        String(e).includes("GasBalanceTooLow")
+        String(err).includes("Balance of gas object") ||
+        String(err).includes("GasBalanceTooLow")
       ) {
+        this.logger.error(err, "Insufficient gas balance");
         // If the error is caused by insufficient gas, we should panic
-        throw e;
+        throw err;
       } else {
+        this.logger.error(
+          err,
+          "Failed to update price. Trying to refresh gas object references."
+        );
         // Refresh the coin object here in case the error is caused by an object version mismatch.
         nextGasObject = await SuiPricePusher.tryRefreshObjectReference(
           this.provider,
           gasObject
         );
       }
-      console.error(e);
-
-      if ("data" in e) {
-        console.error("Error has .data field:");
-        console.error(JSON.stringify(e.data));
-      }
     }
 
     if (nextGasObject !== undefined) {
@@ -326,20 +334,24 @@ export class SuiPricePusher implements IPricePusher {
     signer: Ed25519Keypair,
     provider: SuiClient,
     numGasObjects: number,
-    ignoreGasObjects: string[]
+    ignoreGasObjects: string[],
+    logger: Logger
   ): Promise<SuiObjectRef[]> {
     const signerAddress = await signer.toSuiAddress();
 
     if (ignoreGasObjects.length > 0) {
-      console.log("Ignoring some gas objects for coin merging:");
-      console.log(ignoreGasObjects);
+      logger.info(
+        { ignoreGasObjects },
+        "Ignoring some gas objects for coin merging"
+      );
     }
 
     const consolidatedCoin = await SuiPricePusher.mergeGasCoinsIntoOne(
       signer,
       provider,
       signerAddress,
-      ignoreGasObjects
+      ignoreGasObjects,
+      logger
     );
     const coinResult = await provider.getObject({
       id: consolidatedCoin.objectId,
@@ -366,7 +378,7 @@ export class SuiPricePusher implements IPricePusher {
       numGasObjects,
       consolidatedCoin
     );
-    console.log("Gas pool is filled with coins: ", gasPool);
+    logger.info({ gasPool }, "Gas pool is filled with coins");
     return gasPool;
   }
 
@@ -470,7 +482,8 @@ export class SuiPricePusher implements IPricePusher {
     signer: Ed25519Keypair,
     provider: SuiClient,
     owner: SuiAddress,
-    initialLockedAddresses: string[]
+    initialLockedAddresses: string[],
+    logger: Logger
   ): Promise<SuiObjectRef> {
     const gasCoins = await SuiPricePusher.getAllGasCoins(provider, owner);
     // skip merging if there is only one coin
@@ -500,17 +513,15 @@ export class SuiPricePusher implements IPricePusher {
           transactionBlock: mergeTx,
           options: { showEffects: true },
         });
-      } catch (e) {
-        console.log("Merge transaction failed with error:");
-        console.log(e);
-        console.log((e as any).data);
-        console.log(JSON.stringify(e));
+      } catch (err) {
+        logger.error(err, "Merge transaction failed with error");
+
         if (
-          String(e).includes(
+          String(err).includes(
             "quorum of validators because of locked objects. Retried a conflicting transaction"
           )
         ) {
-          Object.values((e as any).data).forEach((lockedObjects: any) => {
+          Object.values((err as any).data).forEach((lockedObjects: any) => {
             lockedObjects.forEach((lockedObject: [string, number, string]) => {
               lockedAddresses.add(lockedObject[0]);
             });
@@ -519,7 +530,7 @@ export class SuiPricePusher implements IPricePusher {
           i--;
           continue;
         }
-        throw e;
+        throw err;
       }
       const error = mergeResult?.effects?.status.error;
       if (error) {

+ 235 - 87
pnpm-lock.yaml

@@ -235,9 +235,15 @@ importers:
       '@pythnetwork/solana-utils':
         specifier: workspace:*
         version: link:../../target_chains/solana/sdk/js/solana_utils
+      '@solana/web3.js':
+        specifier: ^1.93.0
+        version: 1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       '@truffle/hdwallet-provider':
         specifier: ^2.1.3
         version: 2.1.5(@babel/core@7.24.7)(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@types/pino':
+        specifier: ^7.0.5
+        version: 7.0.5
       aptos:
         specifier: ^1.8.5
         version: 1.8.5
@@ -250,6 +256,9 @@ importers:
       near-api-js:
         specifier: ^3.0.2
         version: 3.0.4(encoding@0.1.13)
+      pino:
+        specifier: ^9.2.0
+        version: 9.2.0
       web3:
         specifier: ^1.8.1
         version: 1.8.2(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
@@ -287,6 +296,9 @@ importers:
       jest:
         specifier: ^29.7.0
         version: 29.7.0(@types/node@20.14.2)(ts-node@10.9.1(@types/node@20.14.2)(typescript@5.4.5))
+      pino-pretty:
+        specifier: ^11.2.1
+        version: 11.2.1
       prettier:
         specifier: ^2.6.2
         version: 2.8.8
@@ -322,7 +334,7 @@ importers:
         version: 0.49.1
       '@pythnetwork/client':
         specifier: ^2.21.0
-        version: 2.21.0(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+        version: 2.21.0(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       '@pythnetwork/contract-manager':
         specifier: workspace:*
         version: 'link:'
@@ -944,7 +956,7 @@ importers:
         version: 0.0.12(eslint@9.5.0)(typescript@4.9.5)(webpack@5.91.0)
       '@pythnetwork/client':
         specifier: ^2.17.0
-        version: 2.21.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+        version: 2.21.0(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       dotenv:
         specifier: ^16.0.3
         version: 16.4.5
@@ -1575,7 +1587,7 @@ importers:
         version: 0.49.1
       '@pythnetwork/client':
         specifier: ^2.17.0
-        version: 2.21.0(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+        version: 2.21.0(@solana/web3.js@1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       '@pythnetwork/contract-manager':
         specifier: workspace:*
         version: link:../../../contract_manager
@@ -5943,6 +5955,9 @@ packages:
   '@solana/web3.js@1.92.3':
     resolution: {integrity: sha512-NVBWvb9zdJIAx6X+caXaIICCEQfQaQ8ygykCjJW4u2z/sIKcvPj3ZIIllnx0MWMc3IxGq15ozGYDOQIMbwUcHw==}
 
+  '@solana/web3.js@1.93.0':
+    resolution: {integrity: sha512-suf4VYwWxERz4tKoPpXCRHFRNst7jmcFUaD65kII+zg9urpy5PeeqgLV6G5eWGzcVzA9tZeXOju1A1Y+0ojEVw==}
+
   '@solflare-wallet/metamask-sdk@1.0.3':
     resolution: {integrity: sha512-os5Px5PTMYKGS5tzOoyjDxtOtj0jZKnbI1Uwt8+Jsw1HHIA+Ib2UACCGNhQ/un2f8sIbTfLD1WuucNMOy8KZpQ==}
     peerDependencies:
@@ -6181,6 +6196,9 @@ packages:
   '@swc/counter@0.1.3':
     resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
 
+  '@swc/helpers@0.5.11':
+    resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==}
+
   '@swc/helpers@0.5.5':
     resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
 
@@ -6685,6 +6703,10 @@ packages:
   '@types/pbkdf2@3.1.0':
     resolution: {integrity: sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==}
 
+  '@types/pino@7.0.5':
+    resolution: {integrity: sha512-wKoab31pknvILkxAF8ss+v9iNyhw5Iu/0jLtRkUD74cNfOOLJNnqfFKAv0r7wVaTQxRZtWrMpGfShwwBjOcgcg==}
+    deprecated: This is a stub types definition. pino provides its own type definitions, so you do not need this installed.
+
   '@types/prettier@2.7.2':
     resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==}
 
@@ -8651,6 +8673,9 @@ packages:
   colorette@1.4.0:
     resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
 
+  colorette@2.0.20:
+    resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
+
   colors@1.0.3:
     resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==}
     engines: {node: '>=0.1.90'}
@@ -9059,6 +9084,9 @@ packages:
   dateformat@3.0.3:
     resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==}
 
+  dateformat@4.6.3:
+    resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
+
   dayjs@1.11.11:
     resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==}
 
@@ -10301,6 +10329,9 @@ packages:
     resolution: {integrity: sha512-I2FldZwnCbcY6iL+H0rp9m4D+O3PotuFu9FasWjMCzUedYHMP89/37JbSt6/n7Yq/IZmJDW0B2h30sPYdzrfzw==}
     engines: {node: '>=8.0.0'}
 
+  fast-copy@3.0.2:
+    resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==}
+
   fast-deep-equal@3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
 
@@ -10929,6 +10960,9 @@ packages:
   header-case@1.0.1:
     resolution: {integrity: sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==}
 
+  help-me@5.0.0:
+    resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
+
   hermes-estree@0.19.1:
     resolution: {integrity: sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==}
 
@@ -13788,6 +13822,10 @@ packages:
   pino-abstract-transport@1.2.0:
     resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==}
 
+  pino-pretty@11.2.1:
+    resolution: {integrity: sha512-O05NuD9tkRasFRWVaF/uHLOvoRDFD7tb5VMertr78rbsYFjYp48Vg3477EshVAF5eZaEw+OpDl/tu+B0R5o+7g==}
+    hasBin: true
+
   pino-std-serializers@4.0.0:
     resolution: {integrity: sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==}
 
@@ -14724,6 +14762,9 @@ packages:
   rpc-websockets@8.0.1:
     resolution: {integrity: sha512-PptrPRK40uQvifq5sCcObmqInVcZXhy+RRrirzdE5KUPvDI47y1wPvfckD2QzqngOU9xaPW/dT+G+b+wj6M1MQ==}
 
+  rpc-websockets@9.0.2:
+    resolution: {integrity: sha512-YzggvfItxMY3Lwuax5rC18inhbjJv9Py7JXRHxTIi94JOLrqBsSsUUc5bbl5W6c11tXhdfpDPK0KzBhoGe8jjw==}
+
   rtcpeerconnection-shim@1.2.15:
     resolution: {integrity: sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==}
     engines: {node: '>=6.0.0', npm: '>=3.10.0'}
@@ -14827,6 +14868,9 @@ packages:
     resolution: {integrity: sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA==}
     engines: {node: '>=14.0.0'}
 
+  secure-json-parse@2.7.0:
+    resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
+
   selfsigned@2.4.1:
     resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==}
     engines: {node: '>=10'}
@@ -19786,7 +19830,7 @@ snapshots:
 
   '@censo-custody/solana-wallet-adapter@0.1.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bs58: 4.0.1
       eventemitter3: 4.0.7
       uuid: 8.3.2
@@ -19820,11 +19864,11 @@ snapshots:
     dependencies:
       '@certusone/wormhole-sdk-proto-web': 0.0.7(google-protobuf@3.21.2)
       '@certusone/wormhole-sdk-wasm': 0.0.1
-      '@coral-xyz/borsh': 0.2.6(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@coral-xyz/borsh': 0.2.6(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
       '@mysten/sui.js': 0.32.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       '@project-serum/anchor': 0.25.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@solana/spl-token': 0.3.7(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/spl-token': 0.3.7(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       '@terra-money/terra.js': 3.1.9
       '@xpla/xpla.js': 0.2.3
       algosdk: 2.7.0
@@ -19855,11 +19899,11 @@ snapshots:
     dependencies:
       '@certusone/wormhole-sdk-proto-web': 0.0.6(google-protobuf@3.21.2)
       '@certusone/wormhole-sdk-wasm': 0.0.1
-      '@coral-xyz/borsh': 0.2.6(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@coral-xyz/borsh': 0.2.6(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))
       '@mysten/sui.js': 0.32.2(bufferutil@4.0.7)(utf-8-validate@5.0.10)
       '@project-serum/anchor': 0.25.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@solana/spl-token': 0.3.7(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/spl-token': 0.3.7(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       '@terra-money/terra.js': 3.1.9
       '@xpla/xpla.js': 0.2.3
       algosdk: 2.7.0
@@ -19890,11 +19934,11 @@ snapshots:
     dependencies:
       '@certusone/wormhole-sdk-proto-web': 0.0.6(google-protobuf@3.21.2)
       '@certusone/wormhole-sdk-wasm': 0.0.1
-      '@coral-xyz/borsh': 0.2.6(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@coral-xyz/borsh': 0.2.6(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
       '@mysten/sui.js': 0.32.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       '@project-serum/anchor': 0.25.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@solana/spl-token': 0.3.7(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/spl-token': 0.3.7(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       '@terra-money/terra.js': 3.1.9
       '@xpla/xpla.js': 0.2.3
       algosdk: 2.7.0
@@ -19939,8 +19983,8 @@ snapshots:
 
   '@coral-xyz/anchor@0.27.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@coral-xyz/borsh': 0.27.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@coral-xyz/borsh': 0.27.0(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       base64-js: 1.5.1
       bn.js: 5.2.1
       bs58: 4.0.1
@@ -19961,9 +20005,9 @@ snapshots:
 
   '@coral-xyz/anchor@0.29.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@coral-xyz/borsh': 0.29.0(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@coral-xyz/borsh': 0.29.0(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))
       '@noble/hashes': 1.4.0
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       bs58: 4.0.1
       buffer-layout: 1.2.2
@@ -19982,9 +20026,9 @@ snapshots:
 
   '@coral-xyz/anchor@0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@coral-xyz/borsh': 0.29.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@coral-xyz/borsh': 0.29.0(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
       '@noble/hashes': 1.4.0
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       bs58: 4.0.1
       buffer-layout: 1.2.2
@@ -20003,9 +20047,9 @@ snapshots:
 
   '@coral-xyz/anchor@0.30.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@coral-xyz/borsh': 0.30.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@coral-xyz/borsh': 0.30.0(@solana/web3.js@1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
       '@noble/hashes': 1.4.0
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       bs58: 4.0.1
       buffer-layout: 1.2.2
@@ -20022,33 +20066,33 @@ snapshots:
       - encoding
       - utf-8-validate
 
-  '@coral-xyz/borsh@0.2.6(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+  '@coral-xyz/borsh@0.2.6(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
-  '@coral-xyz/borsh@0.2.6(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+  '@coral-xyz/borsh@0.2.6(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
-  '@coral-xyz/borsh@0.27.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+  '@coral-xyz/borsh@0.27.0(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
-  '@coral-xyz/borsh@0.28.0(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+  '@coral-xyz/borsh@0.28.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
-  '@coral-xyz/borsh@0.28.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+  '@coral-xyz/borsh@0.28.0(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
@@ -20058,21 +20102,27 @@ snapshots:
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
-  '@coral-xyz/borsh@0.29.0(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+  '@coral-xyz/borsh@0.28.0(@solana/web3.js@1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
-  '@coral-xyz/borsh@0.29.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+  '@coral-xyz/borsh@0.29.0(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
-  '@coral-xyz/borsh@0.30.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+  '@coral-xyz/borsh@0.29.0(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      bn.js: 5.2.1
+      buffer-layout: 1.2.2
+
+  '@coral-xyz/borsh@0.30.0(@solana/web3.js@1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+    dependencies:
+      '@solana/web3.js': 1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
@@ -20578,7 +20628,7 @@ snapshots:
 
   '@ensdomains/ensjs@2.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
     dependencies:
-      '@babel/runtime': 7.23.9
+      '@babel/runtime': 7.24.7
       '@ensdomains/address-encoder': 0.1.9
       '@ensdomains/ens': 0.4.5
       '@ensdomains/resolver': 0.2.4
@@ -22609,7 +22659,7 @@ snapshots:
       '@keystonehq/bc-ur-registry': 0.5.5
       '@keystonehq/bc-ur-registry-sol': 0.3.1
       '@keystonehq/sdk': 0.13.1
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bs58: 5.0.0
       uuid: 8.3.2
     transitivePeerDependencies:
@@ -24179,8 +24229,8 @@ snapshots:
 
   '@project-serum/anchor@0.25.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@project-serum/borsh': 0.2.5(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@project-serum/borsh': 0.2.5(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       base64-js: 1.5.1
       bn.js: 5.2.1
       bs58: 4.0.1
@@ -24201,8 +24251,8 @@ snapshots:
 
   '@project-serum/anchor@0.25.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@project-serum/borsh': 0.2.5(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@project-serum/borsh': 0.2.5(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       base64-js: 1.5.1
       bn.js: 5.2.1
       bs58: 4.0.1
@@ -24221,15 +24271,15 @@ snapshots:
       - encoding
       - utf-8-validate
 
-  '@project-serum/borsh@0.2.5(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+  '@project-serum/borsh@0.2.5(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
-  '@project-serum/borsh@0.2.5(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
+  '@project-serum/borsh@0.2.5(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
       buffer-layout: 1.2.2
 
@@ -24262,22 +24312,22 @@ snapshots:
 
   '@protobufjs/utf8@1.1.0': {}
 
-  '@pythnetwork/client@2.21.0(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)':
+  '@pythnetwork/client@2.21.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@coral-xyz/borsh': 0.28.0(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@coral-xyz/borsh': 0.28.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       buffer: 6.0.3
     transitivePeerDependencies:
       - bufferutil
       - encoding
       - utf-8-validate
 
-  '@pythnetwork/client@2.21.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
+  '@pythnetwork/client@2.21.0(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@coral-xyz/borsh': 0.28.0(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@coral-xyz/borsh': 0.28.0(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       buffer: 6.0.3
     transitivePeerDependencies:
       - bufferutil
@@ -24295,6 +24345,17 @@ snapshots:
       - encoding
       - utf-8-validate
 
+  '@pythnetwork/client@2.21.0(@solana/web3.js@1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
+    dependencies:
+      '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@coral-xyz/borsh': 0.28.0(@solana/web3.js@1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))
+      '@solana/web3.js': 1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      buffer: 6.0.3
+    transitivePeerDependencies:
+      - bufferutil
+      - encoding
+      - utf-8-validate
+
   '@pythnetwork/price-service-client@1.9.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)':
     dependencies:
       '@pythnetwork/price-service-sdk': 1.7.1
@@ -24321,7 +24382,7 @@ snapshots:
 
   '@radix-ui/react-arrow@1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
-      '@babel/runtime': 7.23.9
+      '@babel/runtime': 7.24.7
       '@radix-ui/react-primitive': 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
@@ -24428,7 +24489,7 @@ snapshots:
 
   '@radix-ui/react-use-callback-ref@1.0.0(react@18.3.1)':
     dependencies:
-      '@babel/runtime': 7.23.9
+      '@babel/runtime': 7.24.7
       react: 18.3.1
 
   '@radix-ui/react-use-controllable-state@1.0.0(react@18.3.1)':
@@ -24439,24 +24500,24 @@ snapshots:
 
   '@radix-ui/react-use-escape-keydown@1.0.2(react@18.3.1)':
     dependencies:
-      '@babel/runtime': 7.23.9
+      '@babel/runtime': 7.24.7
       '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1)
       react: 18.3.1
 
   '@radix-ui/react-use-layout-effect@1.0.0(react@18.3.1)':
     dependencies:
-      '@babel/runtime': 7.23.9
+      '@babel/runtime': 7.24.7
       react: 18.3.1
 
   '@radix-ui/react-use-rect@1.0.0(react@18.3.1)':
     dependencies:
-      '@babel/runtime': 7.23.9
+      '@babel/runtime': 7.24.7
       '@radix-ui/rect': 1.0.0
       react: 18.3.1
 
   '@radix-ui/react-use-size@1.0.0(react@18.3.1)':
     dependencies:
-      '@babel/runtime': 7.23.9
+      '@babel/runtime': 7.24.7
       '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1)
       react: 18.3.1
 
@@ -24469,7 +24530,7 @@ snapshots:
 
   '@radix-ui/rect@1.0.0':
     dependencies:
-      '@babel/runtime': 7.23.9
+      '@babel/runtime': 7.24.7
 
   '@react-aria/focus@3.17.1(react@18.3.1)':
     dependencies:
@@ -25017,7 +25078,7 @@ snapshots:
   '@solana/buffer-layout-utils@0.2.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
       '@solana/buffer-layout': 4.0.1
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bigint-buffer: 1.1.5
       bignumber.js: 9.1.2
     transitivePeerDependencies:
@@ -25028,7 +25089,7 @@ snapshots:
   '@solana/buffer-layout-utils@0.2.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
       '@solana/buffer-layout': 4.0.1
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bigint-buffer: 1.1.5
       bignumber.js: 9.1.2
     transitivePeerDependencies:
@@ -25040,22 +25101,33 @@ snapshots:
     dependencies:
       buffer: 6.0.3
 
-  '@solana/spl-token@0.3.7(@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)':
+  '@solana/spl-token@0.3.7(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
+    dependencies:
+      '@solana/buffer-layout': 4.0.1
+      '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      buffer: 6.0.3
+    transitivePeerDependencies:
+      - bufferutil
+      - encoding
+      - utf-8-validate
+
+  '@solana/spl-token@0.3.7(@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
       '@solana/buffer-layout': 4.0.1
       '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       buffer: 6.0.3
     transitivePeerDependencies:
       - bufferutil
       - encoding
       - utf-8-validate
 
-  '@solana/spl-token@0.3.7(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
+  '@solana/spl-token@0.3.7(@solana/web3.js@1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
       '@solana/buffer-layout': 4.0.1
       '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       buffer: 6.0.3
     transitivePeerDependencies:
       - bufferutil
@@ -25561,7 +25633,7 @@ snapshots:
       - encoding
       - utf-8-validate
 
-  '@solana/web3.js@1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)':
+  '@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
       '@babel/runtime': 7.23.9
       '@noble/curves': 1.4.0
@@ -25574,7 +25646,7 @@ snapshots:
       bs58: 4.0.1
       buffer: 6.0.3
       fast-stable-stringify: 1.0.0
-      jayson: 4.1.0(bufferutil@4.0.7)(utf-8-validate@5.0.10)
+      jayson: 4.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       node-fetch: 2.7.0(encoding@0.1.13)
       rpc-websockets: 7.5.1
       superstruct: 0.14.2
@@ -25583,9 +25655,9 @@ snapshots:
       - encoding
       - utf-8-validate
 
-  '@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
+  '@solana/web3.js@1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@babel/runtime': 7.23.9
+      '@babel/runtime': 7.24.7
       '@noble/curves': 1.4.0
       '@noble/hashes': 1.4.0
       '@solana/buffer-layout': 4.0.1
@@ -25596,10 +25668,10 @@ snapshots:
       bs58: 4.0.1
       buffer: 6.0.3
       fast-stable-stringify: 1.0.0
-      jayson: 4.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      jayson: 4.1.0(bufferutil@4.0.7)(utf-8-validate@5.0.10)
       node-fetch: 2.7.0(encoding@0.1.13)
-      rpc-websockets: 7.5.1
-      superstruct: 0.14.2
+      rpc-websockets: 8.0.1
+      superstruct: 1.0.4
     transitivePeerDependencies:
       - bufferutil
       - encoding
@@ -25627,6 +25699,28 @@ snapshots:
       - encoding
       - utf-8-validate
 
+  '@solana/web3.js@1.93.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
+    dependencies:
+      '@babel/runtime': 7.24.7
+      '@noble/curves': 1.4.0
+      '@noble/hashes': 1.4.0
+      '@solana/buffer-layout': 4.0.1
+      agentkeepalive: 4.5.0
+      bigint-buffer: 1.1.5
+      bn.js: 5.2.1
+      borsh: 0.7.0
+      bs58: 4.0.1
+      buffer: 6.0.3
+      fast-stable-stringify: 1.0.0
+      jayson: 4.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+      node-fetch: 2.7.0(encoding@0.1.13)
+      rpc-websockets: 9.0.2
+      superstruct: 1.0.4
+    transitivePeerDependencies:
+      - bufferutil
+      - encoding
+      - utf-8-validate
+
   '@solflare-wallet/metamask-sdk@1.0.3(@solana/web3.js@1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10))':
     dependencies:
       '@solana/wallet-standard-features': 1.2.0
@@ -25650,7 +25744,7 @@ snapshots:
   '@sqds/mesh@1.0.6(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
       '@project-serum/anchor': 0.25.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.7)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
     transitivePeerDependencies:
       - bufferutil
@@ -25660,7 +25754,7 @@ snapshots:
   '@sqds/mesh@1.0.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
       '@project-serum/anchor': 0.25.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bn.js: 5.2.1
     transitivePeerDependencies:
       - bufferutil
@@ -25753,7 +25847,7 @@ snapshots:
 
   '@strike-protocols/solana-wallet-adapter@0.1.8(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       bs58: 4.0.1
       eventemitter3: 4.0.7
       uuid: 8.3.2
@@ -25948,6 +26042,10 @@ snapshots:
 
   '@swc/counter@0.1.3': {}
 
+  '@swc/helpers@0.5.11':
+    dependencies:
+      tslib: 2.6.3
+
   '@swc/helpers@0.5.5':
     dependencies:
       '@swc/counter': 0.1.3
@@ -26150,7 +26248,7 @@ snapshots:
   '@toruslabs/solana-embed@0.3.4(@babel/runtime@7.24.7)(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)':
     dependencies:
       '@babel/runtime': 7.24.7
-      '@solana/web3.js': 1.90.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
+      '@solana/web3.js': 1.92.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)
       '@toruslabs/base-controllers': 2.9.0(@babel/runtime@7.24.7)(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       '@toruslabs/http-helpers': 3.4.0(@babel/runtime@7.24.7)
       '@toruslabs/openlogin-jrpc': 3.2.0(@babel/runtime@7.24.7)
@@ -26768,6 +26866,10 @@ snapshots:
     dependencies:
       '@types/node': 20.14.2
 
+  '@types/pino@7.0.5':
+    dependencies:
+      pino: 9.2.0
+
   '@types/prettier@2.7.2': {}
 
   '@types/prop-types@15.7.12': {}
@@ -26906,7 +27008,7 @@ snapshots:
       '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5)
       '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5)
       '@typescript-eslint/visitor-keys': 6.21.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4
       eslint: 8.57.0
       graphemer: 1.4.0
       ignore: 5.3.1
@@ -27006,7 +27108,7 @@ snapshots:
       '@typescript-eslint/types': 6.21.0
       '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.4.5)
       '@typescript-eslint/visitor-keys': 6.21.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4
       eslint: 8.57.0
     optionalDependencies:
       typescript: 5.4.5
@@ -27110,7 +27212,7 @@ snapshots:
     dependencies:
       '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.4.5)
       '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5)
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4
       eslint: 8.57.0
       ts-api-utils: 1.3.0(typescript@5.4.5)
     optionalDependencies:
@@ -27186,7 +27288,7 @@ snapshots:
     dependencies:
       '@typescript-eslint/types': 6.21.0
       '@typescript-eslint/visitor-keys': 6.21.0
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4
       globby: 11.1.0
       is-glob: 4.0.3
       minimatch: 9.0.3
@@ -28587,7 +28689,7 @@ snapshots:
 
   axios@0.21.4:
     dependencies:
-      follow-redirects: 1.15.6(debug@4.3.4)
+      follow-redirects: 1.15.6
     transitivePeerDependencies:
       - debug
 
@@ -28605,7 +28707,7 @@ snapshots:
 
   axios@0.27.2:
     dependencies:
-      follow-redirects: 1.15.6(debug@4.3.4)
+      follow-redirects: 1.15.6
       form-data: 4.0.0
     transitivePeerDependencies:
       - debug
@@ -29586,6 +29688,8 @@ snapshots:
 
   colorette@1.4.0: {}
 
+  colorette@2.0.20: {}
+
   colors@1.0.3: {}
 
   colors@1.4.0: {}
@@ -30206,6 +30310,8 @@ snapshots:
 
   dateformat@3.0.3: {}
 
+  dateformat@4.6.3: {}
+
   dayjs@1.11.11: {}
 
   debounce-fn@4.0.0:
@@ -30242,6 +30348,10 @@ snapshots:
     optionalDependencies:
       supports-color: 8.1.1
 
+  debug@4.3.4:
+    dependencies:
+      ms: 2.1.2
+
   debug@4.3.4(supports-color@8.1.1):
     dependencies:
       ms: 2.1.2
@@ -31384,7 +31494,7 @@ snapshots:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.4(supports-color@8.1.1)
+      debug: 4.3.4
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
       eslint-scope: 7.2.2
@@ -31516,7 +31626,7 @@ snapshots:
   eth-block-tracker@4.4.3(@babel/core@7.24.7):
     dependencies:
       '@babel/plugin-transform-runtime': 7.19.6(@babel/core@7.24.7)
-      '@babel/runtime': 7.23.9
+      '@babel/runtime': 7.24.7
       eth-query: 2.1.2
       json-rpc-random-id: 1.0.1
       pify: 3.0.0
@@ -32022,6 +32132,8 @@ snapshots:
     dependencies:
       pure-rand: 6.0.4
 
+  fast-copy@3.0.2: {}
+
   fast-deep-equal@3.1.3: {}
 
   fast-glob@3.2.7:
@@ -32178,6 +32290,8 @@ snapshots:
 
   flow-parser@0.237.2: {}
 
+  follow-redirects@1.15.6: {}
+
   follow-redirects@1.15.6(debug@4.3.4):
     optionalDependencies:
       debug: 4.3.4(supports-color@8.1.1)
@@ -32878,6 +32992,8 @@ snapshots:
       no-case: 2.3.2
       upper-case: 1.1.3
 
+  help-me@5.0.0: {}
+
   hermes-estree@0.19.1: {}
 
   hermes-estree@0.20.1: {}
@@ -37417,6 +37533,23 @@ snapshots:
       readable-stream: 4.5.2
       split2: 4.2.0
 
+  pino-pretty@11.2.1:
+    dependencies:
+      colorette: 2.0.20
+      dateformat: 4.6.3
+      fast-copy: 3.0.2
+      fast-safe-stringify: 2.1.1
+      help-me: 5.0.0
+      joycon: 3.1.1
+      minimist: 1.2.7
+      on-exit-leak-free: 2.1.2
+      pino-abstract-transport: 1.2.0
+      pump: 3.0.0
+      readable-stream: 4.5.2
+      secure-json-parse: 2.7.0
+      sonic-boom: 4.0.1
+      strip-json-comments: 3.1.1
+
   pino-std-serializers@4.0.0: {}
 
   pino-std-serializers@7.0.0: {}
@@ -38647,6 +38780,19 @@ snapshots:
       bufferutil: 4.0.8
       utf-8-validate: 5.0.10
 
+  rpc-websockets@9.0.2:
+    dependencies:
+      '@swc/helpers': 0.5.11
+      '@types/uuid': 8.3.4
+      '@types/ws': 8.5.4
+      buffer: 6.0.3
+      eventemitter3: 5.0.1
+      uuid: 8.3.2
+      ws: 8.17.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+    optionalDependencies:
+      bufferutil: 4.0.8
+      utf-8-validate: 5.0.10
+
   rtcpeerconnection-shim@1.2.15:
     dependencies:
       sdp: 2.12.0
@@ -38767,6 +38913,8 @@ snapshots:
       node-addon-api: 5.1.0
       node-gyp-build: 4.6.0
 
+  secure-json-parse@2.7.0: {}
+
   selfsigned@2.4.1:
     dependencies:
       '@types/node-forge': 1.3.11