Explorar o código

feat(solana): js sdk (#1307)

* Do it

* Remove some duplicate code

* Cleanup

* Cleanup

* Cleanup import

* Correct description

* Fix path

* Cleanup deps

* Unique

* Works

* Continue

* Lint

* Lint config

* Fix ci

* Checkpoint

* Checkpoint

* Gitignore

* Cleanup

* Cleanup

* Continue building the sdk

* build function

* Remove files

* Remove files

* Rename

* Refactor : make transaction builder

* Make commitment

* Move

* Progress

* Checkpoint

* Ephemeral signers 2

* Checkpoint

* Checkpoint

* Fix bug

* Cleanup idls

* Compute units

* Make program addresses configurable

* Handle arrays

* Handle arrays

* Move PythSolanaReceiver

* Cleanup constants

* Contants

* Refactor constants

* Gitignore refactor

* package lock

* Cleanup idl

* Add useful static

* Add useful static

* Add useful static

* Lint

* Add lint config
guibescos hai 1 ano
pai
achega
e986b69c9a
Modificáronse 28 ficheiros con 5682 adicións e 396 borrados
  1. 1 1
      .github/workflows/ci-solana-contract.yml
  2. 2 1
      governance/xc_admin/packages/xc_admin_common/package.json
  3. 9 14
      governance/xc_admin/packages/xc_admin_common/src/__tests__/TransactionSize.test.ts
  4. 3 76
      governance/xc_admin/packages/xc_admin_common/src/propose.ts
  5. 5 3
      governance/xc_admin/packages/xc_admin_frontend/hooks/usePyth.ts
  6. 281 294
      package-lock.json
  7. 2 0
      package.json
  8. 52 2
      price_service/sdk/js/src/AccumulatorUpdateData.ts
  9. 18 4
      price_service/sdk/js/src/__tests__/AccumulatorUpdateData.test.ts
  10. 1 1
      price_service/sdk/js/src/index.ts
  11. 1 0
      target_chains/solana/.gitignore
  12. 10 0
      target_chains/solana/sdk/js/pyth_solana_receiver/.eslintrc.js
  13. 49 0
      target_chains/solana/sdk/js/pyth_solana_receiver/package.json
  14. 319 0
      target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts
  15. 31 0
      target_chains/solana/sdk/js/pyth_solana_receiver/src/address.ts
  16. 3 0
      target_chains/solana/sdk/js/pyth_solana_receiver/src/compute_budget.ts
  17. 1205 0
      target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.ts
  18. 3211 0
      target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/wormhole_core_bridge_solana.ts
  19. 2 0
      target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts
  20. 81 0
      target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts
  21. 9 0
      target_chains/solana/sdk/js/pyth_solana_receiver/tsconfig.json
  22. 10 0
      target_chains/solana/sdk/js/solana_utils/.eslintrc.js
  23. 5 0
      target_chains/solana/sdk/js/solana_utils/jest.config.js
  24. 47 0
      target_chains/solana/sdk/js/solana_utils/package.json
  25. 84 0
      target_chains/solana/sdk/js/solana_utils/src/__tests__/TransactionSize.test.ts
  26. 6 0
      target_chains/solana/sdk/js/solana_utils/src/index.ts
  27. 226 0
      target_chains/solana/sdk/js/solana_utils/src/transaction.ts
  28. 9 0
      target_chains/solana/sdk/js/solana_utils/tsconfig.json

+ 1 - 1
.github/workflows/ci-solana-contract.yml

@@ -32,4 +32,4 @@ jobs:
       - name: Run tests
         run: cargo-test-sbf
       - name: Run sdk tests
-        run: cargo test --p pyth-solana-sdk
+        run: cargo test --package pyth-solana-receiver-state

+ 2 - 1
governance/xc_admin/packages/xc_admin_common/package.json

@@ -29,7 +29,8 @@
     "bigint-buffer": "^1.1.5",
     "ethers": "^5.7.2",
     "lodash": "^4.17.21",
-    "typescript": "^4.9.4"
+    "typescript": "^4.9.4",
+    "@pythnetwork/solana-utils": "*"
   },
   "devDependencies": {
     "@types/bn.js": "^5.1.1",

+ 9 - 14
governance/xc_admin/packages/xc_admin_common/src/__tests__/TransactionSize.test.ts

@@ -15,19 +15,13 @@ import {
 } from "@solana/web3.js";
 import {
   batchIntoExecutorPayload,
-  batchIntoTransactions,
-  getSizeOfCompressedU16,
   getSizeOfExecutorInstructions,
-  getSizeOfTransaction,
   MAX_EXECUTOR_PAYLOAD_SIZE,
 } from "..";
-
-it("Unit test compressed u16 size", async () => {
-  expect(getSizeOfCompressedU16(127)).toBe(1);
-  expect(getSizeOfCompressedU16(128)).toBe(2);
-  expect(getSizeOfCompressedU16(16383)).toBe(2);
-  expect(getSizeOfCompressedU16(16384)).toBe(3);
-});
+import {
+  getSizeOfTransaction,
+  TransactionBuilder,
+} from "@pythnetwork/solana-utils";
 
 it("Unit test for getSizeOfTransaction", async () => {
   jest.setTimeout(60000);
@@ -84,7 +78,7 @@ it("Unit test for getSizeOfTransaction", async () => {
   transaction.recentBlockhash = "GqdFtdM7zzWw33YyHtBNwPhyBsdYKcfm9gT47bWnbHvs"; // Mock blockhash from devnet
   transaction.feePayer = payer.publicKey;
   expect(transaction.serialize({ requireAllSignatures: false }).length).toBe(
-    getSizeOfTransaction(ixsToSend)
+    getSizeOfTransaction(ixsToSend, false)
   );
 });
 
@@ -115,13 +109,14 @@ it("Unit test for getSizeOfTransaction", async () => {
     );
   }
 
-  const txToSend: Transaction[] = batchIntoTransactions(ixsToSend);
+  const txToSend: Transaction[] =
+    TransactionBuilder.batchIntoLegacyTransactions(ixsToSend);
   expect(
     txToSend.map((tx) => tx.instructions.length).reduce((a, b) => a + b)
   ).toBe(ixsToSend.length);
   expect(
     txToSend.every(
-      (tx) => getSizeOfTransaction(tx.instructions) <= PACKET_DATA_SIZE
+      (tx) => getSizeOfTransaction(tx.instructions, false) <= PACKET_DATA_SIZE
     )
   ).toBeTruthy();
 
@@ -129,7 +124,7 @@ it("Unit test for getSizeOfTransaction", async () => {
     tx.recentBlockhash = "GqdFtdM7zzWw33YyHtBNwPhyBsdYKcfm9gT47bWnbHvs"; // Mock blockhash from devnet
     tx.feePayer = payer.publicKey;
     expect(tx.serialize({ requireAllSignatures: false }).length).toBe(
-      getSizeOfTransaction(tx.instructions)
+      getSizeOfTransaction(tx.instructions, false)
     );
   }
 

+ 3 - 76
governance/xc_admin/packages/xc_admin_common/src/propose.ts

@@ -25,6 +25,7 @@ import SquadsMesh, { getIxAuthorityPDA, getTxPDA } from "@sqds/mesh";
 import { MultisigAccount } from "@sqds/mesh/lib/types";
 import { mapKey } from "./remote_executor";
 import { WORMHOLE_ADDRESS } from "./wormhole";
+import { TransactionBuilder } from "@pythnetwork/solana-utils";
 
 export const MAX_EXECUTOR_PAYLOAD_SIZE = PACKET_DATA_SIZE - 687; // Bigger payloads won't fit in one addInstruction call when adding to the proposal
 export const MAX_INSTRUCTIONS_PER_PROPOSAL = 256 - 1;
@@ -256,7 +257,7 @@ export class MultisigVault {
     ixToSend.push(await this.activateProposalIx(proposalAddress));
     ixToSend.push(await this.approveProposalIx(proposalAddress));
 
-    const txToSend = batchIntoTransactions(ixToSend);
+    const txToSend = TransactionBuilder.batchIntoLegacyTransactions(ixToSend);
     await this.sendAllTransactions(txToSend);
     return proposalAddress;
   }
@@ -360,7 +361,7 @@ export class MultisigVault {
       }
     }
 
-    const txToSend = batchIntoTransactions(ixToSend);
+    const txToSend = TransactionBuilder.batchIntoLegacyTransactions(ixToSend);
 
     await this.sendAllTransactions(txToSend);
     return newProposals;
@@ -445,32 +446,6 @@ export function batchIntoExecutorPayload(
   return batches;
 }
 
-/**
- * Batch instructions into transactions
- */
-export function batchIntoTransactions(
-  instructions: TransactionInstruction[]
-): Transaction[] {
-  let i = 0;
-  const txToSend: Transaction[] = [];
-  while (i < instructions.length) {
-    let j = i + 2;
-    while (
-      j < instructions.length &&
-      getSizeOfTransaction(instructions.slice(i, j)) <= PACKET_DATA_SIZE
-    ) {
-      j += 1;
-    }
-    const tx = new Transaction();
-    for (let k = i; k < j - 1; k += 1) {
-      tx.add(instructions[k]);
-    }
-    i = j - 1;
-    txToSend.push(tx);
-  }
-  return txToSend;
-}
-
 /** Get the size of instructions when serialized as in a remote executor payload */
 export function getSizeOfExecutorInstructions(
   instructions: TransactionInstruction[]
@@ -481,54 +456,6 @@ export function getSizeOfExecutorInstructions(
     })
     .reduce((a, b) => a + b);
 }
-/**
- * Get the size of a transaction that would contain the provided array of instructions
- */
-export function getSizeOfTransaction(
-  instructions: TransactionInstruction[]
-): number {
-  const signers = new Set<string>();
-  const accounts = new Set<string>();
-
-  instructions.map((ix) => {
-    accounts.add(ix.programId.toBase58()),
-      ix.keys.map((key) => {
-        if (key.isSigner) {
-          signers.add(key.pubkey.toBase58());
-        }
-        accounts.add(key.pubkey.toBase58());
-      });
-  });
-
-  const instruction_sizes: number = instructions
-    .map(
-      (ix) =>
-        1 +
-        getSizeOfCompressedU16(ix.keys.length) +
-        ix.keys.length +
-        getSizeOfCompressedU16(ix.data.length) +
-        ix.data.length
-    )
-    .reduce((a, b) => a + b, 0);
-
-  return (
-    1 +
-    signers.size * 64 +
-    3 +
-    getSizeOfCompressedU16(accounts.size) +
-    32 * accounts.size +
-    32 +
-    getSizeOfCompressedU16(instructions.length) +
-    instruction_sizes
-  );
-}
-
-/**
- * Get the size of n in bytes when serialized as a CompressedU16
- */
-export function getSizeOfCompressedU16(n: number) {
-  return 1 + Number(n >= 128) + Number(n >= 16384);
-}
 
 /**
  * Wrap `instruction` in a Wormhole message for remote execution

+ 5 - 3
governance/xc_admin/packages/xc_admin_frontend/hooks/usePyth.ts

@@ -74,9 +74,11 @@ const usePyth = (): PythHookData => {
     connectionRef.current = connection
     ;(async () => {
       try {
-        const allPythAccounts = await connection.getProgramAccounts(
-          getPythProgramKeyForCluster(cluster)
-        )
+        const allPythAccounts = [
+          ...(await connection.getProgramAccounts(
+            getPythProgramKeyForCluster(cluster)
+          )),
+        ]
         if (cancelled) return
         const priceRawConfigs: { [key: string]: PriceRawConfig } = {}
 

+ 281 - 294
package-lock.json

@@ -24,6 +24,8 @@
         "target_chains/ethereum/examples/oracle_swap/app",
         "target_chains/sui/sdk/js",
         "target_chains/sui/cli",
+        "target_chains/solana/sdk/js/solana_utils",
+        "target_chains/solana/sdk/js/pyth_solana_receiver",
         "contract_manager"
       ],
       "dependencies": {
@@ -527,36 +529,6 @@
         "protobufjs": "~6.11.2"
       }
     },
-    "contract_manager/node_modules/jayson": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
-      "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
-      "dependencies": {
-        "@types/connect": "^3.4.33",
-        "@types/node": "^12.12.54",
-        "@types/ws": "^7.4.4",
-        "commander": "^2.20.3",
-        "delay": "^5.0.0",
-        "es6-promisify": "^5.0.0",
-        "eyes": "^0.1.8",
-        "isomorphic-ws": "^4.0.1",
-        "json-stringify-safe": "^5.0.1",
-        "JSONStream": "^1.3.5",
-        "uuid": "^8.3.2",
-        "ws": "^7.4.5"
-      },
-      "bin": {
-        "jayson": "bin/jayson.js"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "contract_manager/node_modules/jayson/node_modules/@types/node": {
-      "version": "12.20.55",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
-      "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
-    },
     "contract_manager/node_modules/protobufjs": {
       "version": "6.11.3",
       "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz",
@@ -595,6 +567,7 @@
       "version": "7.5.9",
       "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
       "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "optional": true,
       "engines": {
         "node": ">=8.3.0"
       },
@@ -3397,6 +3370,7 @@
         "@certusone/wormhole-sdk": "^0.9.22",
         "@coral-xyz/anchor": "^0.26.0",
         "@pythnetwork/client": "^2.17.0",
+        "@pythnetwork/solana-utils": "*",
         "@solana/buffer-layout": "^4.0.1",
         "@solana/web3.js": "^1.73.0",
         "@sqds/mesh": "^1.0.6",
@@ -3821,36 +3795,6 @@
         "node": ">=8.0.0"
       }
     },
-    "governance/xc_admin/packages/xc_admin_common/node_modules/jayson": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
-      "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
-      "dependencies": {
-        "@types/connect": "^3.4.33",
-        "@types/node": "^12.12.54",
-        "@types/ws": "^7.4.4",
-        "commander": "^2.20.3",
-        "delay": "^5.0.0",
-        "es6-promisify": "^5.0.0",
-        "eyes": "^0.1.8",
-        "isomorphic-ws": "^4.0.1",
-        "json-stringify-safe": "^5.0.1",
-        "JSONStream": "^1.3.5",
-        "uuid": "^8.3.2",
-        "ws": "^7.4.5"
-      },
-      "bin": {
-        "jayson": "bin/jayson.js"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "governance/xc_admin/packages/xc_admin_common/node_modules/jayson/node_modules/@types/node": {
-      "version": "12.20.55",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
-      "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
-    },
     "governance/xc_admin/packages/xc_admin_common/node_modules/prettier": {
       "version": "2.8.3",
       "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
@@ -3920,6 +3864,7 @@
       "version": "7.5.9",
       "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
       "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "optional": true,
       "engines": {
         "node": ">=8.3.0"
       },
@@ -6471,16 +6416,21 @@
       }
     },
     "node_modules/@babel/runtime": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
-      "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==",
+      "version": "7.23.9",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
+      "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
       "dependencies": {
-        "regenerator-runtime": "^0.13.11"
+        "regenerator-runtime": "^0.14.0"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@babel/runtime/node_modules/regenerator-runtime": {
+      "version": "0.14.1",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+    },
     "node_modules/@babel/template": {
       "version": "7.20.7",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
@@ -13248,10 +13198,18 @@
       "resolved": "target_chains/ethereum/sdk/solidity",
       "link": true
     },
+    "node_modules/@pythnetwork/pyth-solana-receiver": {
+      "resolved": "target_chains/solana/sdk/js/pyth_solana_receiver",
+      "link": true
+    },
     "node_modules/@pythnetwork/pyth-sui-js": {
       "resolved": "target_chains/sui/sdk/js",
       "link": true
     },
+    "node_modules/@pythnetwork/solana-utils": {
+      "resolved": "target_chains/solana/sdk/js/solana_utils",
+      "link": true
+    },
     "node_modules/@radix-ui/primitive": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
@@ -16274,37 +16232,48 @@
       }
     },
     "node_modules/@solana/web3.js": {
-      "version": "1.76.0",
-      "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.76.0.tgz",
-      "integrity": "sha512-aJtF/nTs+9St+KtTK/wgVJ+SinfjYzn+3w1ygYIPw8ST6LH+qHBn8XkodgDTwlv/xzNkaVz1kkUDOZ8BPXyZWA==",
+      "version": "1.90.0",
+      "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.90.0.tgz",
+      "integrity": "sha512-p0cb/COXb8NNVSMkGMPwqQ6NvObZgUitN80uOedMB+jbYWOKOeJBuPnzhenkIV9RX0krGwyuY1Ltn5O8MGFsEw==",
       "dependencies": {
-        "@babel/runtime": "^7.12.5",
-        "@noble/curves": "^1.0.0",
-        "@noble/hashes": "^1.3.0",
-        "@solana/buffer-layout": "^4.0.0",
-        "agentkeepalive": "^4.2.1",
+        "@babel/runtime": "^7.23.4",
+        "@noble/curves": "^1.2.0",
+        "@noble/hashes": "^1.3.2",
+        "@solana/buffer-layout": "^4.0.1",
+        "agentkeepalive": "^4.5.0",
         "bigint-buffer": "^1.1.5",
-        "bn.js": "^5.0.0",
+        "bn.js": "^5.2.1",
         "borsh": "^0.7.0",
         "bs58": "^4.0.1",
         "buffer": "6.0.3",
         "fast-stable-stringify": "^1.0.0",
-        "jayson": "^3.4.4",
-        "node-fetch": "^2.6.7",
+        "jayson": "^4.1.0",
+        "node-fetch": "^2.7.0",
         "rpc-websockets": "^7.5.1",
         "superstruct": "^0.14.2"
       }
     },
-    "node_modules/@solana/web3.js/node_modules/@noble/hashes": {
+    "node_modules/@solana/web3.js/node_modules/@noble/curves": {
       "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
-      "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
-      "funding": [
-        {
-          "type": "individual",
-          "url": "https://paulmillr.com/funding/"
-        }
-      ]
+      "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz",
+      "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==",
+      "dependencies": {
+        "@noble/hashes": "1.3.3"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/@solana/web3.js/node_modules/@noble/hashes": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz",
+      "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==",
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
     },
     "node_modules/@solana/web3.js/node_modules/buffer": {
       "version": "6.0.3",
@@ -22006,12 +21975,10 @@
       }
     },
     "node_modules/agentkeepalive": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz",
-      "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==",
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
+      "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
       "dependencies": {
-        "debug": "^4.1.0",
-        "depd": "^1.1.2",
         "humanize-ms": "^1.2.1"
       },
       "engines": {
@@ -34442,9 +34409,9 @@
       }
     },
     "node_modules/jayson": {
-      "version": "3.7.0",
-      "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz",
-      "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
+      "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
       "dependencies": {
         "@types/connect": "^3.4.33",
         "@types/node": "^12.12.54",
@@ -34456,7 +34423,6 @@
         "isomorphic-ws": "^4.0.1",
         "json-stringify-safe": "^5.0.1",
         "JSONStream": "^1.3.5",
-        "lodash": "^4.17.20",
         "uuid": "^8.3.2",
         "ws": "^7.4.5"
       },
@@ -58819,41 +58785,6 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
-    "target_chains/ethereum/contracts/node_modules/jayson": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
-      "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
-      "dependencies": {
-        "@types/connect": "^3.4.33",
-        "@types/node": "^12.12.54",
-        "@types/ws": "^7.4.4",
-        "commander": "^2.20.3",
-        "delay": "^5.0.0",
-        "es6-promisify": "^5.0.0",
-        "eyes": "^0.1.8",
-        "isomorphic-ws": "^4.0.1",
-        "json-stringify-safe": "^5.0.1",
-        "JSONStream": "^1.3.5",
-        "uuid": "^8.3.2",
-        "ws": "^7.4.5"
-      },
-      "bin": {
-        "jayson": "bin/jayson.js"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "target_chains/ethereum/contracts/node_modules/jayson/node_modules/@types/node": {
-      "version": "12.20.55",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
-      "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
-    },
-    "target_chains/ethereum/contracts/node_modules/jayson/node_modules/commander": {
-      "version": "2.20.3",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
-    },
     "target_chains/ethereum/contracts/node_modules/jsonfile": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
@@ -58949,6 +58880,7 @@
       "version": "7.5.9",
       "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
       "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "optional": true,
       "engines": {
         "node": ">=8.3.0"
       },
@@ -59598,6 +59530,107 @@
         "node": ">=10.0.0"
       }
     },
+    "target_chains/solana/sdk/js/pyth_solana_receiver": {
+      "version": "0.1.0",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@coral-xyz/anchor": "^0.29.0",
+        "@pythnetwork/price-service-sdk": "*",
+        "@pythnetwork/solana-utils": "*",
+        "@solana/web3.js": "^1.90.0"
+      },
+      "devDependencies": {
+        "@types/jest": "^29.4.0",
+        "@typescript-eslint/eslint-plugin": "^5.20.0",
+        "@typescript-eslint/parser": "^5.20.0",
+        "eslint": "^8.13.0",
+        "jest": "^29.4.0",
+        "prettier": "^2.6.2",
+        "quicktype": "^23.0.76",
+        "ts-jest": "^29.0.5",
+        "typescript": "^4.6.3"
+      }
+    },
+    "target_chains/solana/sdk/js/pyth_solana_receiver/node_modules/@coral-xyz/anchor": {
+      "version": "0.29.0",
+      "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.29.0.tgz",
+      "integrity": "sha512-eny6QNG0WOwqV0zQ7cs/b1tIuzZGmP7U7EcH+ogt4Gdbl8HDmIYVMh/9aTmYZPaFWjtUaI8qSn73uYEXWfATdA==",
+      "dependencies": {
+        "@coral-xyz/borsh": "^0.29.0",
+        "@noble/hashes": "^1.3.1",
+        "@solana/web3.js": "^1.68.0",
+        "bn.js": "^5.1.2",
+        "bs58": "^4.0.1",
+        "buffer-layout": "^1.2.2",
+        "camelcase": "^6.3.0",
+        "cross-fetch": "^3.1.5",
+        "crypto-hash": "^1.3.0",
+        "eventemitter3": "^4.0.7",
+        "pako": "^2.0.3",
+        "snake-case": "^3.0.4",
+        "superstruct": "^0.15.4",
+        "toml": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=11"
+      }
+    },
+    "target_chains/solana/sdk/js/pyth_solana_receiver/node_modules/@coral-xyz/borsh": {
+      "version": "0.29.0",
+      "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.29.0.tgz",
+      "integrity": "sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ==",
+      "dependencies": {
+        "bn.js": "^5.1.2",
+        "buffer-layout": "^1.2.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "@solana/web3.js": "^1.68.0"
+      }
+    },
+    "target_chains/solana/sdk/js/pyth_solana_receiver/node_modules/@noble/hashes": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz",
+      "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==",
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "target_chains/solana/sdk/js/pyth_solana_receiver/node_modules/camelcase": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "target_chains/solana/sdk/js/solana_utils": {
+      "name": "@pythnetwork/solana-utils",
+      "version": "0.1.0",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@solana/web3.js": "^1.90.0"
+      },
+      "devDependencies": {
+        "@types/jest": "^29.4.0",
+        "@typescript-eslint/eslint-plugin": "^5.20.0",
+        "@typescript-eslint/parser": "^5.20.0",
+        "eslint": "^8.13.0",
+        "jest": "^29.4.0",
+        "prettier": "^2.6.2",
+        "quicktype": "^23.0.76",
+        "ts-jest": "^29.0.5",
+        "typescript": "^4.6.3"
+      }
+    },
     "target_chains/sui/cli": {
       "name": "pyth-sui-cli",
       "version": "0.0.1",
@@ -60047,36 +60080,6 @@
         "node-fetch": "^2.6.12"
       }
     },
-    "target_chains/sui/cli/node_modules/jayson": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
-      "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
-      "dependencies": {
-        "@types/connect": "^3.4.33",
-        "@types/node": "^12.12.54",
-        "@types/ws": "^7.4.4",
-        "commander": "^2.20.3",
-        "delay": "^5.0.0",
-        "es6-promisify": "^5.0.0",
-        "eyes": "^0.1.8",
-        "isomorphic-ws": "^4.0.1",
-        "json-stringify-safe": "^5.0.1",
-        "JSONStream": "^1.3.5",
-        "uuid": "^8.3.2",
-        "ws": "^7.4.5"
-      },
-      "bin": {
-        "jayson": "bin/jayson.js"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "target_chains/sui/cli/node_modules/jayson/node_modules/@types/node": {
-      "version": "12.20.55",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
-      "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
-    },
     "target_chains/sui/cli/node_modules/prettier": {
       "version": "2.8.8",
       "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
@@ -60141,6 +60144,7 @@
       "version": "7.5.9",
       "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
       "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "optional": true,
       "engines": {
         "node": ">=8.3.0"
       },
@@ -61783,11 +61787,18 @@
       }
     },
     "@babel/runtime": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
-      "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==",
+      "version": "7.23.9",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
+      "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
       "requires": {
-        "regenerator-runtime": "^0.13.11"
+        "regenerator-runtime": "^0.14.0"
+      },
+      "dependencies": {
+        "regenerator-runtime": {
+          "version": "0.14.1",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+          "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+        }
       }
     },
     "@babel/template": {
@@ -70538,37 +70549,6 @@
             "path-is-absolute": "^1.0.0"
           }
         },
-        "jayson": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
-          "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
-          "requires": {
-            "@types/connect": "^3.4.33",
-            "@types/node": "^12.12.54",
-            "@types/ws": "^7.4.4",
-            "commander": "^2.20.3",
-            "delay": "^5.0.0",
-            "es6-promisify": "^5.0.0",
-            "eyes": "^0.1.8",
-            "isomorphic-ws": "^4.0.1",
-            "json-stringify-safe": "^5.0.1",
-            "JSONStream": "^1.3.5",
-            "uuid": "^8.3.2",
-            "ws": "^7.4.5"
-          },
-          "dependencies": {
-            "@types/node": {
-              "version": "12.20.55",
-              "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
-              "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
-            },
-            "commander": {
-              "version": "2.20.3",
-              "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-              "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
-            }
-          }
-        },
         "jsonfile": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
@@ -70641,6 +70621,7 @@
           "version": "7.5.9",
           "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
           "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+          "optional": true,
           "requires": {}
         }
       }
@@ -71515,6 +71496,66 @@
         }
       }
     },
+    "@pythnetwork/pyth-solana-receiver": {
+      "version": "file:target_chains/solana/sdk/js/pyth_solana_receiver",
+      "requires": {
+        "@coral-xyz/anchor": "^0.29.0",
+        "@pythnetwork/price-service-sdk": "*",
+        "@pythnetwork/solana-utils": "*",
+        "@solana/web3.js": "^1.90.0",
+        "@types/jest": "^29.4.0",
+        "@typescript-eslint/eslint-plugin": "^5.20.0",
+        "@typescript-eslint/parser": "^5.20.0",
+        "eslint": "^8.13.0",
+        "jest": "^29.4.0",
+        "prettier": "^2.6.2",
+        "quicktype": "^23.0.76",
+        "ts-jest": "^29.0.5",
+        "typescript": "^4.6.3"
+      },
+      "dependencies": {
+        "@coral-xyz/anchor": {
+          "version": "0.29.0",
+          "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.29.0.tgz",
+          "integrity": "sha512-eny6QNG0WOwqV0zQ7cs/b1tIuzZGmP7U7EcH+ogt4Gdbl8HDmIYVMh/9aTmYZPaFWjtUaI8qSn73uYEXWfATdA==",
+          "requires": {
+            "@coral-xyz/borsh": "^0.29.0",
+            "@noble/hashes": "^1.3.1",
+            "@solana/web3.js": "^1.68.0",
+            "bn.js": "^5.1.2",
+            "bs58": "^4.0.1",
+            "buffer-layout": "^1.2.2",
+            "camelcase": "^6.3.0",
+            "cross-fetch": "^3.1.5",
+            "crypto-hash": "^1.3.0",
+            "eventemitter3": "^4.0.7",
+            "pako": "^2.0.3",
+            "snake-case": "^3.0.4",
+            "superstruct": "^0.15.4",
+            "toml": "^3.0.0"
+          }
+        },
+        "@coral-xyz/borsh": {
+          "version": "0.29.0",
+          "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.29.0.tgz",
+          "integrity": "sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ==",
+          "requires": {
+            "bn.js": "^5.1.2",
+            "buffer-layout": "^1.2.0"
+          }
+        },
+        "@noble/hashes": {
+          "version": "1.3.3",
+          "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz",
+          "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA=="
+        },
+        "camelcase": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+          "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="
+        }
+      }
+    },
     "@pythnetwork/pyth-sui-js": {
       "version": "file:target_chains/sui/sdk/js",
       "requires": {
@@ -71581,6 +71622,21 @@
         }
       }
     },
+    "@pythnetwork/solana-utils": {
+      "version": "file:target_chains/solana/sdk/js/solana_utils",
+      "requires": {
+        "@solana/web3.js": "^1.90.0",
+        "@types/jest": "^29.4.0",
+        "@typescript-eslint/eslint-plugin": "^5.20.0",
+        "@typescript-eslint/parser": "^5.20.0",
+        "eslint": "^8.13.0",
+        "jest": "^29.4.0",
+        "prettier": "^2.6.2",
+        "quicktype": "^23.0.76",
+        "ts-jest": "^29.0.5",
+        "typescript": "^4.6.3"
+      }
+    },
     "@radix-ui/primitive": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
@@ -73818,31 +73874,39 @@
       }
     },
     "@solana/web3.js": {
-      "version": "1.76.0",
-      "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.76.0.tgz",
-      "integrity": "sha512-aJtF/nTs+9St+KtTK/wgVJ+SinfjYzn+3w1ygYIPw8ST6LH+qHBn8XkodgDTwlv/xzNkaVz1kkUDOZ8BPXyZWA==",
+      "version": "1.90.0",
+      "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.90.0.tgz",
+      "integrity": "sha512-p0cb/COXb8NNVSMkGMPwqQ6NvObZgUitN80uOedMB+jbYWOKOeJBuPnzhenkIV9RX0krGwyuY1Ltn5O8MGFsEw==",
       "requires": {
-        "@babel/runtime": "^7.12.5",
-        "@noble/curves": "^1.0.0",
-        "@noble/hashes": "^1.3.0",
-        "@solana/buffer-layout": "^4.0.0",
-        "agentkeepalive": "^4.2.1",
+        "@babel/runtime": "^7.23.4",
+        "@noble/curves": "^1.2.0",
+        "@noble/hashes": "^1.3.2",
+        "@solana/buffer-layout": "^4.0.1",
+        "agentkeepalive": "^4.5.0",
         "bigint-buffer": "^1.1.5",
-        "bn.js": "^5.0.0",
+        "bn.js": "^5.2.1",
         "borsh": "^0.7.0",
         "bs58": "^4.0.1",
         "buffer": "6.0.3",
         "fast-stable-stringify": "^1.0.0",
-        "jayson": "^3.4.4",
-        "node-fetch": "^2.6.7",
+        "jayson": "^4.1.0",
+        "node-fetch": "^2.7.0",
         "rpc-websockets": "^7.5.1",
         "superstruct": "^0.14.2"
       },
       "dependencies": {
-        "@noble/hashes": {
+        "@noble/curves": {
           "version": "1.3.0",
-          "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
-          "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg=="
+          "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz",
+          "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==",
+          "requires": {
+            "@noble/hashes": "1.3.3"
+          }
+        },
+        "@noble/hashes": {
+          "version": "1.3.3",
+          "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz",
+          "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA=="
         },
         "buffer": {
           "version": "6.0.3",
@@ -78597,12 +78661,10 @@
       }
     },
     "agentkeepalive": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz",
-      "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==",
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
+      "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
       "requires": {
-        "debug": "^4.1.0",
-        "depd": "^1.1.2",
         "humanize-ms": "^1.2.1"
       }
     },
@@ -81883,32 +81945,6 @@
             "protobufjs": "~6.11.2"
           }
         },
-        "jayson": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
-          "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
-          "requires": {
-            "@types/connect": "^3.4.33",
-            "@types/node": "^12.12.54",
-            "@types/ws": "^7.4.4",
-            "commander": "^2.20.3",
-            "delay": "^5.0.0",
-            "es6-promisify": "^5.0.0",
-            "eyes": "^0.1.8",
-            "isomorphic-ws": "^4.0.1",
-            "json-stringify-safe": "^5.0.1",
-            "JSONStream": "^1.3.5",
-            "uuid": "^8.3.2",
-            "ws": "^7.4.5"
-          },
-          "dependencies": {
-            "@types/node": {
-              "version": "12.20.55",
-              "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
-              "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
-            }
-          }
-        },
         "protobufjs": {
           "version": "6.11.3",
           "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz",
@@ -81939,6 +81975,7 @@
           "version": "7.5.9",
           "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
           "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+          "optional": true,
           "requires": {}
         }
       }
@@ -88900,9 +88937,9 @@
       }
     },
     "jayson": {
-      "version": "3.7.0",
-      "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz",
-      "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
+      "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
       "requires": {
         "@types/connect": "^3.4.33",
         "@types/node": "^12.12.54",
@@ -88914,7 +88951,6 @@
         "isomorphic-ws": "^4.0.1",
         "json-stringify-safe": "^5.0.1",
         "JSONStream": "^1.3.5",
-        "lodash": "^4.17.20",
         "uuid": "^8.3.2",
         "ws": "^7.4.5"
       },
@@ -97094,32 +97130,6 @@
             "node-fetch": "^2.6.12"
           }
         },
-        "jayson": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
-          "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
-          "requires": {
-            "@types/connect": "^3.4.33",
-            "@types/node": "^12.12.54",
-            "@types/ws": "^7.4.4",
-            "commander": "^2.20.3",
-            "delay": "^5.0.0",
-            "es6-promisify": "^5.0.0",
-            "eyes": "^0.1.8",
-            "isomorphic-ws": "^4.0.1",
-            "json-stringify-safe": "^5.0.1",
-            "JSONStream": "^1.3.5",
-            "uuid": "^8.3.2",
-            "ws": "^7.4.5"
-          },
-          "dependencies": {
-            "@types/node": {
-              "version": "12.20.55",
-              "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
-              "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
-            }
-          }
-        },
         "prettier": {
           "version": "2.8.8",
           "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
@@ -97160,6 +97170,7 @@
           "version": "7.5.9",
           "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
           "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+          "optional": true,
           "requires": {}
         }
       }
@@ -105908,6 +105919,7 @@
         "@certusone/wormhole-sdk": "^0.9.22",
         "@coral-xyz/anchor": "^0.26.0",
         "@pythnetwork/client": "^2.17.0",
+        "@pythnetwork/solana-utils": "*",
         "@solana/buffer-layout": "^4.0.1",
         "@solana/web3.js": "^1.73.0",
         "@sqds/mesh": "^1.0.6",
@@ -106298,32 +106310,6 @@
             "pure-rand": "^6.0.0"
           }
         },
-        "jayson": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz",
-          "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==",
-          "requires": {
-            "@types/connect": "^3.4.33",
-            "@types/node": "^12.12.54",
-            "@types/ws": "^7.4.4",
-            "commander": "^2.20.3",
-            "delay": "^5.0.0",
-            "es6-promisify": "^5.0.0",
-            "eyes": "^0.1.8",
-            "isomorphic-ws": "^4.0.1",
-            "json-stringify-safe": "^5.0.1",
-            "JSONStream": "^1.3.5",
-            "uuid": "^8.3.2",
-            "ws": "^7.4.5"
-          },
-          "dependencies": {
-            "@types/node": {
-              "version": "12.20.55",
-              "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
-              "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="
-            }
-          }
-        },
         "prettier": {
           "version": "2.8.3",
           "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
@@ -106366,6 +106352,7 @@
           "version": "7.5.9",
           "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
           "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+          "optional": true,
           "requires": {}
         }
       }

+ 2 - 0
package.json

@@ -19,6 +19,8 @@
     "target_chains/ethereum/examples/oracle_swap/app",
     "target_chains/sui/sdk/js",
     "target_chains/sui/cli",
+    "target_chains/solana/sdk/js/solana_utils",
+    "target_chains/solana/sdk/js/pyth_solana_receiver",
     "contract_manager"
   ],
   "dependencies": {

+ 52 - 2
price_service/sdk/js/src/AccumulatorUpdateData.ts

@@ -1,11 +1,25 @@
+import BN from "bn.js";
+
 const ACCUMULATOR_MAGIC = "504e4155";
 const MAJOR_VERSION = 1;
 const MINOR_VERSION = 0;
 const KECCAK160_HASH_SIZE = 20;
+const PRICE_FEED_MESSAGE_VARIANT = 0;
 
 export type AccumulatorUpdateData = {
   vaa: Buffer;
-  updates: { message: Buffer; proof: Buffer[] }[];
+  updates: { message: Buffer; proof: number[][] }[];
+};
+
+export type PriceFeedMessage = {
+  feedId: Buffer;
+  price: BN;
+  confidence: BN;
+  exponent: number;
+  publishTime: BN;
+  prevPublishTime: BN;
+  emaPrice: BN;
+  emaConf: BN;
 };
 
 export function isAccumulatorUpdateData(updateBytes: Buffer): boolean {
@@ -15,6 +29,40 @@ export function isAccumulatorUpdateData(updateBytes: Buffer): boolean {
     updateBytes[5] === MINOR_VERSION
   );
 }
+export function parsePriceFeedMessage(message: Buffer): PriceFeedMessage {
+  let cursor = 0;
+  const variant = message.readUInt8(cursor);
+  if (variant !== PRICE_FEED_MESSAGE_VARIANT) {
+    throw new Error("Not a price feed message");
+  }
+  cursor += 1;
+  const feedId = message.subarray(cursor, cursor + 32);
+  cursor += 32;
+  const price = new BN(message.subarray(cursor, cursor + 8), "be");
+  cursor += 8;
+  const confidence = new BN(message.subarray(cursor, cursor + 8), "be");
+  cursor += 8;
+  const exponent = message.readInt32BE(cursor);
+  cursor += 4;
+  const publishTime = new BN(message.subarray(cursor, cursor + 8), "be");
+  cursor += 8;
+  const prevPublishTime = new BN(message.subarray(cursor, cursor + 8), "be");
+  cursor += 8;
+  const emaPrice = new BN(message.subarray(cursor, cursor + 8), "be");
+  cursor += 8;
+  const emaConf = new BN(message.subarray(cursor, cursor + 8), "be");
+  cursor += 8;
+  return {
+    feedId,
+    price,
+    confidence,
+    exponent,
+    publishTime,
+    prevPublishTime,
+    emaPrice,
+    emaConf,
+  };
+}
 
 export function parseAccumulatorUpdateData(
   data: Buffer
@@ -50,7 +98,9 @@ export function parseAccumulatorUpdateData(
     cursor += 1;
     const proof = [];
     for (let j = 0; j < numProofs; j++) {
-      proof.push(data.subarray(cursor, cursor + KECCAK160_HASH_SIZE));
+      proof.push(
+        Array.from(data.subarray(cursor, cursor + KECCAK160_HASH_SIZE))
+      );
       cursor += KECCAK160_HASH_SIZE;
     }
 

+ 18 - 4
price_service/sdk/js/src/__tests__/AccumulatorUpdateData.test.ts

@@ -1,14 +1,28 @@
-import { parseAccumulatorUpdateData } from "../AccumulatorUpdateData";
+import {
+  parseAccumulatorUpdateData,
+  parsePriceFeedMessage,
+} from "../AccumulatorUpdateData";
 
 // This is just a sample update data from hermes
 const TEST_ACCUMULATOR_UPDATE_DATA =
-  "UE5BVQEAAAADuAEAAAADDQCRWcud7VE0FQkptV7iZh1Ls8O4dszQ4HmdOZLNDVQiQnMB5Q9jVd52Y9IMI8k1QhOhT2xn82hveqZ6AIn+c6vPAQL4nN+ynbzaJnbGFpWW5ysEA811BblKr+DXO5I5tD3SgEjPmByPIEVPHRqdgnq7M8r6AG4q8qfbmeonROr67i4eAAPVteKrUXD6f13GG/Qj0xHcJ/NuR+xwrbs6KGmYZHq0fHv4m0C3LPIOgVo9iy2ednK5IB/pAEMoaGK/fwoL2ouSAQSulF0XQGZ0J5oFKvCwPBSZOYtITwEQSicnwIWu9a+j7SjMh/zF4vtqWFAqfkFatVMZI6/dkQkmwlcMkEkGHvN5AQZYiYD8teZVpmCzn9jxZo/qTF4qrWgrHWv3/i4kZsXmkDSq1QTiYd7ikQQVWVxgH3PKl03SPFvqoc7SmwKIZKyyAQjfPTwpqeTTi0zFRyyb9HKMYjcbXEcuRXn7uOaNF83ry1s+cudCcWsiaCNYEPzv1BvHxgYYXcx2MkNxUbXiLlmoAQpQSpOkNb9780k2EsrUjZd/ieD+sTQA6P0iZWL5jA8ONEi46mAufCfRlAO2a5jfUvjuN4Z/ZOklgT9eZ7v3JoleAAv/2wkZ5rQx+cl/jlL9k6rbzrDU8sYLTJnlFTsuOr66/iVUqCe0Clwv682NgvH8yLbtw9He/vdn3OeLn19eDU0qAQxk47DIhc9EAtNrdhFSyAoEBtQtgcxRvSnjIIMPTGIhIzv52WFY/I2CwyKcQLhERdjjfh7EhZvBUXHTFRk2xjc2AA3wNaGbjUXsJqL8VyBQg7t0dILbUQ8AiOZJQVfx+L+1mFVZAc4v8/0BWsIF5b7+YmoN6psArWCvZcd9Hkjuxda4AQ5Rxgs32U2Jm43W4voTk42MibgvPMas3xQbuCW88pH1skdSTfvtgoIOa6BdoS3YEUJu78a0X3AiIUem1fDOdOs7AA/lHyqNz4vwuTNs8U6G51VqO2g1yEJyRwrMqsjEvK9VC0EjieacqPBwPL9/DMssbHU01bL+YzEY5XTxi1QiBeyFABJuE+6jHgEh9WvwaPDZe7me9sl5EiPDUxAAryErsB0LDTrnzls7qgDymCp+MSJur8U4I08ul/mL1rVesK3uUqqtAGV20Z0AAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAAB1IYyAUFVV1YAAAAAAAbbm1gAACcQralfDHbB9c321F6ngWz+RspcmdsBAFUAyWRY05P+net6fWOgrEHiiYpnp3UNvRZmcyeeBsho3woAAAAAAFEIJAAAAAAAAAxT////+AAAAABldtGcAAAAAGV20ZsAAAAAAFEWJQAAAAAAAAu6CqMvc3++cZquwewCu6kJe8aPB1SFPI41uwi10MgNwqRbCue30EvorUjF4mKpFB+Cwx8KH5bFnAAX13DPmu7OCbX7k0LdKtr9pb8zPVsXwlx+BteFyBWNtJmeLIx7tG88H2uARL/B+MJw2GcVujs6qdnIQkIjjBdDIR3XRtY2zMfK58eeXuiAkJDHIQ3H41GmYRAVe8FtPvtMWTY51Q63Tkmfq60qsB1yy4Srd5QI/x60eBnOlAYC67+gjB0sGHLrjSapbXzGUf//";
-
+  "UE5BVQEAAAADuAEAAAADDQILBDsleD7xENCN7O7KjXAe15OQQ8FvD0E6L+bIemppBjNEZEOF0XuxCPQ/ti4gWUplKpieCWYiibuaDvXOeSFFAQOuYekUPV6yqK2BRWo/KRmJ00SV01OJDRhNzc1YnzUi+j8dt1tNBkVY99NIs5leyixUmeq4Dn1n8y37xN4JtAacAQSxJzGeZ6tejBD1YlPBlVwaCX8crkp19R1es7rDBhX/iit2plz5grz66fPj/mpoffZqKo95Fq/0sxWHIvn4nhgXAQYLl99cpa6KlaA8q1Pj7sN6TXNrXtmTBlzRU6dZ0ptO8VKp4K3zkVqbWkB5mbHCeuYNgOGMCnVsS7Ce9J7NganNAQf0nyez/5yR/U2zu+XRbi8eNzI1yJ9Hc4lmMl8pTPPQRgrs9HyiVCliCOcHdLzLio3JoLBhmFxQ3ygYj2eB+k3UAQgHX1e/+vbCjBNnmx/UQV8m0y/wifKAMfYpK4mR8voG3wgxo5MIFUvvCZ9/Gt1GizTX5CuoQD9J4ioxjoCFghVtAQqG5lFSpVRpC0dQlMv2ju2K89Ph0tJGsX7LGRXRnh9lEkkM8W+Uxf1R50HFsZHiXU08Grz0mKRPavesrzD+1xYGAQuYL6q5SagvBS7TfZJYS4kUMw74TvMiHLWx2ps3EdEJbh3WCWGfOM3amrplQBnqctDYh3StqspyTdaU5QTxfyYvAQwNWdPBEtAR6yIHB8KYrEDGGUH91uqD768NGigW6ziLwnNw2un+gcDUiafL3pZpqC4yIDhmnEz26PmQs4cAI5nkAA3/Zl3Pt7fLG3E5xBa/lbdrBUT3J+znFExbuFZuZipvbBwnQq/yyBSXqyfuHG3GTQZ/wXBto5zUEyex9889XYzaAQ52EUUCG0X4i0nWHeAf00+s6cODkW6hanQ1MHfTdvvVMXqK9nfvicz8pBna/NVp1wiTN5zR9rWjQuAf0g0c6TRLARCPT46a0/3xER/tV7WLQ6JQUWHMbV0G6cXKmdFT0Qg3/m08Dlabic+EHW9u2ugZA5sJ/Jl4oGk/lWLJoNoxDFMZARJgsgN2RdhjvJMRmf/Kj0d5PSvI7kE+J7ShlJd5058+ETZVPR15fJpT3BWUJ8i/vdGmU90A6iGyXRmNRBFx21qqAGXeGOEAAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAACjFEsAUFVV1YAAAAAAAeUpesAACcQy5naBQ5EEmAnr2RvKBD1SUJH9zwBAFUA7w2Lb9os66QdoV1AldHaOSoNL47Qxse8D0z6yMKAtW0AAAACgAWPuQAAAAAAX+C0////+AAAAABl3hjhAAAAAGXeGOEAAAACgrM/OAAAAAAAaAvYCuE9uKnM0BlXJ1E/fLYtWytcVLhkSCzHW9p1ReYbPMt07qbcN5KykfYDlCJxjBT3UmyTbhTT30PmOLkZu9zraLg22Wysdg1W67WoQZi654djZPBpAiHRU2KQXbDSGqJcekD0W+TqKO+QagPAoXksP0iMGEYpBdVZKGhSvw0NpXv/5Qb5NHO/+dTahPFBgXJH+9geJaxYru9ZRMg5o+YjIvkwuom/2NP0mTbfp4syeDQBs+fmcdGmAjgcdF0wt1gR6kYbsWQ/CZ08";
 describe("Test parse accumulator update", () => {
   test("Happy path", async () => {
-    parseAccumulatorUpdateData(
+    const { vaa, updates } = parseAccumulatorUpdateData(
       Buffer.from(TEST_ACCUMULATOR_UPDATE_DATA, "base64")
     );
+
+    const priceMessage = parsePriceFeedMessage(updates[0].message);
+    expect(priceMessage.feedId.toString("hex")).toBe(
+      "ef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d"
+    );
+    expect(priceMessage.price.toString()).toBe("10737782713");
+    expect(priceMessage.confidence.toString()).toBe("6283444");
+    expect(priceMessage.exponent).toBe(-8);
+    expect(priceMessage.publishTime.toString()).toBe("1709054177");
+    expect(priceMessage.prevPublishTime.toString()).toBe("1709054177");
+    expect(priceMessage.emaPrice.toString()).toBe("10782719800");
+    expect(priceMessage.emaConf.toString()).toBe("6818776");
   });
 
   test("Wrong magic number", async () => {

+ 1 - 1
price_service/sdk/js/src/index.ts

@@ -1,4 +1,3 @@
-import { isAccumulatorUpdateData } from "./AccumulatorUpdateData";
 import {
   Convert,
   Price as JsonPrice,
@@ -14,6 +13,7 @@ export {
   isAccumulatorUpdateData,
   parseAccumulatorUpdateData,
   AccumulatorUpdateData,
+  parsePriceFeedMessage,
 } from "./AccumulatorUpdateData";
 
 /**

+ 1 - 0
target_chains/solana/.gitignore

@@ -4,4 +4,5 @@
 target
 **/*.rs.bk
 node_modules
+lib
 test-ledger

+ 10 - 0
target_chains/solana/sdk/js/pyth_solana_receiver/.eslintrc.js

@@ -0,0 +1,10 @@
+module.exports = {
+  root: true,
+  parser: "@typescript-eslint/parser",
+  plugins: ["@typescript-eslint"],
+  extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
+  rules: {
+    "@typescript-eslint/no-explicit-any": "off",
+    "@typescript-eslint/ban-ts-comment": "off",
+  },
+};

+ 49 - 0
target_chains/solana/sdk/js/pyth_solana_receiver/package.json

@@ -0,0 +1,49 @@
+{
+  "name": "@pythnetwork/pyth-solana-receiver",
+  "version": "0.1.0",
+  "description": "Pyth solana receiver SDK",
+  "homepage": "https://pyth.network",
+  "main": "lib/index.js",
+  "types": "lib/index.d.ts",
+  "files": [
+    "lib/**/*"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/pyth-network/pyth-crosschain.git",
+    "directory": "target_chains/solana/sdk/js"
+  },
+  "publishConfig": {
+    "access": "public"
+  },
+  "scripts": {
+    "build": "tsc",
+    "format": "prettier --write \"src/**/*.ts\"",
+    "lint": "eslint src/",
+    "prepublishOnly": "npm run build && npm test && npm run lint",
+    "preversion": "npm run lint",
+    "version": "npm run format && git add -A src"
+  },
+  "keywords": [
+    "pyth",
+    "oracle"
+  ],
+  "license": "Apache-2.0",
+  "devDependencies": {
+    "@types/jest": "^29.4.0",
+    "@typescript-eslint/eslint-plugin": "^5.20.0",
+    "@typescript-eslint/parser": "^5.20.0",
+    "eslint": "^8.13.0",
+    "jest": "^29.4.0",
+    "prettier": "^2.6.2",
+    "quicktype": "^23.0.76",
+    "ts-jest": "^29.0.5",
+    "typescript": "^4.6.3"
+  },
+  "dependencies": {
+    "@coral-xyz/anchor": "^0.29.0",
+    "@pythnetwork/price-service-sdk": "*",
+    "@pythnetwork/solana-utils": "*",
+    "@solana/web3.js": "^1.90.0"
+  }
+}

+ 319 - 0
target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts

@@ -0,0 +1,319 @@
+import { AnchorProvider, Program } from "@coral-xyz/anchor";
+import { Connection, Signer, VersionedTransaction } from "@solana/web3.js";
+import {
+  PythSolanaReceiver as PythSolanaReceiverProgram,
+  IDL as Idl,
+} from "./idl/pyth_solana_receiver";
+import {
+  WormholeCoreBridgeSolana,
+  IDL as WormholeCoreBridgeSolanaIdl,
+} from "./idl/wormhole_core_bridge_solana";
+import {
+  DEFAULT_RECEIVER_PROGRAM_ID,
+  DEFAULT_WORMHOLE_PROGRAM_ID,
+  getConfigPda,
+  getGuardianSetPda,
+  getTreasuryPda,
+} from "./address";
+import { PublicKey, Keypair } from "@solana/web3.js";
+import {
+  parseAccumulatorUpdateData,
+  parsePriceFeedMessage,
+} from "@pythnetwork/price-service-sdk";
+import {
+  POST_UPDATE_ATOMIC_COMPUTE_BUDGET,
+  POST_UPDATE_COMPUTE_BUDGET,
+  VERIFY_ENCODED_VAA_COMPUTE_BUDGET,
+} from "./compute_budget";
+import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider";
+import {
+  buildEncodedVaaCreateInstruction,
+  buildWriteEncodedVaaWithSplit,
+  getGuardianSetIndex,
+  trimSignatures,
+} from "./vaa";
+import {
+  TransactionBuilder,
+  InstructionWithEphemeralSigners,
+} from "@pythnetwork/solana-utils";
+import { PriorityFeeConfig } from "@pythnetwork/solana-utils/lib/transaction";
+
+export const DEFAULT_TREASURY_ID = 0;
+
+export class PythSolanaReceiver {
+  readonly connection: Connection;
+  readonly wallet: Wallet;
+  readonly provider: AnchorProvider;
+  readonly receiver: Program<PythSolanaReceiverProgram>;
+  readonly wormhole: Program<WormholeCoreBridgeSolana>;
+
+  constructor({
+    connection,
+    wallet,
+    wormholeProgramId = DEFAULT_WORMHOLE_PROGRAM_ID,
+    receiverProgramId = DEFAULT_RECEIVER_PROGRAM_ID,
+  }: {
+    connection: Connection;
+    wallet: Wallet;
+    wormholeProgramId?: PublicKey;
+    receiverProgramId?: PublicKey;
+  }) {
+    this.connection = connection;
+    this.wallet = wallet;
+    this.provider = new AnchorProvider(this.connection, this.wallet, {
+      commitment: connection.commitment,
+    });
+    this.receiver = new Program<PythSolanaReceiverProgram>(
+      Idl as PythSolanaReceiverProgram,
+      receiverProgramId,
+      this.provider
+    );
+    this.wormhole = new Program<WormholeCoreBridgeSolana>(
+      WormholeCoreBridgeSolanaIdl as WormholeCoreBridgeSolana,
+      wormholeProgramId,
+      this.provider
+    );
+  }
+
+  async withPriceUpdate(
+    priceUpdateDataArray: string[],
+    getInstructions: (
+      priceFeedIdToPriceUpdateAccount: Record<string, PublicKey>
+    ) => Promise<InstructionWithEphemeralSigners[]>,
+    priorityFeeConfig?: PriorityFeeConfig
+  ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> {
+    const {
+      postInstructions,
+      priceFeedIdToPriceUpdateAccount: priceFeedIdToPriceUpdateAccount,
+      cleanupInstructions,
+    } = await this.buildPostPriceUpdateInstructions(priceUpdateDataArray);
+    return TransactionBuilder.batchIntoVersionedTransactions(
+      this.wallet.publicKey,
+      this.connection,
+      [
+        ...postInstructions,
+        ...(await getInstructions(priceFeedIdToPriceUpdateAccount)),
+        ...cleanupInstructions,
+      ],
+      priorityFeeConfig ?? {}
+    );
+  }
+
+  async withPartiallyVerifiedPriceUpdate(
+    priceUpdateDataArray: string[],
+    getInstructions: (
+      priceFeedIdToPriceUpdateAccount: Record<string, PublicKey>
+    ) => Promise<InstructionWithEphemeralSigners[]>,
+    priorityFeeConfig?: PriorityFeeConfig
+  ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> {
+    const {
+      postInstructions,
+      priceFeedIdToPriceUpdateAccount,
+      cleanupInstructions,
+    } = await this.buildPostPriceUpdateAtomicInstructions(priceUpdateDataArray);
+    return TransactionBuilder.batchIntoVersionedTransactions(
+      this.wallet.publicKey,
+      this.connection,
+      [
+        ...postInstructions,
+        ...(await getInstructions(priceFeedIdToPriceUpdateAccount)),
+        ...cleanupInstructions,
+      ],
+      priorityFeeConfig ?? {}
+    );
+  }
+
+  async buildPostPriceUpdateAtomicInstructions(
+    priceUpdateDataArray: string[]
+  ): Promise<{
+    postInstructions: InstructionWithEphemeralSigners[];
+    priceFeedIdToPriceUpdateAccount: Record<string, PublicKey>;
+    cleanupInstructions: InstructionWithEphemeralSigners[];
+  }> {
+    const postInstructions: InstructionWithEphemeralSigners[] = [];
+    const priceFeedIdToPriceUpdateAccount: Record<string, PublicKey> = {};
+    const cleanupInstructions: InstructionWithEphemeralSigners[] = [];
+
+    for (const priceUpdateData of priceUpdateDataArray) {
+      const accumulatorUpdateData = parseAccumulatorUpdateData(
+        Buffer.from(priceUpdateData, "base64")
+      );
+      const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa);
+      const trimmedVaa = trimSignatures(accumulatorUpdateData.vaa);
+
+      for (const update of accumulatorUpdateData.updates) {
+        const priceUpdateKeypair = new Keypair();
+        postInstructions.push({
+          instruction: await this.receiver.methods
+            .postUpdateAtomic({
+              vaa: trimmedVaa,
+              merklePriceUpdate: update,
+              treasuryId: DEFAULT_TREASURY_ID,
+            })
+            .accounts({
+              priceUpdateAccount: priceUpdateKeypair.publicKey,
+              treasury: getTreasuryPda(DEFAULT_TREASURY_ID),
+              config: getConfigPda(),
+              guardianSet: getGuardianSetPda(guardianSetIndex),
+            })
+            .instruction(),
+          signers: [priceUpdateKeypair],
+          computeUnits: POST_UPDATE_ATOMIC_COMPUTE_BUDGET,
+        });
+        priceFeedIdToPriceUpdateAccount[
+          "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex")
+        ] = priceUpdateKeypair.publicKey;
+
+        cleanupInstructions.push(
+          await this.buildClosePriceUpdateInstruction(
+            priceUpdateKeypair.publicKey
+          )
+        );
+      }
+    }
+    return {
+      postInstructions,
+      priceFeedIdToPriceUpdateAccount,
+      cleanupInstructions,
+    };
+  }
+
+  async buildPostEncodedVaaInstructions(vaa: Buffer): Promise<{
+    postInstructions: InstructionWithEphemeralSigners[];
+    encodedVaaAddress: PublicKey;
+    cleanupInstructions: InstructionWithEphemeralSigners[];
+  }> {
+    const postInstructions: InstructionWithEphemeralSigners[] = [];
+    const cleanupInstructions: InstructionWithEphemeralSigners[] = [];
+    const encodedVaaKeypair = new Keypair();
+    const guardianSetIndex = getGuardianSetIndex(vaa);
+
+    postInstructions.push(
+      await buildEncodedVaaCreateInstruction(
+        this.wormhole,
+        vaa,
+        encodedVaaKeypair
+      )
+    );
+    postInstructions.push({
+      instruction: await this.wormhole.methods
+        .initEncodedVaa()
+        .accounts({
+          encodedVaa: encodedVaaKeypair.publicKey,
+        })
+        .instruction(),
+      signers: [],
+    });
+
+    postInstructions.push(
+      ...(await buildWriteEncodedVaaWithSplit(
+        this.wormhole,
+        vaa,
+        encodedVaaKeypair.publicKey
+      ))
+    );
+
+    postInstructions.push({
+      instruction: await this.wormhole.methods
+        .verifyEncodedVaaV1()
+        .accounts({
+          guardianSet: getGuardianSetPda(guardianSetIndex),
+          draftVaa: encodedVaaKeypair.publicKey,
+        })
+        .instruction(),
+      signers: [],
+      computeUnits: VERIFY_ENCODED_VAA_COMPUTE_BUDGET,
+    });
+
+    cleanupInstructions.push(
+      await this.buildCloseEncodedVaaInstruction(encodedVaaKeypair.publicKey)
+    );
+
+    return {
+      postInstructions,
+      encodedVaaAddress: encodedVaaKeypair.publicKey,
+      cleanupInstructions,
+    };
+  }
+
+  async buildPostPriceUpdateInstructions(
+    priceUpdateDataArray: string[]
+  ): Promise<{
+    postInstructions: InstructionWithEphemeralSigners[];
+    priceFeedIdToPriceUpdateAccount: Record<string, PublicKey>;
+    cleanupInstructions: InstructionWithEphemeralSigners[];
+  }> {
+    const postInstructions: InstructionWithEphemeralSigners[] = [];
+    const priceFeedIdToPriceUpdateAccount: Record<string, PublicKey> = {};
+    const cleanupInstructions: InstructionWithEphemeralSigners[] = [];
+
+    for (const priceUpdateData of priceUpdateDataArray) {
+      const accumulatorUpdateData = parseAccumulatorUpdateData(
+        Buffer.from(priceUpdateData, "base64")
+      );
+
+      const {
+        postInstructions: postEncodedVaaInstructions,
+        encodedVaaAddress: encodedVaa,
+        cleanupInstructions: postEncodedVaaCleanupInstructions,
+      } = await this.buildPostEncodedVaaInstructions(accumulatorUpdateData.vaa);
+      postInstructions.push(...postEncodedVaaInstructions);
+      cleanupInstructions.push(...postEncodedVaaCleanupInstructions);
+
+      for (const update of accumulatorUpdateData.updates) {
+        const priceUpdateKeypair = new Keypair();
+        postInstructions.push({
+          instruction: await this.receiver.methods
+            .postUpdate({
+              merklePriceUpdate: update,
+              treasuryId: DEFAULT_TREASURY_ID,
+            })
+            .accounts({
+              encodedVaa,
+              priceUpdateAccount: priceUpdateKeypair.publicKey,
+              treasury: getTreasuryPda(DEFAULT_TREASURY_ID),
+              config: getConfigPda(),
+            })
+            .instruction(),
+          signers: [priceUpdateKeypair],
+          computeUnits: POST_UPDATE_COMPUTE_BUDGET,
+        });
+
+        priceFeedIdToPriceUpdateAccount[
+          "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex")
+        ] = priceUpdateKeypair.publicKey;
+        cleanupInstructions.push(
+          await this.buildClosePriceUpdateInstruction(
+            priceUpdateKeypair.publicKey
+          )
+        );
+      }
+    }
+
+    return {
+      postInstructions,
+      priceFeedIdToPriceUpdateAccount,
+      cleanupInstructions,
+    };
+  }
+
+  async buildCloseEncodedVaaInstruction(
+    encodedVaa: PublicKey
+  ): Promise<InstructionWithEphemeralSigners> {
+    const instruction = await this.wormhole.methods
+      .closeEncodedVaa()
+      .accounts({ encodedVaa })
+      .instruction();
+    return { instruction, signers: [] };
+  }
+
+  async buildClosePriceUpdateInstruction(
+    priceUpdateAccount: PublicKey
+  ): Promise<InstructionWithEphemeralSigners> {
+    const instruction = await this.receiver.methods
+      .reclaimRent()
+      .accounts({ priceUpdateAccount })
+      .instruction();
+    return { instruction, signers: [] };
+  }
+}

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

@@ -0,0 +1,31 @@
+import { PublicKey } from "@solana/web3.js";
+
+export const DEFAULT_RECEIVER_PROGRAM_ID = new PublicKey(
+  "rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ"
+);
+export const DEFAULT_WORMHOLE_PROGRAM_ID = new PublicKey(
+  "HDwcJBJXjL9FpJ7UBsYBtaDjsBUhuLCUYoz3zr8SWWaQ"
+);
+
+export const getGuardianSetPda = (guardianSetIndex: number) => {
+  const guardianSetIndexBuf = Buffer.alloc(4);
+  guardianSetIndexBuf.writeUInt32BE(guardianSetIndex, 0);
+  return PublicKey.findProgramAddressSync(
+    [Buffer.from("GuardianSet"), guardianSetIndexBuf],
+    DEFAULT_WORMHOLE_PROGRAM_ID
+  )[0];
+};
+
+export const getTreasuryPda = (treasuryId: number) => {
+  return PublicKey.findProgramAddressSync(
+    [Buffer.from("treasury"), Buffer.from([treasuryId])],
+    DEFAULT_RECEIVER_PROGRAM_ID
+  )[0];
+};
+
+export const getConfigPda = () => {
+  return PublicKey.findProgramAddressSync(
+    [Buffer.from("config")],
+    DEFAULT_RECEIVER_PROGRAM_ID
+  )[0];
+};

+ 3 - 0
target_chains/solana/sdk/js/pyth_solana_receiver/src/compute_budget.ts

@@ -0,0 +1,3 @@
+export const VERIFY_ENCODED_VAA_COMPUTE_BUDGET = 400000;
+export const POST_UPDATE_ATOMIC_COMPUTE_BUDGET = 400000;
+export const POST_UPDATE_COMPUTE_BUDGET = 200000;

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

@@ -0,0 +1,1205 @@
+export type PythSolanaReceiver = {
+  version: "0.1.0";
+  name: "pyth_solana_receiver";
+  instructions: [
+    {
+      name: "initialize";
+      accounts: [
+        {
+          name: "payer";
+          isMut: true;
+          isSigner: true;
+        },
+        {
+          name: "config";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "systemProgram";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "initialConfig";
+          type: {
+            defined: "Config";
+          };
+        }
+      ];
+    },
+    {
+      name: "requestGovernanceAuthorityTransfer";
+      accounts: [
+        {
+          name: "payer";
+          isMut: false;
+          isSigner: true;
+        },
+        {
+          name: "config";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "targetGovernanceAuthority";
+          type: "publicKey";
+        }
+      ];
+    },
+    {
+      name: "acceptGovernanceAuthorityTransfer";
+      accounts: [
+        {
+          name: "payer";
+          isMut: false;
+          isSigner: true;
+        },
+        {
+          name: "config";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "setDataSources";
+      accounts: [
+        {
+          name: "payer";
+          isMut: false;
+          isSigner: true;
+        },
+        {
+          name: "config";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "validDataSources";
+          type: {
+            vec: {
+              defined: "DataSource";
+            };
+          };
+        }
+      ];
+    },
+    {
+      name: "setFee";
+      accounts: [
+        {
+          name: "payer";
+          isMut: false;
+          isSigner: true;
+        },
+        {
+          name: "config";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "singleUpdateFeeInLamports";
+          type: "u64";
+        }
+      ];
+    },
+    {
+      name: "setWormholeAddress";
+      accounts: [
+        {
+          name: "payer";
+          isMut: false;
+          isSigner: true;
+        },
+        {
+          name: "config";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "wormhole";
+          type: "publicKey";
+        }
+      ];
+    },
+    {
+      name: "setMinimumSignatures";
+      accounts: [
+        {
+          name: "payer";
+          isMut: false;
+          isSigner: true;
+        },
+        {
+          name: "config";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "minimumSignatures";
+          type: "u8";
+        }
+      ];
+    },
+    {
+      name: "postUpdateAtomic";
+      docs: [
+        "Post a price update using a VAA and a MerklePriceUpdate.",
+        "This function allows you to post a price update in a single transaction.",
+        "Compared to post_update, it is less secure since you won't be able to verify all guardian signatures if you use this function because of transaction size limitations.",
+        "Typically, you can fit 5 guardian signatures in a transaction that uses this."
+      ];
+      accounts: [
+        {
+          name: "payer";
+          isMut: true;
+          isSigner: true;
+        },
+        {
+          name: "guardianSet";
+          isMut: false;
+          isSigner: false;
+          docs: [
+            "Instead we do the same steps in deserialize_guardian_set_checked."
+          ];
+        },
+        {
+          name: "config";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "treasury";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "priceUpdateAccount";
+          isMut: true;
+          isSigner: true;
+          docs: [
+            "The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.",
+            "Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized"
+          ];
+        },
+        {
+          name: "systemProgram";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "params";
+          type: {
+            defined: "PostUpdateAtomicParams";
+          };
+        }
+      ];
+    },
+    {
+      name: "postUpdate";
+      docs: [
+        "Post a price update using an encoded_vaa account and a MerklePriceUpdate calldata.",
+        "This should be called after the client has already verified the Vaa via the Wormhole contract.",
+        "Check out target_chains/solana/cli/src/main.rs for an example of how to do this."
+      ];
+      accounts: [
+        {
+          name: "payer";
+          isMut: true;
+          isSigner: true;
+        },
+        {
+          name: "encodedVaa";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "config";
+          isMut: false;
+          isSigner: false;
+        },
+        {
+          name: "treasury";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "priceUpdateAccount";
+          isMut: true;
+          isSigner: true;
+          docs: [
+            "The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.",
+            "Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized"
+          ];
+        },
+        {
+          name: "systemProgram";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [
+        {
+          name: "params";
+          type: {
+            defined: "PostUpdateParams";
+          };
+        }
+      ];
+    },
+    {
+      name: "reclaimRent";
+      accounts: [
+        {
+          name: "payer";
+          isMut: true;
+          isSigner: true;
+        },
+        {
+          name: "priceUpdateAccount";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [];
+    }
+  ];
+  accounts: [
+    {
+      name: "Config";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "governanceAuthority";
+            type: "publicKey";
+          },
+          {
+            name: "targetGovernanceAuthority";
+            type: {
+              option: "publicKey";
+            };
+          },
+          {
+            name: "wormhole";
+            type: "publicKey";
+          },
+          {
+            name: "validDataSources";
+            type: {
+              vec: {
+                defined: "DataSource";
+              };
+            };
+          },
+          {
+            name: "singleUpdateFeeInLamports";
+            type: "u64";
+          },
+          {
+            name: "minimumSignatures";
+            type: "u8";
+          }
+        ];
+      };
+    },
+    {
+      name: "priceUpdateV1";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "writeAuthority";
+            type: "publicKey";
+          },
+          {
+            name: "verificationLevel";
+            type: {
+              defined: "VerificationLevel";
+            };
+          },
+          {
+            name: "priceMessage";
+            type: {
+              defined: "PriceFeedMessage";
+            };
+          }
+        ];
+      };
+    }
+  ];
+  types: [
+    {
+      name: "PriceFeedMessage";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "feedId";
+            type: {
+              array: ["u8", 32];
+            };
+          },
+          {
+            name: "price";
+            type: "i64";
+          },
+          {
+            name: "conf";
+            type: "u64";
+          },
+          {
+            name: "exponent";
+            type: "i32";
+          },
+          {
+            name: "publishTime";
+            type: "i64";
+          },
+          {
+            name: "prevPublishTime";
+            type: "i64";
+          },
+          {
+            name: "emaPrice";
+            type: "i64";
+          },
+          {
+            name: "emaConf";
+            type: "u64";
+          }
+        ];
+      };
+    },
+    {
+      name: "MerklePriceUpdate";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "message";
+            type: "bytes";
+          },
+          {
+            name: "proof";
+            type: {
+              vec: {
+                array: ["u8", 20];
+              };
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "DataSource";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "chain";
+            type: "u16";
+          },
+          {
+            name: "emitter";
+            type: "publicKey";
+          }
+        ];
+      };
+    },
+    {
+      name: "PostUpdateAtomicParams";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "vaa";
+            type: "bytes";
+          },
+          {
+            name: "merklePriceUpdate";
+            type: {
+              defined: "MerklePriceUpdate";
+            };
+          },
+          {
+            name: "treasuryId";
+            type: "u8";
+          }
+        ];
+      };
+    },
+    {
+      name: "PostUpdateParams";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "merklePriceUpdate";
+            type: {
+              defined: "MerklePriceUpdate";
+            };
+          },
+          {
+            name: "treasuryId";
+            type: "u8";
+          }
+        ];
+      };
+    },
+    {
+      name: "VerificationLevel";
+      docs: [
+        "* This enum represents how many guardian signatures were checked for a Pythnet price update\n * If full, guardian quorum has been attained\n * If partial, at least config.minimum signatures have been verified, but in the case config.minimum_signatures changes in the future we also include the number of signatures that were checked"
+      ];
+      type: {
+        kind: "enum";
+        variants: [
+          {
+            name: "Partial";
+            fields: [
+              {
+                name: "numSignatures";
+                type: "u8";
+              }
+            ];
+          },
+          {
+            name: "Full";
+          }
+        ];
+      };
+    }
+  ];
+  errors: [
+    {
+      code: 6000;
+      name: "InvalidWormholeMessage";
+      msg: "Received an invalid wormhole message";
+    },
+    {
+      code: 6001;
+      name: "DeserializeMessageFailed";
+      msg: "An error occurred when deserializing the message";
+    },
+    {
+      code: 6002;
+      name: "InvalidPriceUpdate";
+      msg: "Received an invalid price update";
+    },
+    {
+      code: 6003;
+      name: "UnsupportedMessageType";
+      msg: "This type of message is not supported currently";
+    },
+    {
+      code: 6004;
+      name: "InvalidDataSource";
+      msg: "The tuple emitter chain, emitter doesn't match one of the valid data sources.";
+    },
+    {
+      code: 6005;
+      name: "InsufficientFunds";
+      msg: "Funds are insufficient to pay the receiving fee";
+    },
+    {
+      code: 6006;
+      name: "WrongWriteAuthority";
+      msg: "This signer can't write to price update account";
+    },
+    {
+      code: 6007;
+      name: "WrongVaaOwner";
+      msg: "The posted VAA account has the wrong owner.";
+    },
+    {
+      code: 6008;
+      name: "DeserializeVaaFailed";
+      msg: "An error occurred when deserializing the VAA.";
+    },
+    {
+      code: 6009;
+      name: "InsufficientGuardianSignatures";
+      msg: "The number of guardian signatures is below the minimum";
+    },
+    {
+      code: 6010;
+      name: "InvalidVaaVersion";
+      msg: "Invalid VAA version";
+    },
+    {
+      code: 6011;
+      name: "GuardianSetMismatch";
+      msg: "Guardian set version in the VAA doesn't match the guardian set passed";
+    },
+    {
+      code: 6012;
+      name: "InvalidGuardianOrder";
+      msg: "Guardian signature indices must be increasing";
+    },
+    {
+      code: 6013;
+      name: "InvalidGuardianIndex";
+      msg: "Guardian index exceeds the number of guardians in the set";
+    },
+    {
+      code: 6014;
+      name: "InvalidSignature";
+      msg: "A VAA signature is invalid";
+    },
+    {
+      code: 6015;
+      name: "InvalidGuardianKeyRecovery";
+      msg: "The recovered guardian public key doesn't match the guardian set";
+    },
+    {
+      code: 6016;
+      name: "WrongGuardianSetOwner";
+      msg: "The guardian set account is owned by the wrong program";
+    },
+    {
+      code: 6017;
+      name: "InvalidGuardianSetPda";
+      msg: "The Guardian Set account doesn't match the PDA derivation";
+    },
+    {
+      code: 6018;
+      name: "GuardianSetExpired";
+      msg: "The Guardian Set is expired";
+    },
+    {
+      code: 6019;
+      name: "GovernanceAuthorityMismatch";
+      msg: "The signer is not authorized to perform this governance action";
+    },
+    {
+      code: 6020;
+      name: "TargetGovernanceAuthorityMismatch";
+      msg: "The signer is not authorized to accept the governance authority";
+    },
+    {
+      code: 6021;
+      name: "NonexistentGovernanceAuthorityTransferRequest";
+      msg: "The governance authority needs to request a transfer first";
+    }
+  ];
+};
+
+export const IDL: PythSolanaReceiver = {
+  version: "0.1.0",
+  name: "pyth_solana_receiver",
+  instructions: [
+    {
+      name: "initialize",
+      accounts: [
+        {
+          name: "payer",
+          isMut: true,
+          isSigner: true,
+        },
+        {
+          name: "config",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "systemProgram",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "initialConfig",
+          type: {
+            defined: "Config",
+          },
+        },
+      ],
+    },
+    {
+      name: "requestGovernanceAuthorityTransfer",
+      accounts: [
+        {
+          name: "payer",
+          isMut: false,
+          isSigner: true,
+        },
+        {
+          name: "config",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "targetGovernanceAuthority",
+          type: "publicKey",
+        },
+      ],
+    },
+    {
+      name: "acceptGovernanceAuthorityTransfer",
+      accounts: [
+        {
+          name: "payer",
+          isMut: false,
+          isSigner: true,
+        },
+        {
+          name: "config",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "setDataSources",
+      accounts: [
+        {
+          name: "payer",
+          isMut: false,
+          isSigner: true,
+        },
+        {
+          name: "config",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "validDataSources",
+          type: {
+            vec: {
+              defined: "DataSource",
+            },
+          },
+        },
+      ],
+    },
+    {
+      name: "setFee",
+      accounts: [
+        {
+          name: "payer",
+          isMut: false,
+          isSigner: true,
+        },
+        {
+          name: "config",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "singleUpdateFeeInLamports",
+          type: "u64",
+        },
+      ],
+    },
+    {
+      name: "setWormholeAddress",
+      accounts: [
+        {
+          name: "payer",
+          isMut: false,
+          isSigner: true,
+        },
+        {
+          name: "config",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "wormhole",
+          type: "publicKey",
+        },
+      ],
+    },
+    {
+      name: "setMinimumSignatures",
+      accounts: [
+        {
+          name: "payer",
+          isMut: false,
+          isSigner: true,
+        },
+        {
+          name: "config",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "minimumSignatures",
+          type: "u8",
+        },
+      ],
+    },
+    {
+      name: "postUpdateAtomic",
+      docs: [
+        "Post a price update using a VAA and a MerklePriceUpdate.",
+        "This function allows you to post a price update in a single transaction.",
+        "Compared to post_update, it is less secure since you won't be able to verify all guardian signatures if you use this function because of transaction size limitations.",
+        "Typically, you can fit 5 guardian signatures in a transaction that uses this.",
+      ],
+      accounts: [
+        {
+          name: "payer",
+          isMut: true,
+          isSigner: true,
+        },
+        {
+          name: "guardianSet",
+          isMut: false,
+          isSigner: false,
+          docs: [
+            "Instead we do the same steps in deserialize_guardian_set_checked.",
+          ],
+        },
+        {
+          name: "config",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "treasury",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "priceUpdateAccount",
+          isMut: true,
+          isSigner: true,
+          docs: [
+            "The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.",
+            "Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized",
+          ],
+        },
+        {
+          name: "systemProgram",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "params",
+          type: {
+            defined: "PostUpdateAtomicParams",
+          },
+        },
+      ],
+    },
+    {
+      name: "postUpdate",
+      docs: [
+        "Post a price update using an encoded_vaa account and a MerklePriceUpdate calldata.",
+        "This should be called after the client has already verified the Vaa via the Wormhole contract.",
+        "Check out target_chains/solana/cli/src/main.rs for an example of how to do this.",
+      ],
+      accounts: [
+        {
+          name: "payer",
+          isMut: true,
+          isSigner: true,
+        },
+        {
+          name: "encodedVaa",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "config",
+          isMut: false,
+          isSigner: false,
+        },
+        {
+          name: "treasury",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "priceUpdateAccount",
+          isMut: true,
+          isSigner: true,
+          docs: [
+            "The contraint is such that either the price_update_account is uninitialized or the payer is the write_authority.",
+            "Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized",
+          ],
+        },
+        {
+          name: "systemProgram",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [
+        {
+          name: "params",
+          type: {
+            defined: "PostUpdateParams",
+          },
+        },
+      ],
+    },
+    {
+      name: "reclaimRent",
+      accounts: [
+        {
+          name: "payer",
+          isMut: true,
+          isSigner: true,
+        },
+        {
+          name: "priceUpdateAccount",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [],
+    },
+  ],
+  accounts: [
+    {
+      name: "Config",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "governanceAuthority",
+            type: "publicKey",
+          },
+          {
+            name: "targetGovernanceAuthority",
+            type: {
+              option: "publicKey",
+            },
+          },
+          {
+            name: "wormhole",
+            type: "publicKey",
+          },
+          {
+            name: "validDataSources",
+            type: {
+              vec: {
+                defined: "DataSource",
+              },
+            },
+          },
+          {
+            name: "singleUpdateFeeInLamports",
+            type: "u64",
+          },
+          {
+            name: "minimumSignatures",
+            type: "u8",
+          },
+        ],
+      },
+    },
+    {
+      name: "priceUpdateV1",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "writeAuthority",
+            type: "publicKey",
+          },
+          {
+            name: "verificationLevel",
+            type: {
+              defined: "VerificationLevel",
+            },
+          },
+          {
+            name: "priceMessage",
+            type: {
+              defined: "PriceFeedMessage",
+            },
+          },
+        ],
+      },
+    },
+  ],
+  types: [
+    {
+      name: "PriceFeedMessage",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "feedId",
+            type: {
+              array: ["u8", 32],
+            },
+          },
+          {
+            name: "price",
+            type: "i64",
+          },
+          {
+            name: "conf",
+            type: "u64",
+          },
+          {
+            name: "exponent",
+            type: "i32",
+          },
+          {
+            name: "publishTime",
+            type: "i64",
+          },
+          {
+            name: "prevPublishTime",
+            type: "i64",
+          },
+          {
+            name: "emaPrice",
+            type: "i64",
+          },
+          {
+            name: "emaConf",
+            type: "u64",
+          },
+        ],
+      },
+    },
+    {
+      name: "MerklePriceUpdate",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "message",
+            type: "bytes",
+          },
+          {
+            name: "proof",
+            type: {
+              vec: {
+                array: ["u8", 20],
+              },
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "DataSource",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "chain",
+            type: "u16",
+          },
+          {
+            name: "emitter",
+            type: "publicKey",
+          },
+        ],
+      },
+    },
+    {
+      name: "PostUpdateAtomicParams",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "vaa",
+            type: "bytes",
+          },
+          {
+            name: "merklePriceUpdate",
+            type: {
+              defined: "MerklePriceUpdate",
+            },
+          },
+          {
+            name: "treasuryId",
+            type: "u8",
+          },
+        ],
+      },
+    },
+    {
+      name: "PostUpdateParams",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "merklePriceUpdate",
+            type: {
+              defined: "MerklePriceUpdate",
+            },
+          },
+          {
+            name: "treasuryId",
+            type: "u8",
+          },
+        ],
+      },
+    },
+    {
+      name: "VerificationLevel",
+      docs: [
+        "* This enum represents how many guardian signatures were checked for a Pythnet price update\n * If full, guardian quorum has been attained\n * If partial, at least config.minimum signatures have been verified, but in the case config.minimum_signatures changes in the future we also include the number of signatures that were checked",
+      ],
+      type: {
+        kind: "enum",
+        variants: [
+          {
+            name: "Partial",
+            fields: [
+              {
+                name: "numSignatures",
+                type: "u8",
+              },
+            ],
+          },
+          {
+            name: "Full",
+          },
+        ],
+      },
+    },
+  ],
+  errors: [
+    {
+      code: 6000,
+      name: "InvalidWormholeMessage",
+      msg: "Received an invalid wormhole message",
+    },
+    {
+      code: 6001,
+      name: "DeserializeMessageFailed",
+      msg: "An error occurred when deserializing the message",
+    },
+    {
+      code: 6002,
+      name: "InvalidPriceUpdate",
+      msg: "Received an invalid price update",
+    },
+    {
+      code: 6003,
+      name: "UnsupportedMessageType",
+      msg: "This type of message is not supported currently",
+    },
+    {
+      code: 6004,
+      name: "InvalidDataSource",
+      msg: "The tuple emitter chain, emitter doesn't match one of the valid data sources.",
+    },
+    {
+      code: 6005,
+      name: "InsufficientFunds",
+      msg: "Funds are insufficient to pay the receiving fee",
+    },
+    {
+      code: 6006,
+      name: "WrongWriteAuthority",
+      msg: "This signer can't write to price update account",
+    },
+    {
+      code: 6007,
+      name: "WrongVaaOwner",
+      msg: "The posted VAA account has the wrong owner.",
+    },
+    {
+      code: 6008,
+      name: "DeserializeVaaFailed",
+      msg: "An error occurred when deserializing the VAA.",
+    },
+    {
+      code: 6009,
+      name: "InsufficientGuardianSignatures",
+      msg: "The number of guardian signatures is below the minimum",
+    },
+    {
+      code: 6010,
+      name: "InvalidVaaVersion",
+      msg: "Invalid VAA version",
+    },
+    {
+      code: 6011,
+      name: "GuardianSetMismatch",
+      msg: "Guardian set version in the VAA doesn't match the guardian set passed",
+    },
+    {
+      code: 6012,
+      name: "InvalidGuardianOrder",
+      msg: "Guardian signature indices must be increasing",
+    },
+    {
+      code: 6013,
+      name: "InvalidGuardianIndex",
+      msg: "Guardian index exceeds the number of guardians in the set",
+    },
+    {
+      code: 6014,
+      name: "InvalidSignature",
+      msg: "A VAA signature is invalid",
+    },
+    {
+      code: 6015,
+      name: "InvalidGuardianKeyRecovery",
+      msg: "The recovered guardian public key doesn't match the guardian set",
+    },
+    {
+      code: 6016,
+      name: "WrongGuardianSetOwner",
+      msg: "The guardian set account is owned by the wrong program",
+    },
+    {
+      code: 6017,
+      name: "InvalidGuardianSetPda",
+      msg: "The Guardian Set account doesn't match the PDA derivation",
+    },
+    {
+      code: 6018,
+      name: "GuardianSetExpired",
+      msg: "The Guardian Set is expired",
+    },
+    {
+      code: 6019,
+      name: "GovernanceAuthorityMismatch",
+      msg: "The signer is not authorized to perform this governance action",
+    },
+    {
+      code: 6020,
+      name: "TargetGovernanceAuthorityMismatch",
+      msg: "The signer is not authorized to accept the governance authority",
+    },
+    {
+      code: 6021,
+      name: "NonexistentGovernanceAuthorityTransferRequest",
+      msg: "The governance authority needs to request a transfer first",
+    },
+  ],
+};

