Kaynağa Gözat

feat(price_pusher): solana price pusher (#1408)

* Checkpoint

* Checkpoint

* Checkpoint

* Checkpoint

* fix: pusher

* Checkpoint

* Works

* fix: pass pusher program id

* Add docs

* 0.1.0

* Bump npm package

* Go

* Comment

* Add customizable shard id

* Allow configurable priority fees
guibescos 1 yıl önce
ebeveyn
işleme
050b8275f7

+ 3 - 1
package-lock.json

@@ -56546,6 +56546,7 @@
         "@mysten/sui.js": "^0.49.1",
         "@pythnetwork/price-service-client": "*",
         "@pythnetwork/pyth-sdk-solidity": "*",
+        "@pythnetwork/pyth-solana-receiver": "*",
         "@pythnetwork/pyth-sui-js": "*",
         "@truffle/hdwallet-provider": "^2.1.3",
         "aptos": "^1.8.5",
@@ -59498,7 +59499,7 @@
     },
     "target_chains/solana/sdk/js/pyth_solana_receiver": {
       "name": "@pythnetwork/pyth-solana-receiver",
-      "version": "0.3.0",
+      "version": "0.4.0",
       "license": "Apache-2.0",
       "dependencies": {
         "@coral-xyz/anchor": "^0.29.0",
@@ -69103,6 +69104,7 @@
         "@mysten/sui.js": "^0.49.1",
         "@pythnetwork/price-service-client": "*",
         "@pythnetwork/pyth-sdk-solidity": "*",
+        "@pythnetwork/pyth-solana-receiver": "*",
         "@pythnetwork/pyth-sui-js": "*",
         "@truffle/hdwallet-provider": "^2.1.3",
         "@types/ethereum-protocol": "^1.0.2",

+ 4 - 0
price_pusher/config.solana.testnet.sample.json

@@ -0,0 +1,4 @@
+{
+  "endpoint": "https://api.devnet.solana.com",
+  "keypair-file": "/keypair"
+}

+ 1 - 0
price_pusher/package.json

@@ -52,6 +52,7 @@
   },
   "dependencies": {
     "@injectivelabs/sdk-ts": "1.10.72",
+    "@pythnetwork/pyth-solana-receiver": "*",
     "@mysten/sui.js": "^0.49.1",
     "@pythnetwork/price-service-client": "*",
     "@pythnetwork/pyth-sdk-solidity": "*",

+ 2 - 0
price_pusher/src/index.ts

@@ -6,6 +6,7 @@ import evm from "./evm/command";
 import aptos from "./aptos/command";
 import sui from "./sui/command";
 import near from "./near/command";
+import solana from "./solana/command";
 
 yargs(hideBin(process.argv))
   .config("config")
@@ -15,4 +16,5 @@ yargs(hideBin(process.argv))
   .command(aptos)
   .command(sui)
   .command(near)
+  .command(solana)
   .help().argv;

+ 115 - 0
price_pusher/src/solana/command.ts

@@ -0,0 +1,115 @@
+import { Options } from "yargs";
+import * as options from "../options";
+import { readPriceConfigFile } from "../price-config";
+import { PriceServiceConnection } from "@pythnetwork/price-service-client";
+import { PythPriceListener } from "../pyth-price-listener";
+import { SolanaPriceListener, SolanaPricePusher } from "./solana";
+import { Controller } from "../controller";
+import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver";
+import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
+import { Keypair, Connection } from "@solana/web3.js";
+import fs from "fs";
+import { PublicKey } from "@solana/web3.js";
+
+export default {
+  command: "solana",
+  describe: "run price pusher for solana",
+  builder: {
+    endpoint: {
+      description: "Solana RPC API endpoint",
+      type: "string",
+      required: true,
+    } as Options,
+    "keypair-file": {
+      description: "Path to a keypair file",
+      type: "string",
+      required: true,
+    } as Options,
+    "shard-id": {
+      description: "Shard ID",
+      type: "number",
+      required: true,
+    } as Options,
+    "compute-unit-price-micro-lamports": {
+      description: "Priority fee per compute unit",
+      type: "number",
+      default: 50000,
+    } as Options,
+    ...options.priceConfigFile,
+    ...options.priceServiceEndpoint,
+    ...options.pythContractAddress,
+    ...options.pollingFrequency,
+    ...options.pushingFrequency,
+  },
+  handler: function (argv: any) {
+    const {
+      endpoint,
+      keypairFile,
+      shardId,
+      computeUnitPriceMicroLamports,
+      priceConfigFile,
+      priceServiceEndpoint,
+      pythContractAddress,
+      pushingFrequency,
+      pollingFrequency,
+    } = argv;
+
+    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,
+        },
+      }
+    );
+
+    const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
+
+    const pythListener = new PythPriceListener(
+      priceServiceConnection,
+      priceItems
+    );
+
+    const wallet = new NodeWallet(
+      Keypair.fromSecretKey(
+        Uint8Array.from(JSON.parse(fs.readFileSync(keypairFile, "ascii")))
+      )
+    );
+
+    const pythSolanaReceiver = new PythSolanaReceiver({
+      connection: new Connection(endpoint),
+      wallet,
+      pushOracleProgramId: new PublicKey(pythContractAddress),
+    });
+
+    const solanaPricePusher = new SolanaPricePusher(
+      pythSolanaReceiver,
+      priceServiceConnection,
+      shardId,
+      computeUnitPriceMicroLamports
+    );
+    const solanaPriceListener = new SolanaPriceListener(
+      pythSolanaReceiver,
+      shardId,
+      priceItems,
+      { pollingFrequency }
+    );
+
+    const controller = new Controller(
+      priceConfigs,
+      pythListener,
+      solanaPriceListener,
+      solanaPricePusher,
+      { pushingFrequency }
+    );
+
+    controller.start();
+  },
+};

