diff --git a/.github/workflows/ci-solana-contract.yml b/.github/workflows/ci-solana-contract.yml index 534a4cee..3b9f7fa2 100644 --- a/.github/workflows/ci-solana-contract.yml +++ b/.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 diff --git a/governance/xc_admin/packages/xc_admin_common/package.json b/governance/xc_admin/packages/xc_admin_common/package.json index db6aeccd..94daf7eb 100644 --- a/governance/xc_admin/packages/xc_admin_common/package.json +++ b/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", diff --git a/governance/xc_admin/packages/xc_admin_common/src/__tests__/TransactionSize.test.ts b/governance/xc_admin/packages/xc_admin_common/src/__tests__/TransactionSize.test.ts index 5f69e4c0..cb6698d4 100644 --- a/governance/xc_admin/packages/xc_admin_common/src/__tests__/TransactionSize.test.ts +++ b/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) ); } diff --git a/governance/xc_admin/packages/xc_admin_common/src/propose.ts b/governance/xc_admin/packages/xc_admin_common/src/propose.ts index 718c6511..05f9ddb2 100644 --- a/governance/xc_admin/packages/xc_admin_common/src/propose.ts +++ b/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(); - const accounts = new Set(); - - 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 diff --git a/governance/xc_admin/packages/xc_admin_frontend/hooks/usePyth.ts b/governance/xc_admin/packages/xc_admin_frontend/hooks/usePyth.ts index cbb27096..8bb454e4 100644 --- a/governance/xc_admin/packages/xc_admin_frontend/hooks/usePyth.ts +++ b/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 } = {} diff --git a/package-lock.json b/package-lock.json index 34509537..ed04eda0 100644 --- a/package-lock.json +++ b/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": {} } } diff --git a/package.json b/package.json index c594a13d..f40fb634 100644 --- a/package.json +++ b/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": { diff --git a/price_service/sdk/js/src/AccumulatorUpdateData.ts b/price_service/sdk/js/src/AccumulatorUpdateData.ts index ce083ef1..a9149e2b 100644 --- a/price_service/sdk/js/src/AccumulatorUpdateData.ts +++ b/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; } diff --git a/price_service/sdk/js/src/__tests__/AccumulatorUpdateData.test.ts b/price_service/sdk/js/src/__tests__/AccumulatorUpdateData.test.ts index 98bf2c5a..4f0337b4 100644 --- a/price_service/sdk/js/src/__tests__/AccumulatorUpdateData.test.ts +++ b/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 () => { diff --git a/price_service/sdk/js/src/index.ts b/price_service/sdk/js/src/index.ts index 77542256..6421de4e 100644 --- a/price_service/sdk/js/src/index.ts +++ b/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"; /** diff --git a/target_chains/solana/.gitignore b/target_chains/solana/.gitignore index d243ecc1..250cb88d 100644 --- a/target_chains/solana/.gitignore +++ b/target_chains/solana/.gitignore @@ -4,4 +4,5 @@ target **/*.rs.bk node_modules +lib test-ledger diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/.eslintrc.js b/target_chains/solana/sdk/js/pyth_solana_receiver/.eslintrc.js new file mode 100644 index 00000000..bb71486c --- /dev/null +++ b/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", + }, +}; diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/package.json b/target_chains/solana/sdk/js/pyth_solana_receiver/package.json new file mode 100644 index 00000000..1cf07c50 --- /dev/null +++ b/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" + } +} diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts new file mode 100644 index 00000000..c0ec4218 --- /dev/null +++ b/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; + readonly wormhole: Program; + + 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( + Idl as PythSolanaReceiverProgram, + receiverProgramId, + this.provider + ); + this.wormhole = new Program( + WormholeCoreBridgeSolanaIdl as WormholeCoreBridgeSolana, + wormholeProgramId, + this.provider + ); + } + + async withPriceUpdate( + priceUpdateDataArray: string[], + getInstructions: ( + priceFeedIdToPriceUpdateAccount: Record + ) => Promise, + 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 + ) => Promise, + 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; + cleanupInstructions: InstructionWithEphemeralSigners[]; + }> { + const postInstructions: InstructionWithEphemeralSigners[] = []; + const priceFeedIdToPriceUpdateAccount: Record = {}; + 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; + cleanupInstructions: InstructionWithEphemeralSigners[]; + }> { + const postInstructions: InstructionWithEphemeralSigners[] = []; + const priceFeedIdToPriceUpdateAccount: Record = {}; + 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 { + const instruction = await this.wormhole.methods + .closeEncodedVaa() + .accounts({ encodedVaa }) + .instruction(); + return { instruction, signers: [] }; + } + + async buildClosePriceUpdateInstruction( + priceUpdateAccount: PublicKey + ): Promise { + const instruction = await this.receiver.methods + .reclaimRent() + .accounts({ priceUpdateAccount }) + .instruction(); + return { instruction, signers: [] }; + } +} diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/address.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/address.ts new file mode 100644 index 00000000..d5f417b8 --- /dev/null +++ b/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]; +}; diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/compute_budget.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/compute_budget.ts new file mode 100644 index 00000000..914f4df7 --- /dev/null +++ b/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; diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.ts new file mode 100644 index 00000000..e42825fa --- /dev/null +++ b/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", + }, + ], +}; diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/wormhole_core_bridge_solana.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/wormhole_core_bridge_solana.ts new file mode 100644 index 00000000..239f106c --- /dev/null +++ b/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 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 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", + }, + ], +}; diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts new file mode 100644 index 00000000..f4f83959 --- /dev/null +++ b/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"; diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts new file mode 100644 index 00000000..b7a08ac7 --- /dev/null +++ b/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, + 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, + vaa: Buffer, + draftVaa: PublicKey +): Promise { + 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: [], + }, + ]; +} diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/tsconfig.json b/target_chains/solana/sdk/js/pyth_solana_receiver/tsconfig.json new file mode 100644 index 00000000..4a55b57b --- /dev/null +++ b/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" + } +} diff --git a/target_chains/solana/sdk/js/solana_utils/.eslintrc.js b/target_chains/solana/sdk/js/solana_utils/.eslintrc.js new file mode 100644 index 00000000..bb71486c --- /dev/null +++ b/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", + }, +}; diff --git a/target_chains/solana/sdk/js/solana_utils/jest.config.js b/target_chains/solana/sdk/js/solana_utils/jest.config.js new file mode 100644 index 00000000..3abcbd94 --- /dev/null +++ b/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", +}; diff --git a/target_chains/solana/sdk/js/solana_utils/package.json b/target_chains/solana/sdk/js/solana_utils/package.json new file mode 100644 index 00000000..36e9a532 --- /dev/null +++ b/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" + } +} diff --git a/target_chains/solana/sdk/js/solana_utils/src/__tests__/TransactionSize.test.ts b/target_chains/solana/sdk/js/solana_utils/src/__tests__/TransactionSize.test.ts new file mode 100644 index 00000000..1215ae8d --- /dev/null +++ b/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) + ); +}); diff --git a/target_chains/solana/sdk/js/solana_utils/src/index.ts b/target_chains/solana/sdk/js/solana_utils/src/index.ts new file mode 100644 index 00000000..23a84b59 --- /dev/null +++ b/target_chains/solana/sdk/js/solana_utils/src/index.ts @@ -0,0 +1,6 @@ +export { + getSizeOfTransaction, + getSizeOfCompressedU16, + TransactionBuilder, + InstructionWithEphemeralSigners, +} from "./transaction"; diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts new file mode 100644 index 00000000..52e32fd4 --- /dev/null +++ b/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(); + const accounts = new Set(); + + 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); + } +} diff --git a/target_chains/solana/sdk/js/solana_utils/tsconfig.json b/target_chains/solana/sdk/js/solana_utils/tsconfig.json new file mode 100644 index 00000000..4a55b57b --- /dev/null +++ b/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" + } +}