+ 3211 - 0
target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/wormhole_core_bridge_solana.ts

@@ -0,0 +1,3211 @@
+export type WormholeCoreBridgeSolana = {
+  version: "0.0.1-alpha.5";
+  name: "wormhole_core_bridge_solana";
+  constants: [
+    {
+      name: "SOLANA_CHAIN";
+      type: "u16";
+      value: "1";
+    },
+    {
+      name: "FEE_COLLECTOR_SEED_PREFIX";
+      type: "bytes";
+      value: "[102, 101, 101, 95, 99, 111, 108, 108, 101, 99, 116, 111, 114]";
+    },
+    {
+      name: "UPGRADE_SEED_PREFIX";
+      type: "bytes";
+      value: "[117, 112, 103, 114, 97, 100, 101]";
+    },
+    {
+      name: "PROGRAM_EMITTER_SEED_PREFIX";
+      type: "bytes";
+      value: "[101, 109, 105, 116, 116, 101, 114]";
+    },
+    {
+      name: "MAX_MESSAGE_PAYLOAD_SIZE";
+      type: {
+        defined: "usize";
+      };
+      value: "30 * 1_024";
+    }
+  ];
+  instructions: [
+    {
+      name: "initMessageV1";
+      docs: [
+        "Processor for initializing a new draft [PostedMessageV1](crate::state::PostedMessageV1)",
+        "account for writing. The emitter authority is established at this point and the payload size",
+        "is inferred from the size of the created account. This instruction handler also allows an",
+        "integrator to publish Wormhole messages using his program's ID as the emitter address",
+        "(by passing `Some(crate::ID)` to the [cpi_program_id](InitMessageV1Args::cpi_program_id)",
+        'argument). **Be aware that the emitter authority\'s seeds must only be \\[b"emitter"\\] in this',
+        "case.**",
+        "",
+        "This instruction should be followed up with `write_message_v1` and `finalize_message_v1` to",
+        "write and indicate that the message is ready for publishing respectively (to prepare it for",
+        "publishing via the",
+        "[post message instruction](crate::legacy::instruction::LegacyInstruction::PostMessage)).",
+        "",
+        "NOTE: If you wish to publish a small message (one where the data does not overflow the",
+        "Solana transaction size), it is recommended that you use an [sdk](crate::sdk::cpi) method to",
+        "either prepare your message or post a message as a program ID emitter."
+      ];
+      accounts: [
+        {
+          name: "emitterAuthority";
+          isMut: false;
+          isSigner: true;
+          docs: [
+            "This authority is the only one who can write to the draft message account."
+          ];
+        },
+        {
+          name: "draftMessage";
+          isMut: true;
+          isSigner: false;
+          docs: ["Bridge."];
+        }
+      ];
+      args: [
+        {
+          name: "args";
+          type: {
+            defined: "InitMessageV1Args";
+          };
+        }
+      ];
+    },
+    {
+      name: "writeMessageV1";
+      docs: [
+        "Processor used to write to a draft [PostedMessageV1](crate::state::PostedMessageV1) account.",
+        "This instruction requires an authority (the emitter authority) to interact with the message",
+        "account."
+      ];
+      accounts: [
+        {
+          name: "emitterAuthority";
+          isMut: false;
+          isSigner: true;
+        },
+        {
+          name: "draftMessage";
+          isMut: true;
+          isSigner: false;
+          docs: ["only be published when the message is finalized."];
+        }
+      ];
+      args: [
+        {
+          name: "args";
+          type: {
+            defined: "WriteMessageV1Args";
+          };
+        }
+      ];
+    },
+    {
+      name: "finalizeMessageV1";
+      docs: [
+        "Processor used to finalize a draft [PostedMessageV1](crate::state::PostedMessageV1) account.",
+        "Once finalized, this message account cannot be written to again. A finalized message is the",
+        "only state the legacy post message instruction can accept before publishing. This",
+        "instruction requires an authority (the emitter authority) to interact with the message",
+        "account."
+      ];
+      accounts: [
+        {
+          name: "emitterAuthority";
+          isMut: false;
+          isSigner: true;
+        },
+        {
+          name: "draftMessage";
+          isMut: true;
+          isSigner: false;
+          docs: ["only be published when the message is finalized."];
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "closeMessageV1";
+      docs: [
+        "Processor used to process a draft [PostedMessageV1](crate::state::PostedMessageV1) account.",
+        "This instruction requires an authority (the emitter authority) to interact with the message",
+        "account."
+      ];
+      accounts: [
+        {
+          name: "emitterAuthority";
+          isMut: false;
+          isSigner: true;
+        },
+        {
+          name: "draftMessage";
+          isMut: true;
+          isSigner: false;
+          docs: ["only be published when the message is finalized."];
+        },
+        {
+          name: "closeAccountDestination";
+          isMut: true;
+          isSigner: false;
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "initEncodedVaa";
+      docs: [
+        "Processor used to intialize a created account as [EncodedVaa](crate::state::EncodedVaa). An",
+        "authority (the write authority) is established with this instruction."
+      ];
+      accounts: [
+        {
+          name: "writeAuthority";
+          isMut: false;
+          isSigner: true;
+          docs: [
+            "The authority who can write to the VAA account when it is being processed."
+          ];
+        },
+        {
+          name: "encodedVaa";
+          isMut: true;
+          isSigner: false;
+          docs: ["Bridge."];
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "closeEncodedVaa";
+      docs: [
+        "Processor used to close an [EncodedVaa](crate::state::EncodedVaa). This instruction requires",
+        "an authority (the write authority) to interact witht he encoded VAA account."
+      ];
+      accounts: [
+        {
+          name: "writeAuthority";
+          isMut: true;
+          isSigner: true;
+          docs: [
+            "This account is only required to be mutable for the `CloseVaaAccount` directive. This",
+            "authority is the same signer that originally created the VAA accounts, so he is the one that",
+            "will receive the lamports back for the closed accounts."
+          ];
+        },
+        {
+          name: "encodedVaa";
+          isMut: true;
+          isSigner: false;
+          docs: ["written to and then verified."];
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "writeEncodedVaa";
+      docs: [
+        "Processor used to write to an [EncodedVaa](crate::state::EncodedVaa) account. This",
+        "instruction requires an authority (the write authority) to interact with the encoded VAA",
+        "account."
+      ];
+      accounts: [
+        {
+          name: "writeAuthority";
+          isMut: false;
+          isSigner: true;
+          docs: [
+            "The only authority that can write to the encoded VAA account."
+          ];
+        },
+        {
+          name: "draftVaa";
+          isMut: true;
+          isSigner: false;
+          docs: ["written to and then verified."];
+        }
+      ];
+      args: [
+        {
+          name: "args";
+          type: {
+            defined: "WriteEncodedVaaArgs";
+          };
+        }
+      ];
+    },
+    {
+      name: "verifyEncodedVaaV1";
+      docs: [
+        "Processor used to verify an [EncodedVaa](crate::state::EncodedVaa) account as a version 1",
+        "VAA (guardian signatures attesting to this observation). This instruction requires an",
+        "authority (the write authority) to interact with the encoded VAA account."
+      ];
+      accounts: [
+        {
+          name: "writeAuthority";
+          isMut: false;
+          isSigner: true;
+        },
+        {
+          name: "draftVaa";
+          isMut: true;
+          isSigner: false;
+          docs: ["written to and then verified."];
+        },
+        {
+          name: "guardianSet";
+          isMut: false;
+          isSigner: false;
+          docs: [
+            "Guardian set account, which should be the same one that was used to attest for the VAA. The",
+            "signatures in the encoded VAA are verified against this guardian set."
+          ];
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "postVaaV1";
+      docs: [
+        "Processor used to close an [EncodedVaa](crate::state::EncodedVaa) account to create a",
+        "[PostedMessageV1](crate::state::PostedMessageV1) account in its place.",
+        "",
+        "NOTE: Because the legacy verify signatures instruction was not required for the Posted VAA",
+        "account to exist, the encoded [SignatureSet](crate::state::SignatureSet) is the default",
+        "[Pubkey]."
+      ];
+      accounts: [
+        {
+          name: "payer";
+          isMut: true;
+          isSigner: true;
+          docs: [
+            "Payer to create the posted VAA account. This instruction allows anyone with an encoded VAA",
+            "to create a posted VAA account."
+          ];
+        },
+        {
+          name: "encodedVaa";
+          isMut: false;
+          isSigner: false;
+          docs: [
+            "Encoded VAA, whose body will be serialized into the posted VAA account.",
+            "",
+            "NOTE: This instruction handler only exists to support integrators that still rely on posted",
+            "VAA accounts. While we encourage integrators to use the encoded VAA account instead, we",
+            "allow a pathway to convert the encoded VAA into a posted VAA. However, the payload is",
+            "restricted to 9.5KB, which is much larger than what was possible with the old implementation",
+            "using the legacy post vaa instruction. The Core Bridge program will not support posting VAAs",
+            "larger than this payload size."
+          ];
+        },
+        {
+          name: "postedVaa";
+          isMut: true;
+          isSigner: false;
+        },
+        {
+          name: "systemProgram";
+          isMut: false;
+          isSigner: false;
+        }
+      ];
+      args: [];
+    },
+    {
+      name: "closeSignatureSet";
+      docs: [
+        "Processor used to close a [SignatureSet](crate::state::SignatureSet), which was used to",
+        "verify the VAA using the legacy parse and verify procedure."
+      ];
+      accounts: [
+        {
+          name: "solDestination";
+          isMut: true;
+          isSigner: true;
+        },
+        {
+          name: "postedVaa";
+          isMut: false;
+          isSigner: false;
+          docs: ["Posted VAA."];
+        },
+        {
+          name: "signatureSet";
+          isMut: true;
+          isSigner: false;
+          docs: [
+            "Signature set that may have been used to create the posted VAA account. If the `post_vaa_v1`",
+            "instruction were used to create the posted VAA account, then the encoded signature set",
+            "pubkey would be all zeroes."
+          ];
+        }
+      ];
+      args: [];
+    }
+  ];
+  accounts: [
+    {
+      name: "guardianSet";
+      docs: [
+        "Account used to store a guardian set. The keys encoded in this account are Ethereum pubkeys.",
+        "Its expiration time is determined at the time a guardian set is updated to a new set, where the",
+        "current network clock time is used with",
+        "[guardian_set_ttl](crate::state::Config::guardian_set_ttl).",
+        "",
+        "NOTE: The account schema is the same as legacy guardian sets, but this account now has a",
+        "discriminator generated by Anchor's [account] macro. When the Core Bridge program performs a",
+        "guardian set update with this implementation, guardian sets will now have this Anchor-generated",
+        "discriminator."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "index";
+            docs: [
+              "Index representing an incrementing version number for this guardian set."
+            ];
+            type: "u32";
+          },
+          {
+            name: "keys";
+            docs: ["Ethereum-style public keys."];
+            type: {
+              vec: {
+                array: ["u8", 20];
+              };
+            };
+          },
+          {
+            name: "creationTime";
+            docs: [
+              "Timestamp representing the time this guardian became active."
+            ];
+            type: {
+              defined: "Timestamp";
+            };
+          },
+          {
+            name: "expirationTime";
+            docs: [
+              "Expiration time when VAAs issued by this set are no longer valid."
+            ];
+            type: {
+              defined: "Timestamp";
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "signatureSet";
+      docs: [
+        "Account used to store information about a guardian set used to sign a VAA. There is only one",
+        "signature set for each verified VAA (associated with a",
+        "[PostedVaaV1](crate::legacy::state::PostedVaaV1) account). This account is created using the",
+        "verify signatures legacy instruction.",
+        "",
+        "NOTE: The account schema is the same as legacy signature sets, but this account now has a",
+        "discriminator generated by Anchor's [account] macro. When the Core Bridge program upgrades to",
+        "this implementation from the old one, integrators in the middle of verifying signatures will",
+        "have to use a new keypair for this account and try again."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "sigVerifySuccesses";
+            docs: ["Signatures of validators"];
+            type: {
+              vec: "bool";
+            };
+          },
+          {
+            name: "messageHash";
+            docs: ["Hash of the VAA message body."];
+            type: {
+              defined: "MessageHash";
+            };
+          },
+          {
+            name: "guardianSetIndex";
+            docs: ["Index of the guardian set"];
+            type: "u32";
+          }
+        ];
+      };
+    },
+    {
+      name: "encodedVaa";
+      docs: [
+        "Account used to warehouse VAA buffer.",
+        "",
+        "NOTE: This account should not be used by an external application unless the header's status is",
+        "`Verified`. It is encouraged to use the `EncodedVaa` zero-copy account struct instead."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "header";
+            docs: ["Status, write authority and VAA version."];
+            type: {
+              defined: "Header";
+            };
+          },
+          {
+            name: "buf";
+            docs: ["VAA buffer."];
+            type: "bytes";
+          }
+        ];
+      };
+    }
+  ];
+  types: [
+    {
+      name: "InitializeArgs";
+      docs: ["Arguments used to initialize the Core Bridge program."];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "guardianSetTtlSeconds";
+            type: "u32";
+          },
+          {
+            name: "feeLamports";
+            type: "u64";
+          },
+          {
+            name: "initialGuardians";
+            type: {
+              vec: {
+                array: ["u8", 20];
+              };
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "PostMessageArgs";
+      docs: [
+        "Arguments used to post a new Wormhole (Core Bridge) message either using",
+        "[post_message](crate::legacy::instruction::post_message) or",
+        "[post_message_unreliable](crate::legacy::instruction::post_message_unreliable)."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "nonce";
+            docs: ["Unique id for this message."];
+            type: "u32";
+          },
+          {
+            name: "payload";
+            docs: ["Encoded message."];
+            type: "bytes";
+          },
+          {
+            name: "commitment";
+            docs: ["Solana commitment level for Guardian observation."];
+            type: {
+              defined: "Commitment";
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "PostVaaArgs";
+      docs: [
+        "Arguments to post new VAA data after signature verification.",
+        "",
+        "NOTE: It is preferred to use the new process of verifying a VAA using the new Core Bridge Anchor",
+        "instructions. See [init_encoded_vaa](crate::wormhole_core_bridge_solana::init_encoded_vaa) and",
+        "[write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa) for more info."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "gap0";
+            docs: ["Unused data."];
+            type: {
+              array: ["u8", 5];
+            };
+          },
+          {
+            name: "timestamp";
+            docs: ["Time the message was submitted."];
+            type: "u32";
+          },
+          {
+            name: "nonce";
+            docs: ["Unique ID for this message."];
+            type: "u32";
+          },
+          {
+            name: "emitterChain";
+            docs: [
+              "The Wormhole chain ID denoting the origin of this message."
+            ];
+            type: "u16";
+          },
+          {
+            name: "emitterAddress";
+            docs: ["Emitter of the message."];
+            type: {
+              array: ["u8", 32];
+            };
+          },
+          {
+            name: "sequence";
+            docs: ["Sequence number of this message."];
+            type: "u64";
+          },
+          {
+            name: "consistencyLevel";
+            docs: ["Level of consistency requested by the emitter."];
+            type: "u8";
+          },
+          {
+            name: "payload";
+            docs: ["Message payload."];
+            type: "bytes";
+          }
+        ];
+      };
+    },
+    {
+      name: "VerifySignaturesArgs";
+      docs: [
+        "Arguments to verify specific guardian indices.",
+        "",
+        "NOTE: It is preferred to use the new process of verifying a VAA using the new Core Bridge Anchor",
+        "instructions. See [init_encoded_vaa](crate::wormhole_core_bridge_solana::init_encoded_vaa) and",
+        "[write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa) for more info."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "signerIndices";
+            docs: [
+              "Indices of verified guardian signatures, where -1 indicates a missing value. There is a",
+              "missing value if the guardian at this index is not expected to have its signature verfied by",
+              "the Sig Verify native program in the instruction invoked prior).",
+              "",
+              "NOTE: In the legacy implementation, this argument being a fixed-sized array of 19 only",
+              "allows the first 19 guardians of any size guardian set to be verified. Because of this, it",
+              "is absolutely important to use the new process of verifying a VAA."
+            ];
+            type: {
+              array: ["i8", 19];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "EmptyArgs";
+      docs: ["Unit struct used to represent an empty instruction argument."];
+      type: {
+        kind: "struct";
+        fields: [];
+      };
+    },
+    {
+      name: "Config";
+      docs: [
+        "Account used to store the current configuration of the bridge, including tracking Wormhole fee",
+        "payments. For governance decrees, the guardian set index is used to determine whether a decree",
+        "was attested for using the latest guardian set."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "guardianSetIndex";
+            docs: [
+              "The current guardian set index, used to decide which signature sets to accept."
+            ];
+            type: "u32";
+          },
+          {
+            name: "gap0";
+            docs: [
+              "Gap. In the old implementation, this was an amount that kept track of message fees that",
+              "were paid to the program's fee collector."
+            ];
+            type: {
+              array: ["u8", 8];
+            };
+          },
+          {
+            name: "guardianSetTtl";
+            docs: [
+              "Period for how long a guardian set is valid after it has been replaced by a new one.  This",
+              "guarantees that VAAs issued by that set can still be submitted for a certain period.  In",
+              "this period we still trust the old guardian set."
+            ];
+            type: {
+              defined: "Duration";
+            };
+          },
+          {
+            name: "feeLamports";
+            docs: [
+              "Amount of lamports that needs to be paid to the protocol to post a message"
+            ];
+            type: "u64";
+          }
+        ];
+      };
+    },
+    {
+      name: "LegacyEmitterSequence";
+      docs: [
+        "Account used to store the current sequence number for a given emitter."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "value";
+            docs: [
+              "Current sequence number, which will be used the next time this emitter publishes a message."
+            ];
+            type: "u64";
+          }
+        ];
+      };
+    },
+    {
+      name: "EmitterSequence";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "legacy";
+            type: {
+              defined: "LegacyEmitterSequence";
+            };
+          },
+          {
+            name: "bump";
+            type: "u8";
+          },
+          {
+            name: "emitterType";
+            type: {
+              defined: "EmitterType";
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "PostedMessageV1Unreliable";
+      docs: ["Account used to store a published (reusable) Wormhole message."];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "data";
+            type: {
+              defined: "PostedMessageV1Data";
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "PostedMessageV1Info";
+      docs: [
+        "Message metadata defining information about a published Wormhole message."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "consistencyLevel";
+            docs: ["Level of consistency requested by the emitter."];
+            type: "u8";
+          },
+          {
+            name: "emitterAuthority";
+            docs: [
+              "Authority used to write the message. This field is set to default when the message is",
+              "posted."
+            ];
+            type: "publicKey";
+          },
+          {
+            name: "status";
+            docs: [
+              "If a message is being written to, this status is used to determine which state this",
+              "account is in (e.g. [MessageStatus::Writing] indicates that the emitter authority is still",
+              "writing its message to this account). When this message is posted, this value will be",
+              "set to [MessageStatus::Published]."
+            ];
+            type: {
+              defined: "MessageStatus";
+            };
+          },
+          {
+            name: "gap0";
+            docs: ["No data is stored here."];
+            type: {
+              array: ["u8", 3];
+            };
+          },
+          {
+            name: "postedTimestamp";
+            docs: ["Time the posted message was created."];
+            type: {
+              defined: "Timestamp";
+            };
+          },
+          {
+            name: "nonce";
+            docs: ["Unique id for this message."];
+            type: "u32";
+          },
+          {
+            name: "sequence";
+            docs: ["Sequence number of this message."];
+            type: "u64";
+          },
+          {
+            name: "solanaChainId";
+            docs: [
+              "Always `1`.",
+              "",
+              "NOTE: Saving this value is silly, but we are keeping it to be consistent with how the posted",
+              "message account is written."
+            ];
+            type: {
+              defined: "ChainIdSolanaOnly";
+            };
+          },
+          {
+            name: "emitter";
+            docs: [
+              "Emitter of the message. This may either be the emitter authority or a program ID."
+            ];
+            type: "publicKey";
+          }
+        ];
+      };
+    },
+    {
+      name: "PostedMessageV1Data";
+      docs: [
+        "Underlying data for either [PostedMessageV1](crate::legacy::state::PostedMessageV1) or",
+        "[PostedMessageV1Unreliable](crate::legacy::state::PostedMessageV1Unreliable)."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "info";
+            docs: ["Message metadata."];
+            type: {
+              defined: "PostedMessageV1Info";
+            };
+          },
+          {
+            name: "payload";
+            docs: ["Encoded message."];
+            type: "bytes";
+          }
+        ];
+      };
+    },
+    {
+      name: "PostedMessageV1";
+      docs: [
+        "Account used to store a published Wormhole message.",
+        "",
+        "NOTE: If your integration requires reusable message accounts, please see",
+        "[PostedMessageV1Unreliable](crate::legacy::state::PostedMessageV1Unreliable)."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "data";
+            docs: ["Message data."];
+            type: {
+              defined: "PostedMessageV1Data";
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "PostedVaaV1Info";
+      docs: [
+        "VAA metadata defining information about a Wormhole message attested for by an active guardian",
+        "set."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "consistencyLevel";
+            docs: ["Level of consistency requested by the emitter."];
+            type: "u8";
+          },
+          {
+            name: "timestamp";
+            docs: ["Time the message was submitted."];
+            type: {
+              defined: "Timestamp";
+            };
+          },
+          {
+            name: "signatureSet";
+            docs: [
+              "Pubkey of [SignatureSet](crate::state::SignatureSet) account that represents this VAA's",
+              "signature verification."
+            ];
+            type: "publicKey";
+          },
+          {
+            name: "guardianSetIndex";
+            docs: [
+              "Guardian set index used to verify signatures for [SignatureSet](crate::state::SignatureSet).",
+              "",
+              'NOTE: In the previous implementation, this member was referred to as the "posted timestamp",',
+              "which is zero for VAA data (posted messages and VAAs resemble the same account schema). By",
+              "changing this to the guardian set index, we patch a bug with verifying governance VAAs for",
+              "the Core Bridge (other Core Bridge implementations require that the guardian set that",
+              "attested for the governance VAA is the current one)."
+            ];
+            type: "u32";
+          },
+          {
+            name: "nonce";
+            docs: ["Unique ID for this message."];
+            type: "u32";
+          },
+          {
+            name: "sequence";
+            docs: ["Sequence number of this message."];
+            type: "u64";
+          },
+          {
+            name: "emitterChain";
+            docs: [
+              "The Wormhole chain ID denoting the origin of this message."
+            ];
+            type: "u16";
+          },
+          {
+            name: "emitterAddress";
+            docs: ["Emitter of the message."];
+            type: {
+              array: ["u8", 32];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "PostedVaaV1";
+      docs: ["Account used to store a verified VAA."];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "info";
+            docs: ["VAA metadata."];
+            type: {
+              defined: "PostedVaaV1Info";
+            };
+          },
+          {
+            name: "payload";
+            docs: ["Message payload."];
+            type: "bytes";
+          }
+        ];
+      };
+    },
+    {
+      name: "WriteEncodedVaaArgs";
+      docs: [
+        "Arguments for the [write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa)",
+        "instruction."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "index";
+            docs: ["Index of VAA buffer."];
+            type: "u32";
+          },
+          {
+            name: "data";
+            docs: [
+              "Data representing subset of VAA buffer starting at specified index."
+            ];
+            type: "bytes";
+          }
+        ];
+      };
+    },
+    {
+      name: "InitMessageV1Args";
+      docs: [
+        "Arguments for the [init_message_v1](crate::wormhole_core_bridge_solana::init_message_v1)",
+        "instruction."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "nonce";
+            docs: ["Unique id for this message."];
+            type: "u32";
+          },
+          {
+            name: "commitment";
+            docs: ["Solana commitment level for Guardian observation."];
+            type: {
+              defined: "Commitment";
+            };
+          },
+          {
+            name: "cpiProgramId";
+            docs: [
+              "Optional program ID if the emitter address will be your program ID.",
+              "",
+              'NOTE: If `Some(program_id)`, your emitter authority seeds to be \\[b"emitter\\].'
+            ];
+            type: {
+              option: "publicKey";
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "WriteMessageV1Args";
+      docs: [
+        "Arguments for the [write_message_v1](crate::wormhole_core_bridge_solana::write_message_v1)",
+        "instruction."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "index";
+            docs: ["Index of message buffer."];
+            type: "u32";
+          },
+          {
+            name: "data";
+            docs: [
+              "Data representing subset of message buffer starting at specified index."
+            ];
+            type: "bytes";
+          }
+        ];
+      };
+    },
+    {
+      name: "Header";
+      docs: ["`EncodedVaa` account header."];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "status";
+            docs: [
+              "Processing status. **This encoded VAA is only considered usable when this status is set",
+              "to [Verified](ProcessingStatus::Verified).**"
+            ];
+            type: {
+              defined: "ProcessingStatus";
+            };
+          },
+          {
+            name: "writeAuthority";
+            docs: ["The authority that has write privilege to this account."];
+            type: "publicKey";
+          },
+          {
+            name: "version";
+            docs: [
+              "VAA version. Only when the VAA is verified is this version set to a value."
+            ];
+            type: "u8";
+          }
+        ];
+      };
+    },
+    {
+      name: "Timestamp";
+      docs: [
+        "This struct defines unix timestamp as u32 (as opposed to more modern systems that have adopted",
+        "i64). Methods for this struct are meant to convert Solana's clock type to this type assuming we",
+        "are far from year 2038."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "value";
+            type: "u32";
+          }
+        ];
+      };
+    },
+    {
+      name: "Duration";
+      docs: [
+        "To be used with the [Timestamp] type, this struct defines a duration in seconds."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "seconds";
+            type: "u32";
+          }
+        ];
+      };
+    },
+    {
+      name: "MessageHash";
+      docs: ["This type is used to represent a message hash (keccak)."];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "bytes";
+            type: {
+              array: ["u8", 32];
+            };
+          }
+        ];
+      };
+    },
+    {
+      name: "ChainIdSolanaOnly";
+      docs: [
+        "This type is kind of silly. But because [PostedMessageV1](crate::state::PostedMessageV1) has the",
+        "emitter chain ID as a field, which is unnecessary since it is always Solana's chain ID, we use",
+        "this type to guarantee that the encoded chain ID is always `1`."
+      ];
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "chainId";
+            type: "u16";
+          }
+        ];
+      };
+    },
+    {
+      name: "EmitterInfo";
+      type: {
+        kind: "struct";
+        fields: [
+          {
+            name: "chain";
+            type: "u16";
+          },
+          {
+            name: "address";
+            type: {
+              array: ["u8", 32];
+            };
+          },
+          {
+            name: "sequence";
+            type: "u64";
+          }
+        ];
+      };
+    },
+    {
+      name: "LegacyInstruction";
+      docs: [
+        "Legacy instruction selector.",
+        "",
+        "NOTE: No more instructions should be added to this enum. Instead, add them as Anchor instruction",
+        "handlers, which will inevitably live in",
+        "[wormhole_core_bridge_solana](crate::wormhole_core_bridge_solana)."
+      ];
+      type: {
+        kind: "enum";
+        variants: [
+          {
+            name: "Initialize";
+          },
+          {
+            name: "PostMessage";
+          },
+          {
+            name: "PostVaa";
+          },
+          {
+            name: "SetMessageFee";
+          },
+          {
+            name: "TransferFees";
+          },
+          {
+            name: "UpgradeContract";
+          },
+          {
+            name: "GuardianSetUpdate";
+          },
+          {
+            name: "VerifySignatures";
+          },
+          {
+            name: "PostMessageUnreliable";
+          }
+        ];
+      };
+    },
+    {
+      name: "EmitterType";
+      type: {
+        kind: "enum";
+        variants: [
+          {
+            name: "Unset";
+          },
+          {
+            name: "Legacy";
+          },
+          {
+            name: "Executable";
+          }
+        ];
+      };
+    },
+    {
+      name: "MessageStatus";
+      docs: [
+        "Status of a message. When a message is posted, its status is",
+        "[Published](MessageStatus::Published)."
+      ];
+      type: {
+        kind: "enum";
+        variants: [
+          {
+            name: "Published";
+          },
+          {
+            name: "Writing";
+          },
+          {
+            name: "ReadyForPublishing";
+          }
+        ];
+      };
+    },
+    {
+      name: "PublishMessageDirective";
+      docs: ["Directive used to determine how to post a Core Bridge message."];
+      type: {
+        kind: "enum";
+        variants: [
+          {
+            name: "Message";
+            fields: [
+              {
+                name: "nonce";
+                type: "u32";
+              },
+              {
+                name: "payload";
+                type: "bytes";
+              },
+              {
+                name: "commitment";
+                type: {
+                  defined: "Commitment";
+                };
+              }
+            ];
+          },
+          {
+            name: "ProgramMessage";
+            fields: [
+              {
+                name: "programId";
+                type: "publicKey";
+              },
+              {
+                name: "nonce";
+                type: "u32";
+              },
+              {
+                name: "payload";
+                type: "bytes";
+              },
+              {
+                name: "commitment";
+                type: {
+                  defined: "Commitment";
+                };
+              }
+            ];
+          },
+          {
+            name: "PreparedMessage";
+          }
+        ];
+      };
+    },
+    {
+      name: "ProcessingStatus";
+      docs: ["Encoded VAA's processing status."];
+      type: {
+        kind: "enum";
+        variants: [
+          {
+            name: "Unset";
+          },
+          {
+            name: "Writing";
+          },
+          {
+            name: "Verified";
+          }
+        ];
+      };
+    },
+    {
+      name: "Commitment";
+      docs: [
+        "Representation of Solana's commitment levels. This enum is not exhaustive because Wormhole only",
+        "considers these two commitment levels in its Guardian observation.",
+        "",
+        "See <https://docs.solana.com/cluster/commitments> for more info."
+      ];
+      type: {
+        kind: "enum";
+        variants: [
+          {
+            name: "Confirmed";
+          },
+          {
+            name: "Finalized";
+          }
+        ];
+      };
+    }
+  ];
+  errors: [
+    {
+      code: 6002;
+      name: "InvalidInstructionArgument";
+      msg: "InvalidInstructionArgument";
+    },
+    {
+      code: 6003;
+      name: "AccountNotZeroed";
+      msg: "AccountNotZeroed";
+    },
+    {
+      code: 6004;
+      name: "InvalidDataConversion";
+      msg: "InvalidDataConversion";
+    },
+    {
+      code: 6006;
+      name: "U64Overflow";
+      msg: "U64Overflow";
+    },
+    {
+      code: 6008;
+      name: "InvalidComputeSize";
+      msg: "InvalidComputeSize";
+    },
+    {
+      code: 6016;
+      name: "InvalidChain";
+      msg: "InvalidChain";
+    },
+    {
+      code: 6032;
+      name: "InvalidGovernanceEmitter";
+      msg: "InvalidGovernanceEmitter";
+    },
+    {
+      code: 6034;
+      name: "InvalidGovernanceAction";
+      msg: "InvalidGovernanceAction";
+    },
+    {
+      code: 6036;
+      name: "LatestGuardianSetRequired";
+      msg: "LatestGuardianSetRequired";
+    },
+    {
+      code: 6038;
+      name: "GovernanceForAnotherChain";
+      msg: "GovernanceForAnotherChain";
+    },
+    {
+      code: 6040;
+      name: "InvalidGovernanceVaa";
+      msg: "InvalidGovernanceVaa";
+    },
+    {
+      code: 6256;
+      name: "InsufficientFees";
+      msg: "InsufficientFees";
+    },
+    {
+      code: 6258;
+      name: "EmitterMismatch";
+      msg: "EmitterMismatch";
+    },
+    {
+      code: 6260;
+      name: "NotReadyForPublishing";
+      msg: "NotReadyForPublishing";
+    },
+    {
+      code: 6262;
+      name: "InvalidPreparedMessage";
+      msg: "InvalidPreparedMessage";
+    },
+    {
+      code: 6264;
+      name: "ExecutableEmitter";
+      msg: "ExecutableEmitter";
+    },
+    {
+      code: 6266;
+      name: "LegacyEmitter";
+      msg: "LegacyEmitter";
+    },
+    {
+      code: 6512;
+      name: "InvalidSignatureSet";
+      msg: "InvalidSignatureSet";
+    },
+    {
+      code: 6514;
+      name: "InvalidMessageHash";
+      msg: "InvalidMessageHash";
+    },
+    {
+      code: 6515;
+      name: "NoQuorum";
+      msg: "NoQuorum";
+    },
+    {
+      code: 6516;
+      name: "MessageMismatch";
+      msg: "MessageMismatch";
+    },
+    {
+      code: 7024;
+      name: "NotEnoughLamports";
+      msg: "NotEnoughLamports";
+    },
+    {
+      code: 7026;
+      name: "InvalidFeeRecipient";
+      msg: "InvalidFeeRecipient";
+    },
+    {
+      code: 7280;
+      name: "ImplementationMismatch";
+      msg: "ImplementationMismatch";
+    },
+    {
+      code: 7536;
+      name: "InvalidGuardianSetIndex";
+      msg: "InvalidGuardianSetIndex";
+    },
+    {
+      code: 7792;
+      name: "GuardianSetMismatch";
+      msg: "GuardianSetMismatch";
+    },
+    {
+      code: 7794;
+      name: "InstructionAtWrongIndex";
+      msg: "InstructionAtWrongIndex";
+    },
+    {
+      code: 7795;
+      name: "EmptySigVerifyInstruction";
+      msg: "EmptySigVerifyInstruction";
+    },
+    {
+      code: 7796;
+      name: "InvalidSigVerifyInstruction";
+      msg: "InvalidSigVerifyInstruction";
+    },
+    {
+      code: 7798;
+      name: "GuardianSetExpired";
+      msg: "GuardianSetExpired";
+    },
+    {
+      code: 7800;
+      name: "InvalidGuardianKeyRecovery";
+      msg: "InvalidGuardianKeyRecovery";
+    },
+    {
+      code: 7802;
+      name: "SignerIndicesMismatch";
+      msg: "SignerIndicesMismatch";
+    },
+    {
+      code: 8048;
+      name: "PayloadSizeMismatch";
+      msg: "PayloadSizeMismatch";
+    },
+    {
+      code: 10112;
+      name: "ZeroGuardians";
+      msg: "ZeroGuardians";
+    },
+    {
+      code: 10128;
+      name: "GuardianZeroAddress";
+      msg: "GuardianZeroAddress";
+    },
+    {
+      code: 10144;
+      name: "DuplicateGuardianAddress";
+      msg: "DuplicateGuardianAddress";
+    },
+    {
+      code: 10160;
+      name: "MessageAlreadyPublished";
+      msg: "MessageAlreadyPublished";
+    },
+    {
+      code: 10176;
+      name: "VaaWritingDisallowed";
+      msg: "VaaWritingDisallowed";
+    },
+    {
+      code: 10192;
+      name: "VaaAlreadyVerified";
+      msg: "VaaAlreadyVerified";
+    },
+    {
+      code: 10208;
+      name: "InvalidGuardianIndex";
+      msg: "InvalidGuardianIndex";
+    },
+    {
+      code: 10224;
+      name: "InvalidSignature";
+      msg: "InvalidSignature";
+    },
+    {
+      code: 10256;
+      name: "UnverifiedVaa";
+      msg: "UnverifiedVaa";
+    },
+    {
+      code: 10258;
+      name: "VaaStillProcessing";
+      msg: "VaaStillProcessing";
+    },
+    {
+      code: 10260;
+      name: "InWritingStatus";
+      msg: "InWritingStatus";
+    },
+    {
+      code: 10262;
+      name: "NotInWritingStatus";
+      msg: "NotInWritingStatus";
+    },
+    {
+      code: 10264;
+      name: "InvalidMessageStatus";
+      msg: "InvalidMessageStatus";
+    },
+    {
+      code: 10266;
+      name: "HashNotComputed";
+      msg: "HashNotComputed";
+    },
+    {
+      code: 10268;
+      name: "InvalidVaaVersion";
+      msg: "InvalidVaaVersion";
+    },
+    {
+      code: 10270;
+      name: "InvalidCreatedAccountSize";
+      msg: "InvalidCreatedAccountSize";
+    },
+    {
+      code: 10272;
+      name: "DataOverflow";
+      msg: "DataOverflow";
+    },
+    {
+      code: 10274;
+      name: "ExceedsMaxPayloadSize";
+      msg: "ExceedsMaxPayloadSize (30KB)";
+    },
+    {
+      code: 10276;
+      name: "CannotParseVaa";
+      msg: "CannotParseVaa";
+    },
+    {
+      code: 10278;
+      name: "EmitterAuthorityMismatch";
+      msg: "EmitterAuthorityMismatch";
+    },
+    {
+      code: 10280;
+      name: "InvalidProgramEmitter";
+      msg: "InvalidProgramEmitter";
+    },
+    {
+      code: 10282;
+      name: "WriteAuthorityMismatch";
+      msg: "WriteAuthorityMismatch";
+    },
+    {
+      code: 10284;
+      name: "PostedVaaPayloadTooLarge";
+      msg: "PostedVaaPayloadTooLarge";
+    },
+    {
+      code: 10286;
+      name: "ExecutableDisallowed";
+      msg: "ExecutableDisallowed";
+    }
+  ];
+};
+
+export const IDL: WormholeCoreBridgeSolana = {
+  version: "0.0.1-alpha.5",
+  name: "wormhole_core_bridge_solana",
+  constants: [
+    {
+      name: "SOLANA_CHAIN",
+      type: "u16",
+      value: "1",
+    },
+    {
+      name: "FEE_COLLECTOR_SEED_PREFIX",
+      type: "bytes",
+      value: "[102, 101, 101, 95, 99, 111, 108, 108, 101, 99, 116, 111, 114]",
+    },
+    {
+      name: "UPGRADE_SEED_PREFIX",
+      type: "bytes",
+      value: "[117, 112, 103, 114, 97, 100, 101]",
+    },
+    {
+      name: "PROGRAM_EMITTER_SEED_PREFIX",
+      type: "bytes",
+      value: "[101, 109, 105, 116, 116, 101, 114]",
+    },
+    {
+      name: "MAX_MESSAGE_PAYLOAD_SIZE",
+      type: {
+        defined: "usize",
+      },
+      value: "30 * 1_024",
+    },
+  ],
+  instructions: [
+    {
+      name: "initMessageV1",
+      docs: [
+        "Processor for initializing a new draft [PostedMessageV1](crate::state::PostedMessageV1)",
+        "account for writing. The emitter authority is established at this point and the payload size",
+        "is inferred from the size of the created account. This instruction handler also allows an",
+        "integrator to publish Wormhole messages using his program's ID as the emitter address",
+        "(by passing `Some(crate::ID)` to the [cpi_program_id](InitMessageV1Args::cpi_program_id)",
+        'argument). **Be aware that the emitter authority\'s seeds must only be \\[b"emitter"\\] in this',
+        "case.**",
+        "",
+        "This instruction should be followed up with `write_message_v1` and `finalize_message_v1` to",
+        "write and indicate that the message is ready for publishing respectively (to prepare it for",
+        "publishing via the",
+        "[post message instruction](crate::legacy::instruction::LegacyInstruction::PostMessage)).",
+        "",
+        "NOTE: If you wish to publish a small message (one where the data does not overflow the",
+        "Solana transaction size), it is recommended that you use an [sdk](crate::sdk::cpi) method to",
+        "either prepare your message or post a message as a program ID emitter.",
+      ],
+      accounts: [
+        {
+          name: "emitterAuthority",
+          isMut: false,
+          isSigner: true,
+          docs: [
+            "This authority is the only one who can write to the draft message account.",
+          ],
+        },
+        {
+          name: "draftMessage",
+          isMut: true,
+          isSigner: false,
+          docs: ["Bridge."],
+        },
+      ],
+      args: [
+        {
+          name: "args",
+          type: {
+            defined: "InitMessageV1Args",
+          },
+        },
+      ],
+    },
+    {
+      name: "writeMessageV1",
+      docs: [
+        "Processor used to write to a draft [PostedMessageV1](crate::state::PostedMessageV1) account.",
+        "This instruction requires an authority (the emitter authority) to interact with the message",
+        "account.",
+      ],
+      accounts: [
+        {
+          name: "emitterAuthority",
+          isMut: false,
+          isSigner: true,
+        },
+        {
+          name: "draftMessage",
+          isMut: true,
+          isSigner: false,
+          docs: ["only be published when the message is finalized."],
+        },
+      ],
+      args: [
+        {
+          name: "args",
+          type: {
+            defined: "WriteMessageV1Args",
+          },
+        },
+      ],
+    },
+    {
+      name: "finalizeMessageV1",
+      docs: [
+        "Processor used to finalize a draft [PostedMessageV1](crate::state::PostedMessageV1) account.",
+        "Once finalized, this message account cannot be written to again. A finalized message is the",
+        "only state the legacy post message instruction can accept before publishing. This",
+        "instruction requires an authority (the emitter authority) to interact with the message",
+        "account.",
+      ],
+      accounts: [
+        {
+          name: "emitterAuthority",
+          isMut: false,
+          isSigner: true,
+        },
+        {
+          name: "draftMessage",
+          isMut: true,
+          isSigner: false,
+          docs: ["only be published when the message is finalized."],
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "closeMessageV1",
+      docs: [
+        "Processor used to process a draft [PostedMessageV1](crate::state::PostedMessageV1) account.",
+        "This instruction requires an authority (the emitter authority) to interact with the message",
+        "account.",
+      ],
+      accounts: [
+        {
+          name: "emitterAuthority",
+          isMut: false,
+          isSigner: true,
+        },
+        {
+          name: "draftMessage",
+          isMut: true,
+          isSigner: false,
+          docs: ["only be published when the message is finalized."],
+        },
+        {
+          name: "closeAccountDestination",
+          isMut: true,
+          isSigner: false,
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "initEncodedVaa",
+      docs: [
+        "Processor used to intialize a created account as [EncodedVaa](crate::state::EncodedVaa). An",
+        "authority (the write authority) is established with this instruction.",
+      ],
+      accounts: [
+        {
+          name: "writeAuthority",
+          isMut: false,
+          isSigner: true,
+          docs: [
+            "The authority who can write to the VAA account when it is being processed.",
+          ],
+        },
+        {
+          name: "encodedVaa",
+          isMut: true,
+          isSigner: false,
+          docs: ["Bridge."],
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "closeEncodedVaa",
+      docs: [
+        "Processor used to close an [EncodedVaa](crate::state::EncodedVaa). This instruction requires",
+        "an authority (the write authority) to interact witht he encoded VAA account.",
+      ],
+      accounts: [
+        {
+          name: "writeAuthority",
+          isMut: true,
+          isSigner: true,
+          docs: [
+            "This account is only required to be mutable for the `CloseVaaAccount` directive. This",
+            "authority is the same signer that originally created the VAA accounts, so he is the one that",
+            "will receive the lamports back for the closed accounts.",
+          ],
+        },
+        {
+          name: "encodedVaa",
+          isMut: true,
+          isSigner: false,
+          docs: ["written to and then verified."],
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "writeEncodedVaa",
+      docs: [
+        "Processor used to write to an [EncodedVaa](crate::state::EncodedVaa) account. This",
+        "instruction requires an authority (the write authority) to interact with the encoded VAA",
+        "account.",
+      ],
+      accounts: [
+        {
+          name: "writeAuthority",
+          isMut: false,
+          isSigner: true,
+          docs: [
+            "The only authority that can write to the encoded VAA account.",
+          ],
+        },
+        {
+          name: "draftVaa",
+          isMut: true,
+          isSigner: false,
+          docs: ["written to and then verified."],
+        },
+      ],
+      args: [
+        {
+          name: "args",
+          type: {
+            defined: "WriteEncodedVaaArgs",
+          },
+        },
+      ],
+    },
+    {
+      name: "verifyEncodedVaaV1",
+      docs: [
+        "Processor used to verify an [EncodedVaa](crate::state::EncodedVaa) account as a version 1",
+        "VAA (guardian signatures attesting to this observation). This instruction requires an",
+        "authority (the write authority) to interact with the encoded VAA account.",
+      ],
+      accounts: [
+        {
+          name: "writeAuthority",
+          isMut: false,
+          isSigner: true,
+        },
+        {
+          name: "draftVaa",
+          isMut: true,
+          isSigner: false,
+          docs: ["written to and then verified."],
+        },
+        {
+          name: "guardianSet",
+          isMut: false,
+          isSigner: false,
+          docs: [
+            "Guardian set account, which should be the same one that was used to attest for the VAA. The",
+            "signatures in the encoded VAA are verified against this guardian set.",
+          ],
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "postVaaV1",
+      docs: [
+        "Processor used to close an [EncodedVaa](crate::state::EncodedVaa) account to create a",
+        "[PostedMessageV1](crate::state::PostedMessageV1) account in its place.",
+        "",
+        "NOTE: Because the legacy verify signatures instruction was not required for the Posted VAA",
+        "account to exist, the encoded [SignatureSet](crate::state::SignatureSet) is the default",
+        "[Pubkey].",
+      ],
+      accounts: [
+        {
+          name: "payer",
+          isMut: true,
+          isSigner: true,
+          docs: [
+            "Payer to create the posted VAA account. This instruction allows anyone with an encoded VAA",
+            "to create a posted VAA account.",
+          ],
+        },
+        {
+          name: "encodedVaa",
+          isMut: false,
+          isSigner: false,
+          docs: [
+            "Encoded VAA, whose body will be serialized into the posted VAA account.",
+            "",
+            "NOTE: This instruction handler only exists to support integrators that still rely on posted",
+            "VAA accounts. While we encourage integrators to use the encoded VAA account instead, we",
+            "allow a pathway to convert the encoded VAA into a posted VAA. However, the payload is",
+            "restricted to 9.5KB, which is much larger than what was possible with the old implementation",
+            "using the legacy post vaa instruction. The Core Bridge program will not support posting VAAs",
+            "larger than this payload size.",
+          ],
+        },
+        {
+          name: "postedVaa",
+          isMut: true,
+          isSigner: false,
+        },
+        {
+          name: "systemProgram",
+          isMut: false,
+          isSigner: false,
+        },
+      ],
+      args: [],
+    },
+    {
+      name: "closeSignatureSet",
+      docs: [
+        "Processor used to close a [SignatureSet](crate::state::SignatureSet), which was used to",
+        "verify the VAA using the legacy parse and verify procedure.",
+      ],
+      accounts: [
+        {
+          name: "solDestination",
+          isMut: true,
+          isSigner: true,
+        },
+        {
+          name: "postedVaa",
+          isMut: false,
+          isSigner: false,
+          docs: ["Posted VAA."],
+        },
+        {
+          name: "signatureSet",
+          isMut: true,
+          isSigner: false,
+          docs: [
+            "Signature set that may have been used to create the posted VAA account. If the `post_vaa_v1`",
+            "instruction were used to create the posted VAA account, then the encoded signature set",
+            "pubkey would be all zeroes.",
+          ],
+        },
+      ],
+      args: [],
+    },
+  ],
+  accounts: [
+    {
+      name: "guardianSet",
+      docs: [
+        "Account used to store a guardian set. The keys encoded in this account are Ethereum pubkeys.",
+        "Its expiration time is determined at the time a guardian set is updated to a new set, where the",
+        "current network clock time is used with",
+        "[guardian_set_ttl](crate::state::Config::guardian_set_ttl).",
+        "",
+        "NOTE: The account schema is the same as legacy guardian sets, but this account now has a",
+        "discriminator generated by Anchor's [account] macro. When the Core Bridge program performs a",
+        "guardian set update with this implementation, guardian sets will now have this Anchor-generated",
+        "discriminator.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "index",
+            docs: [
+              "Index representing an incrementing version number for this guardian set.",
+            ],
+            type: "u32",
+          },
+          {
+            name: "keys",
+            docs: ["Ethereum-style public keys."],
+            type: {
+              vec: {
+                array: ["u8", 20],
+              },
+            },
+          },
+          {
+            name: "creationTime",
+            docs: [
+              "Timestamp representing the time this guardian became active.",
+            ],
+            type: {
+              defined: "Timestamp",
+            },
+          },
+          {
+            name: "expirationTime",
+            docs: [
+              "Expiration time when VAAs issued by this set are no longer valid.",
+            ],
+            type: {
+              defined: "Timestamp",
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "signatureSet",
+      docs: [
+        "Account used to store information about a guardian set used to sign a VAA. There is only one",
+        "signature set for each verified VAA (associated with a",
+        "[PostedVaaV1](crate::legacy::state::PostedVaaV1) account). This account is created using the",
+        "verify signatures legacy instruction.",
+        "",
+        "NOTE: The account schema is the same as legacy signature sets, but this account now has a",
+        "discriminator generated by Anchor's [account] macro. When the Core Bridge program upgrades to",
+        "this implementation from the old one, integrators in the middle of verifying signatures will",
+        "have to use a new keypair for this account and try again.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "sigVerifySuccesses",
+            docs: ["Signatures of validators"],
+            type: {
+              vec: "bool",
+            },
+          },
+          {
+            name: "messageHash",
+            docs: ["Hash of the VAA message body."],
+            type: {
+              defined: "MessageHash",
+            },
+          },
+          {
+            name: "guardianSetIndex",
+            docs: ["Index of the guardian set"],
+            type: "u32",
+          },
+        ],
+      },
+    },
+    {
+      name: "encodedVaa",
+      docs: [
+        "Account used to warehouse VAA buffer.",
+        "",
+        "NOTE: This account should not be used by an external application unless the header's status is",
+        "`Verified`. It is encouraged to use the `EncodedVaa` zero-copy account struct instead.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "header",
+            docs: ["Status, write authority and VAA version."],
+            type: {
+              defined: "Header",
+            },
+          },
+          {
+            name: "buf",
+            docs: ["VAA buffer."],
+            type: "bytes",
+          },
+        ],
+      },
+    },
+  ],
+  types: [
+    {
+      name: "InitializeArgs",
+      docs: ["Arguments used to initialize the Core Bridge program."],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "guardianSetTtlSeconds",
+            type: "u32",
+          },
+          {
+            name: "feeLamports",
+            type: "u64",
+          },
+          {
+            name: "initialGuardians",
+            type: {
+              vec: {
+                array: ["u8", 20],
+              },
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "PostMessageArgs",
+      docs: [
+        "Arguments used to post a new Wormhole (Core Bridge) message either using",
+        "[post_message](crate::legacy::instruction::post_message) or",
+        "[post_message_unreliable](crate::legacy::instruction::post_message_unreliable).",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "nonce",
+            docs: ["Unique id for this message."],
+            type: "u32",
+          },
+          {
+            name: "payload",
+            docs: ["Encoded message."],
+            type: "bytes",
+          },
+          {
+            name: "commitment",
+            docs: ["Solana commitment level for Guardian observation."],
+            type: {
+              defined: "Commitment",
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "PostVaaArgs",
+      docs: [
+        "Arguments to post new VAA data after signature verification.",
+        "",
+        "NOTE: It is preferred to use the new process of verifying a VAA using the new Core Bridge Anchor",
+        "instructions. See [init_encoded_vaa](crate::wormhole_core_bridge_solana::init_encoded_vaa) and",
+        "[write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa) for more info.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "gap0",
+            docs: ["Unused data."],
+            type: {
+              array: ["u8", 5],
+            },
+          },
+          {
+            name: "timestamp",
+            docs: ["Time the message was submitted."],
+            type: "u32",
+          },
+          {
+            name: "nonce",
+            docs: ["Unique ID for this message."],
+            type: "u32",
+          },
+          {
+            name: "emitterChain",
+            docs: [
+              "The Wormhole chain ID denoting the origin of this message.",
+            ],
+            type: "u16",
+          },
+          {
+            name: "emitterAddress",
+            docs: ["Emitter of the message."],
+            type: {
+              array: ["u8", 32],
+            },
+          },
+          {
+            name: "sequence",
+            docs: ["Sequence number of this message."],
+            type: "u64",
+          },
+          {
+            name: "consistencyLevel",
+            docs: ["Level of consistency requested by the emitter."],
+            type: "u8",
+          },
+          {
+            name: "payload",
+            docs: ["Message payload."],
+            type: "bytes",
+          },
+        ],
+      },
+    },
+    {
+      name: "VerifySignaturesArgs",
+      docs: [
+        "Arguments to verify specific guardian indices.",
+        "",
+        "NOTE: It is preferred to use the new process of verifying a VAA using the new Core Bridge Anchor",
+        "instructions. See [init_encoded_vaa](crate::wormhole_core_bridge_solana::init_encoded_vaa) and",
+        "[write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa) for more info.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "signerIndices",
+            docs: [
+              "Indices of verified guardian signatures, where -1 indicates a missing value. There is a",
+              "missing value if the guardian at this index is not expected to have its signature verfied by",
+              "the Sig Verify native program in the instruction invoked prior).",
+              "",
+              "NOTE: In the legacy implementation, this argument being a fixed-sized array of 19 only",
+              "allows the first 19 guardians of any size guardian set to be verified. Because of this, it",
+              "is absolutely important to use the new process of verifying a VAA.",
+            ],
+            type: {
+              array: ["i8", 19],
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "EmptyArgs",
+      docs: ["Unit struct used to represent an empty instruction argument."],
+      type: {
+        kind: "struct",
+        fields: [],
+      },
+    },
+    {
+      name: "Config",
+      docs: [
+        "Account used to store the current configuration of the bridge, including tracking Wormhole fee",
+        "payments. For governance decrees, the guardian set index is used to determine whether a decree",
+        "was attested for using the latest guardian set.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "guardianSetIndex",
+            docs: [
+              "The current guardian set index, used to decide which signature sets to accept.",
+            ],
+            type: "u32",
+          },
+          {
+            name: "gap0",
+            docs: [
+              "Gap. In the old implementation, this was an amount that kept track of message fees that",
+              "were paid to the program's fee collector.",
+            ],
+            type: {
+              array: ["u8", 8],
+            },
+          },
+          {
+            name: "guardianSetTtl",
+            docs: [
+              "Period for how long a guardian set is valid after it has been replaced by a new one.  This",
+              "guarantees that VAAs issued by that set can still be submitted for a certain period.  In",
+              "this period we still trust the old guardian set.",
+            ],
+            type: {
+              defined: "Duration",
+            },
+          },
+          {
+            name: "feeLamports",
+            docs: [
+              "Amount of lamports that needs to be paid to the protocol to post a message",
+            ],
+            type: "u64",
+          },
+        ],
+      },
+    },
+    {
+      name: "LegacyEmitterSequence",
+      docs: [
+        "Account used to store the current sequence number for a given emitter.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "value",
+            docs: [
+              "Current sequence number, which will be used the next time this emitter publishes a message.",
+            ],
+            type: "u64",
+          },
+        ],
+      },
+    },
+    {
+      name: "EmitterSequence",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "legacy",
+            type: {
+              defined: "LegacyEmitterSequence",
+            },
+          },
+          {
+            name: "bump",
+            type: "u8",
+          },
+          {
+            name: "emitterType",
+            type: {
+              defined: "EmitterType",
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "PostedMessageV1Unreliable",
+      docs: ["Account used to store a published (reusable) Wormhole message."],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "data",
+            type: {
+              defined: "PostedMessageV1Data",
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "PostedMessageV1Info",
+      docs: [
+        "Message metadata defining information about a published Wormhole message.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "consistencyLevel",
+            docs: ["Level of consistency requested by the emitter."],
+            type: "u8",
+          },
+          {
+            name: "emitterAuthority",
+            docs: [
+              "Authority used to write the message. This field is set to default when the message is",
+              "posted.",
+            ],
+            type: "publicKey",
+          },
+          {
+            name: "status",
+            docs: [
+              "If a message is being written to, this status is used to determine which state this",
+              "account is in (e.g. [MessageStatus::Writing] indicates that the emitter authority is still",
+              "writing its message to this account). When this message is posted, this value will be",
+              "set to [MessageStatus::Published].",
+            ],
+            type: {
+              defined: "MessageStatus",
+            },
+          },
+          {
+            name: "gap0",
+            docs: ["No data is stored here."],
+            type: {
+              array: ["u8", 3],
+            },
+          },
+          {
+            name: "postedTimestamp",
+            docs: ["Time the posted message was created."],
+            type: {
+              defined: "Timestamp",
+            },
+          },
+          {
+            name: "nonce",
+            docs: ["Unique id for this message."],
+            type: "u32",
+          },
+          {
+            name: "sequence",
+            docs: ["Sequence number of this message."],
+            type: "u64",
+          },
+          {
+            name: "solanaChainId",
+            docs: [
+              "Always `1`.",
+              "",
+              "NOTE: Saving this value is silly, but we are keeping it to be consistent with how the posted",
+              "message account is written.",
+            ],
+            type: {
+              defined: "ChainIdSolanaOnly",
+            },
+          },
+          {
+            name: "emitter",
+            docs: [
+              "Emitter of the message. This may either be the emitter authority or a program ID.",
+            ],
+            type: "publicKey",
+          },
+        ],
+      },
+    },
+    {
+      name: "PostedMessageV1Data",
+      docs: [
+        "Underlying data for either [PostedMessageV1](crate::legacy::state::PostedMessageV1) or",
+        "[PostedMessageV1Unreliable](crate::legacy::state::PostedMessageV1Unreliable).",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "info",
+            docs: ["Message metadata."],
+            type: {
+              defined: "PostedMessageV1Info",
+            },
+          },
+          {
+            name: "payload",
+            docs: ["Encoded message."],
+            type: "bytes",
+          },
+        ],
+      },
+    },
+    {
+      name: "PostedMessageV1",
+      docs: [
+        "Account used to store a published Wormhole message.",
+        "",
+        "NOTE: If your integration requires reusable message accounts, please see",
+        "[PostedMessageV1Unreliable](crate::legacy::state::PostedMessageV1Unreliable).",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "data",
+            docs: ["Message data."],
+            type: {
+              defined: "PostedMessageV1Data",
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "PostedVaaV1Info",
+      docs: [
+        "VAA metadata defining information about a Wormhole message attested for by an active guardian",
+        "set.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "consistencyLevel",
+            docs: ["Level of consistency requested by the emitter."],
+            type: "u8",
+          },
+          {
+            name: "timestamp",
+            docs: ["Time the message was submitted."],
+            type: {
+              defined: "Timestamp",
+            },
+          },
+          {
+            name: "signatureSet",
+            docs: [
+              "Pubkey of [SignatureSet](crate::state::SignatureSet) account that represents this VAA's",
+              "signature verification.",
+            ],
+            type: "publicKey",
+          },
+          {
+            name: "guardianSetIndex",
+            docs: [
+              "Guardian set index used to verify signatures for [SignatureSet](crate::state::SignatureSet).",
+              "",
+              'NOTE: In the previous implementation, this member was referred to as the "posted timestamp",',
+              "which is zero for VAA data (posted messages and VAAs resemble the same account schema). By",
+              "changing this to the guardian set index, we patch a bug with verifying governance VAAs for",
+              "the Core Bridge (other Core Bridge implementations require that the guardian set that",
+              "attested for the governance VAA is the current one).",
+            ],
+            type: "u32",
+          },
+          {
+            name: "nonce",
+            docs: ["Unique ID for this message."],
+            type: "u32",
+          },
+          {
+            name: "sequence",
+            docs: ["Sequence number of this message."],
+            type: "u64",
+          },
+          {
+            name: "emitterChain",
+            docs: [
+              "The Wormhole chain ID denoting the origin of this message.",
+            ],
+            type: "u16",
+          },
+          {
+            name: "emitterAddress",
+            docs: ["Emitter of the message."],
+            type: {
+              array: ["u8", 32],
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "PostedVaaV1",
+      docs: ["Account used to store a verified VAA."],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "info",
+            docs: ["VAA metadata."],
+            type: {
+              defined: "PostedVaaV1Info",
+            },
+          },
+          {
+            name: "payload",
+            docs: ["Message payload."],
+            type: "bytes",
+          },
+        ],
+      },
+    },
+    {
+      name: "WriteEncodedVaaArgs",
+      docs: [
+        "Arguments for the [write_encoded_vaa](crate::wormhole_core_bridge_solana::write_encoded_vaa)",
+        "instruction.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "index",
+            docs: ["Index of VAA buffer."],
+            type: "u32",
+          },
+          {
+            name: "data",
+            docs: [
+              "Data representing subset of VAA buffer starting at specified index.",
+            ],
+            type: "bytes",
+          },
+        ],
+      },
+    },
+    {
+      name: "InitMessageV1Args",
+      docs: [
+        "Arguments for the [init_message_v1](crate::wormhole_core_bridge_solana::init_message_v1)",
+        "instruction.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "nonce",
+            docs: ["Unique id for this message."],
+            type: "u32",
+          },
+          {
+            name: "commitment",
+            docs: ["Solana commitment level for Guardian observation."],
+            type: {
+              defined: "Commitment",
+            },
+          },
+          {
+            name: "cpiProgramId",
+            docs: [
+              "Optional program ID if the emitter address will be your program ID.",
+              "",
+              'NOTE: If `Some(program_id)`, your emitter authority seeds to be \\[b"emitter\\].',
+            ],
+            type: {
+              option: "publicKey",
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "WriteMessageV1Args",
+      docs: [
+        "Arguments for the [write_message_v1](crate::wormhole_core_bridge_solana::write_message_v1)",
+        "instruction.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "index",
+            docs: ["Index of message buffer."],
+            type: "u32",
+          },
+          {
+            name: "data",
+            docs: [
+              "Data representing subset of message buffer starting at specified index.",
+            ],
+            type: "bytes",
+          },
+        ],
+      },
+    },
+    {
+      name: "Header",
+      docs: ["`EncodedVaa` account header."],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "status",
+            docs: [
+              "Processing status. **This encoded VAA is only considered usable when this status is set",
+              "to [Verified](ProcessingStatus::Verified).**",
+            ],
+            type: {
+              defined: "ProcessingStatus",
+            },
+          },
+          {
+            name: "writeAuthority",
+            docs: ["The authority that has write privilege to this account."],
+            type: "publicKey",
+          },
+          {
+            name: "version",
+            docs: [
+              "VAA version. Only when the VAA is verified is this version set to a value.",
+            ],
+            type: "u8",
+          },
+        ],
+      },
+    },
+    {
+      name: "Timestamp",
+      docs: [
+        "This struct defines unix timestamp as u32 (as opposed to more modern systems that have adopted",
+        "i64). Methods for this struct are meant to convert Solana's clock type to this type assuming we",
+        "are far from year 2038.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "value",
+            type: "u32",
+          },
+        ],
+      },
+    },
+    {
+      name: "Duration",
+      docs: [
+        "To be used with the [Timestamp] type, this struct defines a duration in seconds.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "seconds",
+            type: "u32",
+          },
+        ],
+      },
+    },
+    {
+      name: "MessageHash",
+      docs: ["This type is used to represent a message hash (keccak)."],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "bytes",
+            type: {
+              array: ["u8", 32],
+            },
+          },
+        ],
+      },
+    },
+    {
+      name: "ChainIdSolanaOnly",
+      docs: [
+        "This type is kind of silly. But because [PostedMessageV1](crate::state::PostedMessageV1) has the",
+        "emitter chain ID as a field, which is unnecessary since it is always Solana's chain ID, we use",
+        "this type to guarantee that the encoded chain ID is always `1`.",
+      ],
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "chainId",
+            type: "u16",
+          },
+        ],
+      },
+    },
+    {
+      name: "EmitterInfo",
+      type: {
+        kind: "struct",
+        fields: [
+          {
+            name: "chain",
+            type: "u16",
+          },
+          {
+            name: "address",
+            type: {
+              array: ["u8", 32],
+            },
+          },
+          {
+            name: "sequence",
+            type: "u64",
+          },
+        ],
+      },
+    },
+    {
+      name: "LegacyInstruction",
+      docs: [
+        "Legacy instruction selector.",
+        "",
+        "NOTE: No more instructions should be added to this enum. Instead, add them as Anchor instruction",
+        "handlers, which will inevitably live in",
+        "[wormhole_core_bridge_solana](crate::wormhole_core_bridge_solana).",
+      ],
+      type: {
+        kind: "enum",
+        variants: [
+          {
+            name: "Initialize",
+          },
+          {
+            name: "PostMessage",
+          },
+          {
+            name: "PostVaa",
+          },
+          {
+            name: "SetMessageFee",
+          },
+          {
+            name: "TransferFees",
+          },
+          {
+            name: "UpgradeContract",
+          },
+          {
+            name: "GuardianSetUpdate",
+          },
+          {
+            name: "VerifySignatures",
+          },
+          {
+            name: "PostMessageUnreliable",
+          },
+        ],
+      },
+    },
+    {
+      name: "EmitterType",
+      type: {
+        kind: "enum",
+        variants: [
+          {
+            name: "Unset",
+          },
+          {
+            name: "Legacy",
+          },
+          {
+            name: "Executable",
+          },
+        ],
+      },
+    },
+    {
+      name: "MessageStatus",
+      docs: [
+        "Status of a message. When a message is posted, its status is",
+        "[Published](MessageStatus::Published).",
+      ],
+      type: {
+        kind: "enum",
+        variants: [
+          {
+            name: "Published",
+          },
+          {
+            name: "Writing",
+          },
+          {
+            name: "ReadyForPublishing",
+          },
+        ],
+      },
+    },
+    {
+      name: "PublishMessageDirective",
+      docs: ["Directive used to determine how to post a Core Bridge message."],
+      type: {
+        kind: "enum",
+        variants: [
+          {
+            name: "Message",
+            fields: [
+              {
+                name: "nonce",
+                type: "u32",
+              },
+              {
+                name: "payload",
+                type: "bytes",
+              },
+              {
+                name: "commitment",
+                type: {
+                  defined: "Commitment",
+                },
+              },
+            ],
+          },
+          {
+            name: "ProgramMessage",
+            fields: [
+              {
+                name: "programId",
+                type: "publicKey",
+              },
+              {
+                name: "nonce",
+                type: "u32",
+              },
+              {
+                name: "payload",
+                type: "bytes",
+              },
+              {
+                name: "commitment",
+                type: {
+                  defined: "Commitment",
+                },
+              },
+            ],
+          },
+          {
+            name: "PreparedMessage",
+          },
+        ],
+      },
+    },
+    {
+      name: "ProcessingStatus",
+      docs: ["Encoded VAA's processing status."],
+      type: {
+        kind: "enum",
+        variants: [
+          {
+            name: "Unset",
+          },
+          {
+            name: "Writing",
+          },
+          {
+            name: "Verified",
+          },
+        ],
+      },
+    },
+    {
+      name: "Commitment",
+      docs: [
+        "Representation of Solana's commitment levels. This enum is not exhaustive because Wormhole only",
+        "considers these two commitment levels in its Guardian observation.",
+        "",
+        "See <https://docs.solana.com/cluster/commitments> for more info.",
+      ],
+      type: {
+        kind: "enum",
+        variants: [
+          {
+            name: "Confirmed",
+          },
+          {
+            name: "Finalized",
+          },
+        ],
+      },
+    },
+  ],
+  errors: [
+    {
+      code: 6002,
+      name: "InvalidInstructionArgument",
+      msg: "InvalidInstructionArgument",
+    },
+    {
+      code: 6003,
+      name: "AccountNotZeroed",
+      msg: "AccountNotZeroed",
+    },
+    {
+      code: 6004,
+      name: "InvalidDataConversion",
+      msg: "InvalidDataConversion",
+    },
+    {
+      code: 6006,
+      name: "U64Overflow",
+      msg: "U64Overflow",
+    },
+    {
+      code: 6008,
+      name: "InvalidComputeSize",
+      msg: "InvalidComputeSize",
+    },
+    {
+      code: 6016,
+      name: "InvalidChain",
+      msg: "InvalidChain",
+    },
+    {
+      code: 6032,
+      name: "InvalidGovernanceEmitter",
+      msg: "InvalidGovernanceEmitter",
+    },
+    {
+      code: 6034,
+      name: "InvalidGovernanceAction",
+      msg: "InvalidGovernanceAction",
+    },
+    {
+      code: 6036,
+      name: "LatestGuardianSetRequired",
+      msg: "LatestGuardianSetRequired",
+    },
+    {
+      code: 6038,
+      name: "GovernanceForAnotherChain",
+      msg: "GovernanceForAnotherChain",
+    },
+    {
+      code: 6040,
+      name: "InvalidGovernanceVaa",
+      msg: "InvalidGovernanceVaa",
+    },
+    {
+      code: 6256,
+      name: "InsufficientFees",
+      msg: "InsufficientFees",
+    },
+    {
+      code: 6258,
+      name: "EmitterMismatch",
+      msg: "EmitterMismatch",
+    },
+    {
+      code: 6260,
+      name: "NotReadyForPublishing",
+      msg: "NotReadyForPublishing",
+    },
+    {
+      code: 6262,
+      name: "InvalidPreparedMessage",
+      msg: "InvalidPreparedMessage",
+    },
+    {
+      code: 6264,
+      name: "ExecutableEmitter",
+      msg: "ExecutableEmitter",
+    },
+    {
+      code: 6266,
+      name: "LegacyEmitter",
+      msg: "LegacyEmitter",
+    },
+    {
+      code: 6512,
+      name: "InvalidSignatureSet",
+      msg: "InvalidSignatureSet",
+    },
+    {
+      code: 6514,
+      name: "InvalidMessageHash",
+      msg: "InvalidMessageHash",
+    },
+    {
+      code: 6515,
+      name: "NoQuorum",
+      msg: "NoQuorum",
+    },
+    {
+      code: 6516,
+      name: "MessageMismatch",
+      msg: "MessageMismatch",
+    },
+    {
+      code: 7024,
+      name: "NotEnoughLamports",
+      msg: "NotEnoughLamports",
+    },
+    {
+      code: 7026,
+      name: "InvalidFeeRecipient",
+      msg: "InvalidFeeRecipient",
+    },
+    {
+      code: 7280,
+      name: "ImplementationMismatch",
+      msg: "ImplementationMismatch",
+    },
+    {
+      code: 7536,
+      name: "InvalidGuardianSetIndex",
+      msg: "InvalidGuardianSetIndex",
+    },
+    {
+      code: 7792,
+      name: "GuardianSetMismatch",
+      msg: "GuardianSetMismatch",
+    },
+    {
+      code: 7794,
+      name: "InstructionAtWrongIndex",
+      msg: "InstructionAtWrongIndex",
+    },
+    {
+      code: 7795,
+      name: "EmptySigVerifyInstruction",
+      msg: "EmptySigVerifyInstruction",
+    },
+    {
+      code: 7796,
+      name: "InvalidSigVerifyInstruction",
+      msg: "InvalidSigVerifyInstruction",
+    },
+    {
+      code: 7798,
+      name: "GuardianSetExpired",
+      msg: "GuardianSetExpired",
+    },
+    {
+      code: 7800,
+      name: "InvalidGuardianKeyRecovery",
+      msg: "InvalidGuardianKeyRecovery",
+    },
+    {
+      code: 7802,
+      name: "SignerIndicesMismatch",
+      msg: "SignerIndicesMismatch",
+    },
+    {
+      code: 8048,
+      name: "PayloadSizeMismatch",
+      msg: "PayloadSizeMismatch",
+    },
+    {
+      code: 10112,
+      name: "ZeroGuardians",
+      msg: "ZeroGuardians",
+    },
+    {
+      code: 10128,
+      name: "GuardianZeroAddress",
+      msg: "GuardianZeroAddress",
+    },
+    {
+      code: 10144,
+      name: "DuplicateGuardianAddress",
+      msg: "DuplicateGuardianAddress",
+    },
+    {
+      code: 10160,
+      name: "MessageAlreadyPublished",
+      msg: "MessageAlreadyPublished",
+    },
+    {
+      code: 10176,
+      name: "VaaWritingDisallowed",
+      msg: "VaaWritingDisallowed",
+    },
+    {
+      code: 10192,
+      name: "VaaAlreadyVerified",
+      msg: "VaaAlreadyVerified",
+    },
+    {
+      code: 10208,
+      name: "InvalidGuardianIndex",
+      msg: "InvalidGuardianIndex",
+    },
+    {
+      code: 10224,
+      name: "InvalidSignature",
+      msg: "InvalidSignature",
+    },
+    {
+      code: 10256,
+      name: "UnverifiedVaa",
+      msg: "UnverifiedVaa",
+    },
+    {
+      code: 10258,
+      name: "VaaStillProcessing",
+      msg: "VaaStillProcessing",
+    },
+    {
+      code: 10260,
+      name: "InWritingStatus",
+      msg: "InWritingStatus",
+    },
+    {
+      code: 10262,
+      name: "NotInWritingStatus",
+      msg: "NotInWritingStatus",
+    },
+    {
+      code: 10264,
+      name: "InvalidMessageStatus",
+      msg: "InvalidMessageStatus",
+    },
+    {
+      code: 10266,
+      name: "HashNotComputed",
+      msg: "HashNotComputed",
+    },
+    {
+      code: 10268,
+      name: "InvalidVaaVersion",
+      msg: "InvalidVaaVersion",
+    },
+    {
+      code: 10270,
+      name: "InvalidCreatedAccountSize",
+      msg: "InvalidCreatedAccountSize",
+    },
+    {
+      code: 10272,
+      name: "DataOverflow",
+      msg: "DataOverflow",
+    },
+    {
+      code: 10274,
+      name: "ExceedsMaxPayloadSize",
+      msg: "ExceedsMaxPayloadSize (30KB)",
+    },
+    {
+      code: 10276,
+      name: "CannotParseVaa",
+      msg: "CannotParseVaa",
+    },
+    {
+      code: 10278,
+      name: "EmitterAuthorityMismatch",
+      msg: "EmitterAuthorityMismatch",
+    },
+    {
+      code: 10280,
+      name: "InvalidProgramEmitter",
+      msg: "InvalidProgramEmitter",
+    },
+    {
+      code: 10282,
+      name: "WriteAuthorityMismatch",
+      msg: "WriteAuthorityMismatch",
+    },
+    {
+      code: 10284,
+      name: "PostedVaaPayloadTooLarge",
+      msg: "PostedVaaPayloadTooLarge",
+    },
+    {
+      code: 10286,
+      name: "ExecutableDisallowed",
+      msg: "ExecutableDisallowed",
+    },
+  ],
+};