+ 95 - 0
price_pusher/src/solana/solana.ts

@@ -0,0 +1,95 @@
+import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver";
+import {
+  ChainPriceListener,
+  IPricePusher,
+  PriceInfo,
+  PriceItem,
+} from "../interface";
+import { DurationInSeconds } from "../utils";
+import { PriceServiceConnection } from "@pythnetwork/price-service-client";
+
+export class SolanaPriceListener extends ChainPriceListener {
+  constructor(
+    private pythSolanaReceiver: PythSolanaReceiver,
+    private shardId: number,
+    priceItems: PriceItem[],
+    config: {
+      pollingFrequency: DurationInSeconds;
+    }
+  ) {
+    super("solana", config.pollingFrequency, priceItems);
+  }
+
+  async getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined> {
+    try {
+      const priceFeedAccount =
+        await this.pythSolanaReceiver.fetchPriceFeedAccount(
+          this.shardId,
+          Buffer.from(priceId, "hex")
+        );
+      console.log(
+        `Polled a Solana on chain price for feed ${this.priceIdToAlias.get(
+          priceId
+        )} (${priceId}).`
+      );
+      if (priceFeedAccount) {
+        return {
+          conf: priceFeedAccount.priceMessage.conf.toString(),
+          price: priceFeedAccount.priceMessage.price.toString(),
+          publishTime: priceFeedAccount.priceMessage.publishTime.toNumber(),
+        };
+      } else {
+        return undefined;
+      }
+    } catch (e) {
+      console.error(`Polling on-chain price for ${priceId} failed. Error:`);
+      console.error(e);
+      return undefined;
+    }
+  }
+}
+
+export class SolanaPricePusher implements IPricePusher {
+  constructor(
+    private pythSolanaReceiver: PythSolanaReceiver,
+    private priceServiceConnection: PriceServiceConnection,
+    private shardId: number,
+    private computeUnitPriceMicroLamports: number
+  ) {}
+
+  async updatePriceFeed(
+    priceIds: string[],
+    pubTimesToPush: number[]
+  ): Promise<void> {
+    if (priceIds.length === 0) {
+      return;
+    }
+
+    let priceFeedUpdateData;
+    try {
+      priceFeedUpdateData = await this.priceServiceConnection.getLatestVaas(
+        priceIds
+      );
+    } catch (e: any) {
+      console.error(new Date(), "getPriceFeedsUpdateData failed:", e);
+      return;
+    }
+
+    const transactionBuilder = this.pythSolanaReceiver.newTransactionBuilder({
+      closeUpdateAccounts: false,
+    });
+    transactionBuilder.addUpdatePriceFeed(priceFeedUpdateData, this.shardId);
+
+    try {
+      await this.pythSolanaReceiver.provider.sendAll(
+        await transactionBuilder.buildVersionedTransactions({
+          computeUnitPriceMicroLamports: this.computeUnitPriceMicroLamports,
+        })
+      );
+      console.log(new Date(), "updatePriceFeed successful");
+    } catch (e: any) {
+      console.error(new Date(), "updatePriceFeed failed", e);
+      return;
+    }
+  }
+}