+ 2 - 0
target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts

@@ -0,0 +1,2 @@
+export { PythSolanaReceiver } from "./PythSolanaReceiver";
+export { TransactionBuilder } from "@pythnetwork/solana-utils";

+ 81 - 0
target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts

@@ -0,0 +1,81 @@
+import { Keypair, PublicKey } from "@solana/web3.js";
+import { WormholeCoreBridgeSolana } from "./idl/wormhole_core_bridge_solana";
+import { Program } from "@coral-xyz/anchor";
+import { InstructionWithEphemeralSigners } from "@pythnetwork/solana-utils";
+
+export const VAA_START = 46;
+export const VAA_SIGNATURE_SIZE = 66;
+export const VAA_SPLIT_INDEX = 792;
+export const DEFAULT_REDUCED_GUARDIAN_SET_SIZE = 5;
+
+export function getGuardianSetIndex(vaa: Buffer) {
+  return vaa.readUInt32BE(1);
+}
+
+export function trimSignatures(
+  vaa: Buffer,
+  n = DEFAULT_REDUCED_GUARDIAN_SET_SIZE
+): Buffer {
+  const currentNumSignatures = vaa[5];
+  if (n > currentNumSignatures) {
+    throw new Error(
+      "Resulting VAA can't have more signatures than the original VAA"
+    );
+  }
+
+  const trimmedVaa = Buffer.concat([
+    vaa.subarray(0, 6 + n * VAA_SIGNATURE_SIZE),
+    vaa.subarray(6 + currentNumSignatures * VAA_SIGNATURE_SIZE),
+  ]);
+
+  trimmedVaa[5] = n;
+  return trimmedVaa;
+}
+
+export async function buildEncodedVaaCreateInstruction(
+  wormhole: Program<WormholeCoreBridgeSolana>,
+  vaa: Buffer,
+  encodedVaaKeypair: Keypair
+) {
+  const encodedVaaSize = vaa.length + VAA_START;
+  return {
+    instruction: await wormhole.account.encodedVaa.createInstruction(
+      encodedVaaKeypair,
+      encodedVaaSize
+    ),
+    signers: [encodedVaaKeypair],
+  };
+}
+
+export async function buildWriteEncodedVaaWithSplit(
+  wormhole: Program<WormholeCoreBridgeSolana>,
+  vaa: Buffer,
+  draftVaa: PublicKey
+): Promise<InstructionWithEphemeralSigners[]> {
+  return [
+    {
+      instruction: await wormhole.methods
+        .writeEncodedVaa({
+          index: 0,
+          data: vaa.subarray(0, VAA_SPLIT_INDEX),
+        })
+        .accounts({
+          draftVaa,
+        })
+        .instruction(),
+      signers: [],
+    },
+    {
+      instruction: await wormhole.methods
+        .writeEncodedVaa({
+          index: VAA_SPLIT_INDEX,
+          data: vaa.subarray(VAA_SPLIT_INDEX),
+        })
+        .accounts({
+          draftVaa,
+        })
+        .instruction(),
+      signers: [],
+    },
+  ];
+}

+ 9 - 0
target_chains/solana/sdk/js/pyth_solana_receiver/tsconfig.json

@@ -0,0 +1,9 @@
+{
+  "extends": "../../../../../tsconfig.base.json",
+  "include": ["src/**/*.ts", "src/**/*.json"],
+  "exclude": ["node_modules", "**/__tests__/*"],
+  "compilerOptions": {
+    "rootDir": "src/",
+    "outDir": "./lib"
+  }
+}

+ 10 - 0
target_chains/solana/sdk/js/solana_utils/.eslintrc.js

@@ -0,0 +1,10 @@
+module.exports = {
+  root: true,
+  parser: "@typescript-eslint/parser",
+  plugins: ["@typescript-eslint"],
+  extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
+  rules: {
+    "@typescript-eslint/no-explicit-any": "off",
+    "@typescript-eslint/ban-ts-comment": "off",
+  },
+};

+ 5 - 0
target_chains/solana/sdk/js/solana_utils/jest.config.js

@@ -0,0 +1,5 @@
+/** @type {import('ts-jest').JestConfigWithTsJest} */
+module.exports = {
+  preset: "ts-jest",
+  testEnvironment: "node",
+};

+ 47 - 0
target_chains/solana/sdk/js/solana_utils/package.json

@@ -0,0 +1,47 @@
+{
+  "name": "@pythnetwork/solana-utils",
+  "version": "0.1.0",
+  "description": "Utility functions for Solana",
+  "homepage": "https://pyth.network",
+  "main": "lib/index.js",
+  "types": "lib/index.d.ts",
+  "files": [
+    "lib/**/*"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/pyth-network/pyth-crosschain.git",
+    "directory": "target_chains/solana/sdk/js/solana_utils"
+  },
+  "publishConfig": {
+    "access": "public"
+  },
+  "scripts": {
+    "test": "jest",
+    "build": "tsc",
+    "format": "prettier --write \"src/**/*.ts\"",
+    "lint": "eslint src/",
+    "prepublishOnly": "npm run build && npm test && npm run lint",
+    "preversion": "npm run lint",
+    "version": "npm run format && git add -A src"
+  },
+  "keywords": [
+    "pyth",
+    "oracle"
+  ],
+  "license": "Apache-2.0",
+  "devDependencies": {
+    "@types/jest": "^29.4.0",
+    "@typescript-eslint/eslint-plugin": "^5.20.0",
+    "@typescript-eslint/parser": "^5.20.0",
+    "eslint": "^8.13.0",
+    "jest": "^29.4.0",
+    "prettier": "^2.6.2",
+    "quicktype": "^23.0.76",
+    "ts-jest": "^29.0.5",
+    "typescript": "^4.6.3"
+  },
+  "dependencies": {
+    "@solana/web3.js": "^1.90.0"
+  }
+}