+ 4 - 0
target_chains/solana/programs/pyth-push-oracle/src/lib.rs

@@ -89,10 +89,14 @@ pub struct UpdatePriceFeed<'info> {
     #[account(mut)]
     pub payer:                Signer<'info>,
     pub pyth_solana_receiver: Program<'info, PythSolanaReceiver>,
+    /// CHECK: Checked by CPI into the Pyth Solana Receiver
     pub encoded_vaa:          AccountInfo<'info>,
+    /// CHECK: Checked by CPI into the Pyth Solana Receiver
     pub config:               AccountInfo<'info>,
+    /// CHECK: Checked by CPI into the Pyth Solana Receiver
     #[account(mut)]
     pub treasury:             AccountInfo<'info>,
+    /// CHECK: This account's seeds are checked
     #[account(mut, seeds = [&shard_id.to_le_bytes(), &feed_id], bump)]
     pub price_feed_account:   AccountInfo<'info>,
     pub system_program:       Program<'info, System>,

+ 1 - 1
target_chains/solana/pyth_solana_receiver_sdk/src/lib.rs

@@ -12,4 +12,4 @@ pub mod price_update;
 
 declare_id!("rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ");
 
-pub const PYTH_PUSH_ORACLE_ID: Pubkey = pubkey!("F9SP6tBXw9Af7BYauo7Y2R5Es2mpv8FP5aNCXMihp6Za");
+pub const PYTH_PUSH_ORACLE_ID: Pubkey = pubkey!("pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT");

+ 1 - 1
target_chains/solana/sdk/js/pyth_solana_receiver/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@pythnetwork/pyth-solana-receiver",
-  "version": "0.3.0",
+  "version": "0.4.0",
   "description": "Pyth solana receiver SDK",
   "homepage": "https://pyth.network",
   "main": "lib/index.js",

+ 208 - 4
target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts

@@ -1,4 +1,4 @@
-import { AnchorProvider, Program } from "@coral-xyz/anchor";
+import { AnchorProvider, IdlAccounts, Program } from "@coral-xyz/anchor";
 import {
   Connection,
   Signer,
@@ -14,6 +14,7 @@ import {
   IDL as WormholeCoreBridgeSolanaIdl,
 } from "./idl/wormhole_core_bridge_solana";
 import {
+  DEFAULT_PUSH_ORACLE_PROGRAM_ID,
   DEFAULT_RECEIVER_PROGRAM_ID,
   DEFAULT_TREASURY_ID,
   DEFAULT_WORMHOLE_PROGRAM_ID,
@@ -43,7 +44,13 @@ import {
   InstructionWithEphemeralSigners,
   PriorityFeeConfig,
 } from "@pythnetwork/solana-utils";
+import {
+  PythPushOracle,
+  IDL as PythPushOracleIdl,
+} from "./idl/pyth_push_oracle";
 
+export type PriceUpdateAccount =
+  IdlAccounts<PythSolanaReceiverProgram>["priceUpdateV2"];
 /**
  * Configuration for the PythTransactionBuilder
  * @property closeUpdateAccounts (default: true) if true, the builder will add instructions to close the price update accounts and the encoded vaa accounts to recover the rent
@@ -93,6 +100,18 @@ export class PythTransactionBuilder extends TransactionBuilder {
    * Add instructions to post price updates to the builder.
    *
    * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates.
+   *
+   * @example
+   * ```typescript
+   * const priceUpdateData = await priceServiceConnection.getLatestVaas([
+   *    SOL_PRICE_FEED_ID,
+   *    ETH_PRICE_FEED_ID,
+   * ]);
+   *
+   * const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({});
+   * await transactionBuilder.addPostPriceUpdates(priceUpdateData);
+   * await transactionBuilder.addPriceConsumerInstructions(...)
+   * ```
    */
   async addPostPriceUpdates(priceUpdateDataArray: string[]) {
     const {
@@ -147,11 +166,50 @@ export class PythTransactionBuilder extends TransactionBuilder {
     this.addInstructions(postInstructions);
   }
 
+  /**
+   * Add instructions to update price feed accounts to the builder.
+   *
+   * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates.
+   * @param shardId the shard ID of the set of price feed accounts. This shard ID allows for multiple sets of price feed accounts to be managed by the same program.
+   *
+   * Price feed accounts are a special type of price update accounts.
+   * Instead of using ephemeral addresses, they are PDAs of the Pyth Push Oracle program derived from the feed ID. They can only be updated with a more recent price update.
+   *
+   * @example
+   * ```typescript
+   * const priceUpdateData = await priceServiceConnection.getLatestVaas([
+   *    SOL_PRICE_FEED_ID,
+   *    ETH_PRICE_FEED_ID,
+   * ]);
+   *
+   * const transactionBuilder = pythSolanaReceiver.newTransactionBuilder({});
+   * await transactionBuilder.addUpdatePriceFeed(priceUpdateData);
+   * await transactionBuilder.addPriceConsumerInstructions(...)
+   * ...
+   * ```
+   */
+  async addUpdatePriceFeed(priceUpdateDataArray: string[], shardId: number) {
+    const {
+      postInstructions,
+      priceFeedIdToPriceUpdateAccount,
+      closeInstructions,
+    } = await this.pythSolanaReceiver.buildUpdatePriceFeedInstructions(
+      priceUpdateDataArray,
+      shardId
+    );
+    this.closeInstructions.push(...closeInstructions);
+    Object.assign(
+      this.priceFeedIdToPriceUpdateAccount,
+      priceFeedIdToPriceUpdateAccount
+    );
+    this.addInstructions(postInstructions);
+  }
+
   /**
    * Add instructions that consume price updates to the builder.
    *
    * @param getInstructions a function that given a mapping of price feed IDs to price update accounts, generates a series of instructions. Price updates get posted to ephemeral accounts and this function allows the user to indicate which accounts in their instruction need to be "replaced" with each price update account.
-   * If multiple price updates for the same price feed id are posted with the same builder, the account corresponding to the last update to get posted will be used.
+   * If multiple price updates for the same price feed ID are posted with the same builder, the account corresponding to the last update to get posted will be used.
    *
    * @example
    * ```typescript
@@ -212,8 +270,8 @@ export class PythTransactionBuilder extends TransactionBuilder {
   }
 
   /**
-   * This method is used to retrieve the address of the price update account where the price update for a given price feed id will be posted.
-   * If multiple price updates for the same price feed id will be posted with the same builder, the address of the account corresponding to the last update to get posted will be returned.
+   * This method is used to retrieve the address of the price update account where the price update for a given price feed ID will be posted.
+   * If multiple price updates for the same price feed ID will be posted with the same builder, the address of the account corresponding to the last update to get posted will be returned.
    * */
   getPriceUpdateAccount(priceFeedId: string): PublicKey {
     const priceUpdateAccount =
@@ -240,17 +298,20 @@ export class PythSolanaReceiver {
   readonly provider: AnchorProvider;
   readonly receiver: Program<PythSolanaReceiverProgram>;
   readonly wormhole: Program<WormholeCoreBridgeSolana>;
+  readonly pushOracle: Program<PythPushOracle>;
 
   constructor({
     connection,
     wallet,
     wormholeProgramId = DEFAULT_WORMHOLE_PROGRAM_ID,
     receiverProgramId = DEFAULT_RECEIVER_PROGRAM_ID,
+    pushOracleProgramId = DEFAULT_PUSH_ORACLE_PROGRAM_ID,
   }: {
     connection: Connection;
     wallet: Wallet;
     wormholeProgramId?: PublicKey;
     receiverProgramId?: PublicKey;
+    pushOracleProgramId?: PublicKey;
   }) {
     this.connection = connection;
     this.wallet = wallet;
@@ -267,6 +328,11 @@ export class PythSolanaReceiver {
       wormholeProgramId,
       this.provider
     );
+    this.pushOracle = new Program<PythPushOracle>(
+      PythPushOracleIdl as PythPushOracle,
+      pushOracleProgramId,
+      this.provider
+    );
   }
 
   /**
@@ -491,6 +557,88 @@ export class PythSolanaReceiver {
     };
   }
 
+  /**
+   * Build a series of helper instructions that update one or many price feed accounts and another series to close the encoded vaa accounts used to update the price feed accounts.
+   *
+   * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates.
+   * @param shardId the shard ID of the set of price feed accounts. This shard ID allows for multiple sets of price feed accounts to be managed by the same program.
+   * @returns `postInstructions`: the instructions to update the price feed accounts. If the price feed accounts don't contain a recent update, these should be called before consuming the price updates.
+   * @returns `priceFeedIdToPriceUpdateAccount`: this is a map of price feed IDs to Solana address. Given a price feed ID, you can use this map to find the account where `postInstructions` will post the price update. Note that since price feed accounts are PDAs, the address of the account can also be found with `getPriceFeedAccountAddress`.
+   * @returns `closeInstructions`: the instructions to close the encoded VAA accounts that were used to update the price feed accounts.
+   */
+  async buildUpdatePriceFeedInstructions(
+    priceUpdateDataArray: string[],
+    shardId: number
+  ): Promise<{
+    postInstructions: InstructionWithEphemeralSigners[];
+    priceFeedIdToPriceUpdateAccount: Record<string, PublicKey>;
+    closeInstructions: InstructionWithEphemeralSigners[];
+  }> {
+    const postInstructions: InstructionWithEphemeralSigners[] = [];
+    const priceFeedIdToPriceUpdateAccount: Record<string, PublicKey> = {};
+    const closeInstructions: InstructionWithEphemeralSigners[] = [];
+
+    for (const priceUpdateData of priceUpdateDataArray) {
+      const accumulatorUpdateData = parseAccumulatorUpdateData(
+        Buffer.from(priceUpdateData, "base64")
+      );
+
+      const {
+        postInstructions: postEncodedVaaInstructions,
+        encodedVaaAddress: encodedVaa,
+        closeInstructions: postEncodedVaacloseInstructions,
+      } = await this.buildPostEncodedVaaInstructions(accumulatorUpdateData.vaa);
+      postInstructions.push(...postEncodedVaaInstructions);
+      closeInstructions.push(...postEncodedVaacloseInstructions);
+
+      for (const update of accumulatorUpdateData.updates) {
+        const feedId = parsePriceFeedMessage(update.message).feedId;
+
+        postInstructions.push({
+          instruction: await this.pushOracle.methods
+            .updatePriceFeed(
+              {
+                merklePriceUpdate: update,
+                treasuryId: DEFAULT_TREASURY_ID,
+              },
+              shardId,
+              Array.from(feedId)
+            )
+            .accounts({
+              pythSolanaReceiver: this.receiver.programId,
+              encodedVaa,
+              priceFeedAccount: getPriceFeedAccountAddress(
+                shardId,
+                feedId,
+                this.pushOracle.programId
+              ),
+              treasury: getTreasuryPda(
+                DEFAULT_TREASURY_ID,
+                this.receiver.programId
+              ),
+              config: getConfigPda(this.receiver.programId),
+            })
+            .instruction(),
+          signers: [],
+          computeUnits: POST_UPDATE_COMPUTE_BUDGET,
+        });
+
+        priceFeedIdToPriceUpdateAccount[
+          "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex")
+        ] = getPriceFeedAccountAddress(
+          shardId,
+          feedId,
+          this.pushOracle.programId
+        );
+      }
+    }
+    return {
+      postInstructions,
+      priceFeedIdToPriceUpdateAccount,
+      closeInstructions,
+    };
+  }
+
   /**
    * Build an instruction to close an encoded VAA account, recovering the rent.
    */
@@ -531,4 +679,60 @@ export class PythSolanaReceiver {
       priorityFeeConfig
     );
   }
+
+  /**
+   * Fetch the contents of a price update account
+   * @param priceUpdateAccount The address of the price update account
+   * @returns The contents of the deserialized price update account or `null` if the account doesn't exist
+   */
+  async fetchPriceUpdateAccount(
+    priceUpdateAccount: PublicKey
+  ): Promise<PriceUpdateAccount | null> {
+    return this.receiver.account.priceUpdateV2.fetchNullable(
+      priceUpdateAccount
+    );
+  }
+
+  /**
+   * Fetch the contents of a price feed account
+   * @param shardId The shard ID of the set of price feed accounts. This shard ID allows for multiple sets of price feed accounts to be managed by the same program.
+   * @param priceFeedId The price feed ID.
+   * @returns The contents of the deserialized price feed account or `null` if the account doesn't exist
+   */
+  async fetchPriceFeedAccount(
+    shardId: number,
+    priceFeedId: Buffer
+  ): Promise<PriceUpdateAccount | null> {
+    return this.receiver.account.priceUpdateV2.fetchNullable(
+      getPriceFeedAccountAddress(
+        shardId,
+        priceFeedId,
+        this.pushOracle.programId
+      )
+    );
+  }
+}
+
+/**
+ * Derive the address of a price feed account
+ * @param shardId The shard ID of the set of price feed accounts. This shard ID allows for multiple sets of price feed accounts to be managed by the same program.
+ * @param priceFeedId The price feed ID.
+ * @param pushOracleProgramId The program ID of the Pyth Push Oracle program. If not provided, the default deployment will be used.
+ * @returns The address of the price feed account
+ */
+function getPriceFeedAccountAddress(
+  shardId: number,
+  feedId: Buffer,
+  pushOracleProgramId?: PublicKey
+): PublicKey {
+  if (feedId.length != 32) {
+    throw new Error("Feed ID should be 32 bytes long");
+  }
+  const shardBuffer = Buffer.alloc(2);
+  shardBuffer.writeUint16LE(shardId, 0);
+
+  return PublicKey.findProgramAddressSync(
+    [shardBuffer, feedId],
+    pushOracleProgramId ?? DEFAULT_PUSH_ORACLE_PROGRAM_ID
+  )[0];
 }

+ 4 - 0
target_chains/solana/sdk/js/pyth_solana_receiver/src/address.ts

@@ -15,6 +15,10 @@ export const DEFAULT_WORMHOLE_PROGRAM_ID = new PublicKey(
   "HDwcJBJXjL9FpJ7UBsYBtaDjsBUhuLCUYoz3zr8SWWaQ"
 );
 
+export const DEFAULT_PUSH_ORACLE_PROGRAM_ID = new PublicKey(
+  "pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT"
+);
+
 /**
  * Returns the address of a guardian set account from the Wormhole program.
  */

+ 233 - 0
target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_push_oracle.ts

@@ -0,0 +1,233 @@
+export type PythPushOracle = {
+  version: "0.1.0";
+  name: "pyth_push_oracle";
+  instructions: [
+    {
+      name: "updatePriceFeed";
+      accounts: [
+        {
+          name: "payer";
+          isMut: true;
+          isSigner: true;
+        },
+        {
+          name: "pythSolanaReceiver";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "encodedVaa";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "config";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "treasury";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "priceFeedAccount";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "systemProgram";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "params";
+          type: {
+            defined: "PostUpdateParams";
+          };
+        },
+        {
+          name: "shardId";
+          type: "u16";
+        },
+        {
+          name: "feedId";
+          type: {
+            array: ["u8", 32];
+          };
+        }
+      ];
+    }
+  ];
+  types: [
+    {
+      name: "PostUpdateParams";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "merklePriceUpdate";
+            type: {
+              defined: "MerklePriceUpdate";
+            };
+          },
+          {
+            name: "treasuryId";
+            type: "u8";
+          }
+        ];
+      };
+    },
+    {
+      name: "MerklePriceUpdate";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "message";
+            type: "bytes";
+          },
+          {
+            name: "proof";
+            type: {
+              vec: {
+                array: ["u8", 20];
+              };
+            };
+          }
+        ];
+      };
+    }
+  ];
+  errors: [
+    {
+      code: 6000;
+      name: "UpdatesNotMonotonic";
+      msg: "Updates must be monotonically increasing";
+    },
+    {
+      code: 6001;
+      name: "PriceFeedMessageMismatch";
+      msg: "Trying to update price feed with the wrong feed id";
+    }
+  ];
+};
+
+export const IDL: PythPushOracle = {
+  version: "0.1.0",
+  name: "pyth_push_oracle",
+  instructions: [
+    {
+      name: "updatePriceFeed",
+      accounts: [
+        {
+          name: "payer",
+          isMut: true,
+          isSigner: true,
+        },
+        {
+          name: "pythSolanaReceiver",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "encodedVaa",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "config",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "treasury",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "priceFeedAccount",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "systemProgram",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "params",
+          type: {
+            defined: "PostUpdateParams",
+          },
+        },
+        {
+          name: "shardId",
+          type: "u16",
+        },
+        {
+          name: "feedId",
+          type: {
+            array: ["u8", 32],
+          },
+        },
+      ],
+    },
+  ],
+  types: [
+    {
+      name: "PostUpdateParams",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "merklePriceUpdate",
+            type: {
+              defined: "MerklePriceUpdate",
+            },
+          },
+          {
+            name: "treasuryId",
+            type: "u8",
+          },
+        ],
+      },
+    },
+    {
+      name: "MerklePriceUpdate",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "message",
+            type: "bytes",
+          },
+          {
+            name: "proof",
+            type: {
+              vec: {
+                array: ["u8", 20],
+              },
+            },
+          },
+        ],
+      },
+    },
+  ],
+  errors: [
+    {
+      code: 6000,
+      name: "UpdatesNotMonotonic",
+      msg: "Updates must be monotonically increasing",
+    },
+    {
+      code: 6001,
+      name: "PriceFeedMessageMismatch",
+      msg: "Trying to update price feed with the wrong feed id",
+    },
+  ],
+};