+ 84 - 0
target_chains/solana/sdk/js/solana_utils/src/__tests__/TransactionSize.test.ts

@@ -0,0 +1,84 @@
+import {
+  ComputeBudgetProgram,
+  Keypair,
+  PublicKey,
+  SystemProgram,
+  Transaction,
+  TransactionInstruction,
+  TransactionMessage,
+  VersionedTransaction,
+} from "@solana/web3.js";
+import { getSizeOfCompressedU16, getSizeOfTransaction } from "..";
+
+it("Unit test compressed u16 size", async () => {
+  expect(getSizeOfCompressedU16(127)).toBe(1);
+  expect(getSizeOfCompressedU16(128)).toBe(2);
+  expect(getSizeOfCompressedU16(16383)).toBe(2);
+  expect(getSizeOfCompressedU16(16384)).toBe(3);
+});
+
+it("Unit test for getSizeOfTransaction", async () => {
+  jest.setTimeout(60000);
+
+  const payer = new Keypair();
+
+  const ixsToSend: TransactionInstruction[] = [];
+
+  ixsToSend.push(
+    SystemProgram.createAccount({
+      fromPubkey: payer.publicKey,
+      newAccountPubkey: PublicKey.unique(),
+      space: 100,
+      lamports: 1000000000,
+      programId: SystemProgram.programId,
+    })
+  );
+
+  ixsToSend.push(
+    SystemProgram.createAccountWithSeed({
+      fromPubkey: PublicKey.unique(),
+      basePubkey: PublicKey.unique(),
+      seed: "seed",
+      newAccountPubkey: PublicKey.unique(),
+      space: 100,
+      lamports: 1000000000,
+      programId: SystemProgram.programId,
+    })
+  );
+
+  ixsToSend.push(
+    new TransactionInstruction({
+      keys: [{ pubkey: PublicKey.unique(), isSigner: true, isWritable: true }],
+      programId: PublicKey.unique(),
+      data: Buffer.from([1, 2, 3]),
+    })
+  );
+
+  ixsToSend.push(ComputeBudgetProgram.setComputeUnitLimit({ units: 69 }));
+
+  ixsToSend.push(
+    ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1000000 })
+  );
+
+  const transaction = new Transaction();
+  for (const ix of ixsToSend) {
+    transaction.add(ix);
+  }
+
+  transaction.recentBlockhash = "GqdFtdM7zzWw33YyHtBNwPhyBsdYKcfm9gT47bWnbHvs"; // Mock blockhash from devnet
+  transaction.feePayer = payer.publicKey;
+  expect(transaction.serialize({ requireAllSignatures: false }).length).toBe(
+    getSizeOfTransaction(ixsToSend, false)
+  );
+
+  const versionedTransaction = new VersionedTransaction(
+    new TransactionMessage({
+      recentBlockhash: transaction.recentBlockhash,
+      payerKey: payer.publicKey,
+      instructions: ixsToSend,
+    }).compileToV0Message()
+  );
+  expect(versionedTransaction.serialize().length).toBe(
+    getSizeOfTransaction(ixsToSend)
+  );
+});

+ 6 - 0
target_chains/solana/sdk/js/solana_utils/src/index.ts

@@ -0,0 +1,6 @@
+export {
+  getSizeOfTransaction,
+  getSizeOfCompressedU16,
+  TransactionBuilder,
+  InstructionWithEphemeralSigners,
+} from "./transaction";

+ 226 - 0
target_chains/solana/sdk/js/solana_utils/src/transaction.ts

@@ -0,0 +1,226 @@
+import {
+  ComputeBudgetProgram,
+  Connection,
+  PACKET_DATA_SIZE,
+  PublicKey,
+  Signer,
+  Transaction,
+  TransactionInstruction,
+  TransactionMessage,
+  VersionedTransaction,
+} from "@solana/web3.js";
+
+export const DEFAULT_COMPUTE_BUDGET_UNITS = 200000;
+export const PACKET_DATA_SIZE_WITH_ROOM_FOR_COMPUTE_BUDGET =
+  PACKET_DATA_SIZE - 52;
+
+export type InstructionWithEphemeralSigners = {
+  instruction: TransactionInstruction;
+  signers: Signer[];
+  computeUnits?: number;
+};
+
+export type PriorityFeeConfig = {
+  computeUnitPriceMicroLamports?: number;
+};
+
+/**
+ * Get the size of a transaction that would contain the provided array of instructions
+ */
+export function getSizeOfTransaction(
+  instructions: TransactionInstruction[],
+  versionedTransaction = true
+): number {
+  const signers = new Set<string>();
+  const accounts = new Set<string>();
+
+  instructions.map((ix) => {
+    accounts.add(ix.programId.toBase58()),
+      ix.keys.map((key) => {
+        if (key.isSigner) {
+          signers.add(key.pubkey.toBase58());
+        }
+        accounts.add(key.pubkey.toBase58());
+      });
+  });
+
+  const instruction_sizes: number = instructions
+    .map(
+      (ix) =>
+        1 +
+        getSizeOfCompressedU16(ix.keys.length) +
+        ix.keys.length +
+        getSizeOfCompressedU16(ix.data.length) +
+        ix.data.length
+    )
+    .reduce((a, b) => a + b, 0);
+
+  return (
+    1 +
+    signers.size * 64 +
+    3 +
+    getSizeOfCompressedU16(accounts.size) +
+    32 * accounts.size +
+    32 +
+    getSizeOfCompressedU16(instructions.length) +
+    instruction_sizes +
+    (versionedTransaction ? 1 + getSizeOfCompressedU16(0) : 0)
+  );
+}
+
+/**
+ * Get the size of n in bytes when serialized as a CompressedU16
+ */
+export function getSizeOfCompressedU16(n: number) {
+  return 1 + Number(n >= 128) + Number(n >= 16384);
+}
+
+/**
+ * This class is helpful for batching instructions into transactions in an efficient way.
+ * As you add instructions, it adds them to the current transactions until it's full, then it starts a new transaction.
+ */
+export class TransactionBuilder {
+  readonly transactionInstructions: {
+    instructions: TransactionInstruction[];
+    signers: Signer[];
+    computeUnits: number;
+  }[] = [];
+  readonly payer: PublicKey;
+  readonly connection: Connection;
+
+  constructor(payer: PublicKey, connection: Connection) {
+    this.payer = payer;
+    this.connection = connection;
+  }
+
+  /**
+   * Add an instruction to the builder, the signers argument can be used to specify ephemeral signers that need to sign the transaction
+   * where this instruction appears
+   */
+  addInstruction(args: InstructionWithEphemeralSigners) {
+    const { instruction, signers, computeUnits } = args;
+    if (this.transactionInstructions.length === 0) {
+      this.transactionInstructions.push({
+        instructions: [instruction],
+        signers: signers,
+        computeUnits: computeUnits ?? 0,
+      });
+    } else if (
+      getSizeOfTransaction([
+        ...this.transactionInstructions[this.transactionInstructions.length - 1]
+          .instructions,
+        instruction,
+      ]) <= PACKET_DATA_SIZE_WITH_ROOM_FOR_COMPUTE_BUDGET
+    ) {
+      this.transactionInstructions[
+        this.transactionInstructions.length - 1
+      ].instructions.push(instruction);
+      this.transactionInstructions[
+        this.transactionInstructions.length - 1
+      ].signers.push(...signers);
+      this.transactionInstructions[
+        this.transactionInstructions.length - 1
+      ].computeUnits += computeUnits ?? 0;
+    } else
+      this.transactionInstructions.push({
+        instructions: [instruction],
+        signers: signers,
+        computeUnits: computeUnits ?? 0,
+      });
+  }
+
+  addInstructions(instructions: InstructionWithEphemeralSigners[]) {
+    for (const { instruction, signers, computeUnits } of instructions) {
+      this.addInstruction({ instruction, signers, computeUnits });
+    }
+  }
+
+  /**
+   * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it
+   */
+  async getVersionedTransactions(
+    args: PriorityFeeConfig
+  ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> {
+    const blockhash = (await this.connection.getLatestBlockhash()).blockhash;
+
+    return this.transactionInstructions.map(
+      ({ instructions, signers, computeUnits }) => {
+        const instructionsWithComputeBudget: TransactionInstruction[] = [
+          ...instructions,
+        ];
+        if (computeUnits > DEFAULT_COMPUTE_BUDGET_UNITS * instructions.length) {
+          instructionsWithComputeBudget.push(
+            ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnits })
+          );
+        }
+        if (args.computeUnitPriceMicroLamports) {
+          instructionsWithComputeBudget.push(
+            ComputeBudgetProgram.setComputeUnitPrice({
+              microLamports: args.computeUnitPriceMicroLamports,
+            })
+          );
+        }
+
+        return {
+          tx: new VersionedTransaction(
+            new TransactionMessage({
+              recentBlockhash: blockhash,
+              instructions: instructionsWithComputeBudget,
+              payerKey: this.payer,
+            }).compileToV0Message()
+          ),
+          signers: signers,
+        };
+      }
+    );
+  }
+
+  /**
+   * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it
+   */
+  getLegacyTransactions(
+    args: PriorityFeeConfig
+  ): { tx: Transaction; signers: Signer[] }[] {
+    return this.transactionInstructions.map(({ instructions, signers }) => {
+      const instructionsWithComputeBudget = args.computeUnitPriceMicroLamports
+        ? [
+            ...instructions,
+            ComputeBudgetProgram.setComputeUnitPrice({
+              microLamports: args.computeUnitPriceMicroLamports,
+            }),
+          ]
+        : instructions;
+
+      return {
+        tx: new Transaction().add(...instructionsWithComputeBudget),
+        signers: signers,
+      };
+    });
+  }
+
+  static batchIntoLegacyTransactions(
+    instructions: TransactionInstruction[]
+  ): Transaction[] {
+    const transactionBuilder = new TransactionBuilder(
+      PublicKey.unique(),
+      new Connection("http://placeholder.placeholder")
+    ); // We only need wallet and connection for `VersionedTransaction` so we can put placeholders here
+    for (const instruction of instructions) {
+      transactionBuilder.addInstruction({ instruction, signers: [] });
+    }
+    return transactionBuilder.getLegacyTransactions({}).map(({ tx }) => {
+      return tx;
+    });
+  }
+
+  static async batchIntoVersionedTransactions(
+    payer: PublicKey,
+    connection: Connection,
+    instructions: InstructionWithEphemeralSigners[],
+    priorityFeeConfig: PriorityFeeConfig
+  ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> {
+    const transactionBuilder = new TransactionBuilder(payer, connection);
+    transactionBuilder.addInstructions(instructions);
+    return transactionBuilder.getVersionedTransactions(priorityFeeConfig);
+  }
+}

+ 9 - 0
target_chains/solana/sdk/js/solana_utils/tsconfig.json

@@ -0,0 +1,9 @@
+{
+  "extends": "../../../../../tsconfig.base.json",
+  "include": ["src/**/*.ts", "src/**/*.json"],
+  "exclude": ["node_modules", "**/__tests__/*"],
+  "compilerOptions": {
+    "rootDir": "src/",
+    "outDir": "./lib"
+  }
+}