From 2ebde4b4f3604808371ca318e107a2b2ceef9754 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 19:42:41 +0000 Subject: [PATCH 01/64] Do it --- .../packages/xc_admin_common/package.json | 3 +- .../src/__tests__/TransactionSize.test.ts | 17 +- .../packages/xc_admin_common/src/propose.ts | 81 +-- package-lock.json | 533 ++++++++---------- package.json | 1 + .../solana/sdk/js/solana_utils/.gitignore | 2 + .../solana/sdk/js/solana_utils/jest.config.js | 5 + .../solana/sdk/js/solana_utils/package.json | 48 ++ .../src/__tests__/TransactionSize.test.ts | 85 +++ .../solana/sdk/js/solana_utils/src/index.ts | 5 + .../sdk/js/solana_utils/src/transaction.ts | 143 +++++ .../solana/sdk/js/solana_utils/tsconfig.json | 9 + 12 files changed, 554 insertions(+), 378 deletions(-) create mode 100644 target_chains/solana/sdk/js/solana_utils/.gitignore create mode 100644 target_chains/solana/sdk/js/solana_utils/jest.config.js create mode 100644 target_chains/solana/sdk/js/solana_utils/package.json create mode 100644 target_chains/solana/sdk/js/solana_utils/src/__tests__/TransactionSize.test.ts create mode 100644 target_chains/solana/sdk/js/solana_utils/src/index.ts create mode 100644 target_chains/solana/sdk/js/solana_utils/src/transaction.ts create mode 100644 target_chains/solana/sdk/js/solana_utils/tsconfig.json diff --git a/governance/xc_admin/packages/xc_admin_common/package.json b/governance/xc_admin/packages/xc_admin_common/package.json index db6aeccd98..94daf7ebe0 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 5f69e4c0da..6c5c7f4e28 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,12 +15,14 @@ import { } from "@solana/web3.js"; import { batchIntoExecutorPayload, - batchIntoTransactions, - getSizeOfCompressedU16, getSizeOfExecutorInstructions, - getSizeOfTransaction, MAX_EXECUTOR_PAYLOAD_SIZE, } from ".."; +import { + getSizeOfTransaction, + getSizeOfCompressedU16, +} from "@pythnetwork/solana-utils"; +import { TransactionBuilder } from "@pythnetwork/solana-utils/src"; it("Unit test compressed u16 size", async () => { expect(getSizeOfCompressedU16(127)).toBe(1); @@ -84,7 +86,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 +117,14 @@ it("Unit test for getSizeOfTransaction", async () => { ); } - const txToSend: Transaction[] = batchIntoTransactions(ixsToSend); + const txToSend: Transaction[] = + await 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 +132,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 718c651161..2019a9257c 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,9 @@ export class MultisigVault { ixToSend.push(await this.activateProposalIx(proposalAddress)); ixToSend.push(await this.approveProposalIx(proposalAddress)); - const txToSend = batchIntoTransactions(ixToSend); + const txToSend = await TransactionBuilder.batchIntoLegacyTransactions( + ixToSend + ); await this.sendAllTransactions(txToSend); return proposalAddress; } @@ -360,7 +363,7 @@ export class MultisigVault { } } - const txToSend = batchIntoTransactions(ixToSend); + const txToSend = TransactionBuilder.batchIntoLegacyTransactions(ixToSend); await this.sendAllTransactions(txToSend); return newProposals; @@ -445,32 +448,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 +458,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/package-lock.json b/package-lock.json index 3450953715..0f16a9dbb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "target_chains/ethereum/examples/oracle_swap/app", "target_chains/sui/sdk/js", "target_chains/sui/cli", + "target_chains/solana/sdk/js/solana_utils", "contract_manager" ], "dependencies": { @@ -527,36 +528,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 +566,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 +3369,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 +3794,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 +3863,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 +6415,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", @@ -13252,6 +13201,10 @@ "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 +16227,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 +21970,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 +34404,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 +34418,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 +58780,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 +58875,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 +59525,87 @@ "node": ">=10.0.0" } }, + "target_chains/solana/sdk/js/solana_utils": { + "name": "@pythnetwork/solana-utils", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@coral-xyz/anchor": "^0.29.0", + "@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/solana_utils/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/solana_utils/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/solana_utils/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/solana_utils/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/sui/cli": { "name": "pyth-sui-cli", "version": "0.0.1", @@ -60047,36 +60055,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 +60119,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 +61762,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 +70524,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 +70596,7 @@ "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "optional": true, "requires": {} } } @@ -71581,6 +71537,64 @@ } } }, + "@pythnetwork/solana-utils": { + "version": "file:target_chains/solana/sdk/js/solana_utils", + "requires": { + "@coral-xyz/anchor": "^0.29.0", + "@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==" + } + } + }, "@radix-ui/primitive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", @@ -73818,31 +73832,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 +78619,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 +81903,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 +81933,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 +88895,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 +88909,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 +97088,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 +97128,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 +105877,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 +106268,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 +106310,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 c594a13d0c..3ff440eb98 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "target_chains/ethereum/examples/oracle_swap/app", "target_chains/sui/sdk/js", "target_chains/sui/cli", + "target_chains/solana/sdk/js/solana_utils", "contract_manager" ], "dependencies": { diff --git a/target_chains/solana/sdk/js/solana_utils/.gitignore b/target_chains/solana/sdk/js/solana_utils/.gitignore new file mode 100644 index 0000000000..491fc35975 --- /dev/null +++ b/target_chains/solana/sdk/js/solana_utils/.gitignore @@ -0,0 +1,2 @@ +node_modules +lib 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 0000000000..3abcbd9467 --- /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 0000000000..c3bcbe13ad --- /dev/null +++ b/target_chains/solana/sdk/js/solana_utils/package.json @@ -0,0 +1,48 @@ +{ + "name": "@pythnetwork/solana-utils", + "version": "0.1.0", + "description": "Utility function 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" + }, + "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": { + "@coral-xyz/anchor": "^0.29.0", + "@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 0000000000..9a156cb1ac --- /dev/null +++ b/target_chains/solana/sdk/js/solana_utils/src/__tests__/TransactionSize.test.ts @@ -0,0 +1,85 @@ +import { + ComputeBudgetProgram, + Keypair, + PublicKey, + SystemProgram, + Transaction, + TransactionInstruction, + TransactionMessage, + VersionedTransaction, +} from "@solana/web3.js"; +import { getSizeOfCompressedU16, getSizeOfTransaction } from ".."; +import { TOKEN_PROGRAM_ID } from "@coral-xyz/anchor/dist/cjs/utils/token"; + +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: TOKEN_PROGRAM_ID, + data: Buffer.from([1, 2, 3]), + }) + ); + + ixsToSend.push(ComputeBudgetProgram.setComputeUnitLimit({ units: 69 })); + + ixsToSend.push( + ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1000000 }) + ); + + const transaction = new Transaction(); + for (let 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 0000000000..d9b3e9532c --- /dev/null +++ b/target_chains/solana/sdk/js/solana_utils/src/index.ts @@ -0,0 +1,5 @@ +export { + getSizeOfTransaction, + getSizeOfCompressedU16, + TransactionBuilder, +} 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 0000000000..1d3f35d5ff --- /dev/null +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -0,0 +1,143 @@ +import { + Connection, + PACKET_DATA_SIZE, + PublicKey, + Signer, + Transaction, + TransactionInstruction, + TransactionMessage, + VersionedTransaction, +} from "@solana/web3.js"; + +/** + * 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); +} + +export class TransactionBuilder { + readonly transactionInstructions: { + instructions: TransactionInstruction[]; + signers: Signer[]; + }[] = []; + readonly payer: PublicKey; + readonly connection: Connection; + + constructor(payer: PublicKey, connection: Connection) { + this.payer = payer; + this.connection = connection; + } + + addInstruction(instruction: TransactionInstruction, signers: Signer[]) { + if (this.transactionInstructions.length === 0) { + this.transactionInstructions.push({ + instructions: [instruction], + signers: signers, + }); + } else if ( + getSizeOfTransaction([ + ...this.transactionInstructions[this.transactionInstructions.length - 1] + .instructions, + instruction, + ]) <= PACKET_DATA_SIZE + ) { + this.transactionInstructions[ + this.transactionInstructions.length - 1 + ].instructions.push(instruction); + this.transactionInstructions[ + this.transactionInstructions.length - 1 + ].signers.push(...signers); + } else + this.transactionInstructions.push({ + instructions: [instruction], + signers: signers, + }); + } + + async getVersionedTransactions(): Promise< + { tx: VersionedTransaction; signers: Signer[] }[] + > { + const blockhash = (await this.connection.getLatestBlockhash()).blockhash; + return this.transactionInstructions.map(({ instructions, signers }) => { + return { + tx: new VersionedTransaction( + new TransactionMessage({ + recentBlockhash: blockhash, + instructions: instructions, + payerKey: this.payer, + }).compileToV0Message() + ), + signers: signers, + }; + }); + } + + getLegacyTransactions(): { tx: Transaction; signers: Signer[] }[] { + return this.transactionInstructions.map(({ instructions, signers }) => { + return { + tx: new Transaction().add(...instructions), + 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 (let instruction of instructions) { + transactionBuilder.addInstruction(instruction, []); + } + return transactionBuilder.getLegacyTransactions().map(({ tx }) => { + return tx; + }); + } +} 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 0000000000..4a55b57b49 --- /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" + } +} From c086dcfa7bed071c45fcced075b0154099a4bfd0 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 19:43:48 +0000 Subject: [PATCH 02/64] Remove some duplicate code --- .../src/__tests__/TransactionSize.test.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) 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 6c5c7f4e28..62af96d2ae 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 @@ -18,19 +18,9 @@ import { getSizeOfExecutorInstructions, MAX_EXECUTOR_PAYLOAD_SIZE, } from ".."; -import { - getSizeOfTransaction, - getSizeOfCompressedU16, -} from "@pythnetwork/solana-utils"; +import { getSizeOfTransaction } from "@pythnetwork/solana-utils"; import { TransactionBuilder } from "@pythnetwork/solana-utils/src"; -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); @@ -118,7 +108,7 @@ it("Unit test for getSizeOfTransaction", async () => { } const txToSend: Transaction[] = - await TransactionBuilder.batchIntoLegacyTransactions(ixsToSend); + TransactionBuilder.batchIntoLegacyTransactions(ixsToSend); expect( txToSend.map((tx) => tx.instructions.length).reduce((a, b) => a + b) ).toBe(ixsToSend.length); From d32169f52957736ce02a1227792ad39e6fa37472 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 19:52:22 +0000 Subject: [PATCH 03/64] Cleanup --- .../solana/sdk/js/solana_utils/src/transaction.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts index 1d3f35d5ff..ae5eda6e2c 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -60,6 +60,10 @@ 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[]; @@ -73,6 +77,10 @@ export class TransactionBuilder { 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(instruction: TransactionInstruction, signers: Signer[]) { if (this.transactionInstructions.length === 0) { this.transactionInstructions.push({ @@ -99,6 +107,9 @@ export class TransactionBuilder { }); } + /** + * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it + */ async getVersionedTransactions(): Promise< { tx: VersionedTransaction; signers: Signer[] }[] > { @@ -117,6 +128,9 @@ export class TransactionBuilder { }); } + /** + * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it + */ getLegacyTransactions(): { tx: Transaction; signers: Signer[] }[] { return this.transactionInstructions.map(({ instructions, signers }) => { return { From b49e08412b44f4b420561415215cb8dd06842ae1 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 19:53:14 +0000 Subject: [PATCH 04/64] Cleanup --- governance/xc_admin/packages/xc_admin_common/src/propose.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 2019a9257c..05f9ddb20d 100644 --- a/governance/xc_admin/packages/xc_admin_common/src/propose.ts +++ b/governance/xc_admin/packages/xc_admin_common/src/propose.ts @@ -257,9 +257,7 @@ export class MultisigVault { ixToSend.push(await this.activateProposalIx(proposalAddress)); ixToSend.push(await this.approveProposalIx(proposalAddress)); - const txToSend = await TransactionBuilder.batchIntoLegacyTransactions( - ixToSend - ); + const txToSend = TransactionBuilder.batchIntoLegacyTransactions(ixToSend); await this.sendAllTransactions(txToSend); return proposalAddress; } From fb0d06f187b1048729c3393798459b7f0a2240b6 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 19:55:26 +0000 Subject: [PATCH 05/64] Cleanup import --- .../xc_admin_common/src/__tests__/TransactionSize.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 62af96d2ae..cb6698d441 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 @@ -18,8 +18,10 @@ import { getSizeOfExecutorInstructions, MAX_EXECUTOR_PAYLOAD_SIZE, } from ".."; -import { getSizeOfTransaction } from "@pythnetwork/solana-utils"; -import { TransactionBuilder } from "@pythnetwork/solana-utils/src"; +import { + getSizeOfTransaction, + TransactionBuilder, +} from "@pythnetwork/solana-utils"; it("Unit test for getSizeOfTransaction", async () => { jest.setTimeout(60000); From d08e92a8136df6642993d511479c1b461496f92b Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 19:58:10 +0000 Subject: [PATCH 06/64] Correct description --- target_chains/solana/sdk/js/solana_utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/solana/sdk/js/solana_utils/package.json b/target_chains/solana/sdk/js/solana_utils/package.json index c3bcbe13ad..dcc31b3456 100644 --- a/target_chains/solana/sdk/js/solana_utils/package.json +++ b/target_chains/solana/sdk/js/solana_utils/package.json @@ -1,7 +1,7 @@ { "name": "@pythnetwork/solana-utils", "version": "0.1.0", - "description": "Utility function for Solana", + "description": "Utility functions for Solana", "homepage": "https://pyth.network", "main": "lib/index.js", "types": "lib/index.d.ts", From d73e8081324e3ed44abe538ad850fd7e1ccdef92 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 19:58:45 +0000 Subject: [PATCH 07/64] Fix path --- target_chains/solana/sdk/js/solana_utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/solana/sdk/js/solana_utils/package.json b/target_chains/solana/sdk/js/solana_utils/package.json index dcc31b3456..e24fdff8f4 100644 --- a/target_chains/solana/sdk/js/solana_utils/package.json +++ b/target_chains/solana/sdk/js/solana_utils/package.json @@ -11,7 +11,7 @@ "repository": { "type": "git", "url": "https://github.com/pyth-network/pyth-crosschain.git", - "directory": "target_chains/solana/sdk/js" + "directory": "target_chains/solana/sdk/js/solana_utils" }, "publishConfig": { "access": "public" From 31130260c7742a2fbb1afd248a567f3cbf93c036 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 19:59:44 +0000 Subject: [PATCH 08/64] Cleanup deps --- package-lock.json | 105 ------------------ .../solana/sdk/js/solana_utils/package.json | 1 - 2 files changed, 106 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f16a9dbb9..5aa08ee9ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59530,7 +59530,6 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "@coral-xyz/anchor": "^0.29.0", "@solana/web3.js": "^1.90.0" }, "devDependencies": { @@ -59545,67 +59544,6 @@ "typescript": "^4.6.3" } }, - "target_chains/solana/sdk/js/solana_utils/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/solana_utils/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/solana_utils/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/solana_utils/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/sui/cli": { "name": "pyth-sui-cli", "version": "0.0.1", @@ -71540,7 +71478,6 @@ "@pythnetwork/solana-utils": { "version": "file:target_chains/solana/sdk/js/solana_utils", "requires": { - "@coral-xyz/anchor": "^0.29.0", "@solana/web3.js": "^1.90.0", "@types/jest": "^29.4.0", "@typescript-eslint/eslint-plugin": "^5.20.0", @@ -71551,48 +71488,6 @@ "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==" - } } }, "@radix-ui/primitive": { diff --git a/target_chains/solana/sdk/js/solana_utils/package.json b/target_chains/solana/sdk/js/solana_utils/package.json index e24fdff8f4..36e9a532cd 100644 --- a/target_chains/solana/sdk/js/solana_utils/package.json +++ b/target_chains/solana/sdk/js/solana_utils/package.json @@ -42,7 +42,6 @@ "typescript": "^4.6.3" }, "dependencies": { - "@coral-xyz/anchor": "^0.29.0", "@solana/web3.js": "^1.90.0" } } From 4b645cc03978d060689b6402208edd6333103871 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 20:00:35 +0000 Subject: [PATCH 09/64] Unique --- .../sdk/js/solana_utils/src/__tests__/TransactionSize.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 index 9a156cb1ac..125e7e6276 100644 --- 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 @@ -9,7 +9,6 @@ import { VersionedTransaction, } from "@solana/web3.js"; import { getSizeOfCompressedU16, getSizeOfTransaction } from ".."; -import { TOKEN_PROGRAM_ID } from "@coral-xyz/anchor/dist/cjs/utils/token"; it("Unit test compressed u16 size", async () => { expect(getSizeOfCompressedU16(127)).toBe(1); @@ -50,7 +49,7 @@ it("Unit test for getSizeOfTransaction", async () => { ixsToSend.push( new TransactionInstruction({ keys: [{ pubkey: PublicKey.unique(), isSigner: true, isWritable: true }], - programId: TOKEN_PROGRAM_ID, + programId: PublicKey.unique(), data: Buffer.from([1, 2, 3]), }) ); From 191fa018e20cbd97480b1eec4b83efe740e2a2d0 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 20:03:47 +0000 Subject: [PATCH 10/64] Works --- target_chains/solana/sdk/js/solana_utils/src/transaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts index ae5eda6e2c..f7811b706f 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -146,7 +146,7 @@ export class TransactionBuilder { 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 + ); // We only need wallet and connection for `VersionedTransaction` so we can put placeholders here for (let instruction of instructions) { transactionBuilder.addInstruction(instruction, []); } From 9172fadc45e6c584adb309f916b639289686fb36 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 20:05:11 +0000 Subject: [PATCH 11/64] Continue --- .../xc_admin/packages/xc_admin_frontend/hooks/usePyth.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 cbb2709665..8bb454e44d 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 } = {} From 063de5608dfc06a88207c79de909007a1a68b655 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 20:15:57 +0000 Subject: [PATCH 12/64] Lint --- .../sdk/js/solana_utils/src/__tests__/TransactionSize.test.ts | 2 +- target_chains/solana/sdk/js/solana_utils/src/transaction.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 125e7e6276..1215ae8dc2 100644 --- 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 @@ -61,7 +61,7 @@ it("Unit test for getSizeOfTransaction", async () => { ); const transaction = new Transaction(); - for (let ix of ixsToSend) { + for (const ix of ixsToSend) { transaction.add(ix); } diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts index f7811b706f..38f8b0520e 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -147,7 +147,7 @@ export class TransactionBuilder { PublicKey.unique(), new Connection("http://placeholder.placeholder") ); // We only need wallet and connection for `VersionedTransaction` so we can put placeholders here - for (let instruction of instructions) { + for (const instruction of instructions) { transactionBuilder.addInstruction(instruction, []); } return transactionBuilder.getLegacyTransactions().map(({ tx }) => { From b90526679ea7da31a434b91aaf757e5de494feab Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 20:16:24 +0000 Subject: [PATCH 13/64] Lint config --- target_chains/solana/sdk/js/solana_utils/.eslintrc.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 target_chains/solana/sdk/js/solana_utils/.eslintrc.js 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 0000000000..bb71486c02 --- /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", + }, +}; From 81a33072ee127103612b1bcfaa665bea43f36c1d Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 20:32:43 +0000 Subject: [PATCH 14/64] Fix ci --- .github/workflows/ci-solana-contract.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-solana-contract.yml b/.github/workflows/ci-solana-contract.yml index 534a4ceed0..3b9f7fa29b 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 From 6a2bdbced0b97328fc48c3a9f4033fed479a2881 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 20 Feb 2024 14:03:18 +0000 Subject: [PATCH 15/64] Checkpoint --- package-lock.json | 428 ++- .../solana/idl/pyth_solana_receiver.ts | 875 +++++ .../solana/idl/wormhole_core_bridge_solana.ts | 3211 +++++++++++++++++ target_chains/solana/sdk/js/package.json | 48 + .../PythSolanaReceiverConnection.test.ts | 10 + target_chains/solana/sdk/js/src/address.ts | 8 + target_chains/solana/sdk/js/src/index.ts | 44 + target_chains/solana/sdk/js/tsconfig.json | 8 + 8 files changed, 4498 insertions(+), 134 deletions(-) create mode 100644 target_chains/solana/idl/pyth_solana_receiver.ts create mode 100644 target_chains/solana/idl/wormhole_core_bridge_solana.ts create mode 100644 target_chains/solana/sdk/js/package.json create mode 100644 target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts create mode 100644 target_chains/solana/sdk/js/src/address.ts create mode 100644 target_chains/solana/sdk/js/src/index.ts create mode 100644 target_chains/solana/sdk/js/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 5aa08ee9ae..3450953715 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,6 @@ "target_chains/ethereum/examples/oracle_swap/app", "target_chains/sui/sdk/js", "target_chains/sui/cli", - "target_chains/solana/sdk/js/solana_utils", "contract_manager" ], "dependencies": { @@ -528,6 +527,36 @@ "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", @@ -566,7 +595,6 @@ "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" }, @@ -3369,7 +3397,6 @@ "@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", @@ -3794,6 +3821,36 @@ "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", @@ -3863,7 +3920,6 @@ "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" }, @@ -6415,21 +6471,16 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dependencies": { - "regenerator-runtime": "^0.14.0" + "regenerator-runtime": "^0.13.11" }, "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", @@ -13201,10 +13252,6 @@ "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", @@ -16227,48 +16274,37 @@ } }, "node_modules/@solana/web3.js": { - "version": "1.90.0", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.90.0.tgz", - "integrity": "sha512-p0cb/COXb8NNVSMkGMPwqQ6NvObZgUitN80uOedMB+jbYWOKOeJBuPnzhenkIV9RX0krGwyuY1Ltn5O8MGFsEw==", + "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==", "dependencies": { - "@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", + "@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", "bigint-buffer": "^1.1.5", - "bn.js": "^5.2.1", + "bn.js": "^5.0.0", "borsh": "^0.7.0", "bs58": "^4.0.1", "buffer": "6.0.3", "fast-stable-stringify": "^1.0.0", - "jayson": "^4.1.0", - "node-fetch": "^2.7.0", + "jayson": "^3.4.4", + "node-fetch": "^2.6.7", "rpc-websockets": "^7.5.1", "superstruct": "^0.14.2" } }, - "node_modules/@solana/web3.js/node_modules/@noble/curves": { - "version": "1.3.0", - "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/" - } + "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/" + } + ] }, "node_modules/@solana/web3.js/node_modules/buffer": { "version": "6.0.3", @@ -21970,10 +22006,12 @@ } }, "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", "humanize-ms": "^1.2.1" }, "engines": { @@ -34404,9 +34442,9 @@ } }, "node_modules/jayson": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz", - "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz", + "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==", "dependencies": { "@types/connect": "^3.4.33", "@types/node": "^12.12.54", @@ -34418,6 +34456,7 @@ "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" }, @@ -58780,6 +58819,41 @@ "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", @@ -58875,7 +58949,6 @@ "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" }, @@ -59525,25 +59598,6 @@ "node": ">=10.0.0" } }, - "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", @@ -59993,6 +60047,36 @@ "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", @@ -60057,7 +60141,6 @@ "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" }, @@ -61700,18 +61783,11 @@ } }, "@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "requires": { - "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==" - } + "regenerator-runtime": "^0.13.11" } }, "@babel/template": { @@ -70462,6 +70538,37 @@ "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", @@ -70534,7 +70641,6 @@ "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "optional": true, "requires": {} } } @@ -71475,21 +71581,6 @@ } } }, - "@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", @@ -73727,39 +73818,31 @@ } }, "@solana/web3.js": { - "version": "1.90.0", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.90.0.tgz", - "integrity": "sha512-p0cb/COXb8NNVSMkGMPwqQ6NvObZgUitN80uOedMB+jbYWOKOeJBuPnzhenkIV9RX0krGwyuY1Ltn5O8MGFsEw==", + "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==", "requires": { - "@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", + "@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", "bigint-buffer": "^1.1.5", - "bn.js": "^5.2.1", + "bn.js": "^5.0.0", "borsh": "^0.7.0", "bs58": "^4.0.1", "buffer": "6.0.3", "fast-stable-stringify": "^1.0.0", - "jayson": "^4.1.0", - "node-fetch": "^2.7.0", + "jayson": "^3.4.4", + "node-fetch": "^2.6.7", "rpc-websockets": "^7.5.1", "superstruct": "^0.14.2" }, "dependencies": { - "@noble/curves": { - "version": "1.3.0", - "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==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz", + "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==" }, "buffer": { "version": "6.0.3", @@ -78514,10 +78597,12 @@ } }, "agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", "humanize-ms": "^1.2.1" } }, @@ -81798,6 +81883,32 @@ "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", @@ -81828,7 +81939,6 @@ "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "optional": true, "requires": {} } } @@ -88790,9 +88900,9 @@ } }, "jayson": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz", - "integrity": "sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.7.0.tgz", + "integrity": "sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ==", "requires": { "@types/connect": "^3.4.33", "@types/node": "^12.12.54", @@ -88804,6 +88914,7 @@ "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" }, @@ -96983,6 +97094,32 @@ "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", @@ -97023,7 +97160,6 @@ "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "optional": true, "requires": {} } } @@ -105772,7 +105908,6 @@ "@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", @@ -106163,6 +106298,32 @@ "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", @@ -106205,7 +106366,6 @@ "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/target_chains/solana/idl/pyth_solana_receiver.ts b/target_chains/solana/idl/pyth_solana_receiver.ts new file mode 100644 index 0000000000..8dc1bc03b3 --- /dev/null +++ b/target_chains/solana/idl/pyth_solana_receiver.ts @@ -0,0 +1,875 @@ +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: []; + } + ]; + types: [ + { + 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"; + } + ]; + }; + } + ]; + 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: [], + }, + ], + types: [ + { + 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", + }, + ], + }, + }, + ], + 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/idl/wormhole_core_bridge_solana.ts b/target_chains/solana/idl/wormhole_core_bridge_solana.ts new file mode 100644 index 0000000000..239f106c6e --- /dev/null +++ b/target_chains/solana/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/package.json b/target_chains/solana/sdk/js/package.json new file mode 100644 index 0000000000..505afb1d40 --- /dev/null +++ b/target_chains/solana/sdk/js/package.json @@ -0,0 +1,48 @@ +{ + "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": { + "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": { + "@coral-xyz/anchor": "^0.29.0", + "@solana/web3.js": "^1.90.0" + } +} diff --git a/target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts b/target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts new file mode 100644 index 0000000000..bd71318a78 --- /dev/null +++ b/target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts @@ -0,0 +1,10 @@ +import { Connection, Keypair } from "@solana/web3.js"; +import { PythSolanaReceiverConnection } from "../index"; +import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; + +test("Initialize PythSolanaReceiverConnection", () => { + const pythSolanaReceiverConnection = new PythSolanaReceiverConnection({ + connection: new Connection("https://api.mainnet-beta.solana.com"), + wallet: new NodeWallet(new Keypair()), + }); +}); diff --git a/target_chains/solana/sdk/js/src/address.ts b/target_chains/solana/sdk/js/src/address.ts new file mode 100644 index 0000000000..4d3172e48c --- /dev/null +++ b/target_chains/solana/sdk/js/src/address.ts @@ -0,0 +1,8 @@ +import { PublicKey } from "@solana/web3.js"; + +export const DEFAULT_RECEIVER_PROGRAM_ID = new PublicKey( + "rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ" +); +export const DEFAULT_WORMHOLE_PROGRAM_ID = new PublicKey( + "HDwcJBJXjL9FpJ7UBsYBtaDjsBUhuLCUYoz3zr8SWWaQ" +); diff --git a/target_chains/solana/sdk/js/src/index.ts b/target_chains/solana/sdk/js/src/index.ts new file mode 100644 index 0000000000..95c4ff4fbe --- /dev/null +++ b/target_chains/solana/sdk/js/src/index.ts @@ -0,0 +1,44 @@ +import { AnchorProvider, Program, Wallet } from "@coral-xyz/anchor"; +import { Connection } from "@solana/web3.js"; +import { + PythSolanaReceiver, + IDL as PythSolanaReceiverIdl, +} 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, +} from "./address"; + +export class PythSolanaReceiverConnection { + readonly connection: Connection; + readonly wallet: Wallet; + private readonly provider: AnchorProvider; + readonly receiver: Program; + readonly wormhole: Program; + + constructor({ + connection, + wallet, + }: { + connection: Connection; + wallet: Wallet; + }) { + this.connection = connection; + this.wallet = wallet; + this.provider = new AnchorProvider(this.connection, this.wallet, {}); + this.receiver = new Program( + PythSolanaReceiverIdl as PythSolanaReceiver, + DEFAULT_RECEIVER_PROGRAM_ID, + this.provider + ); + this.wormhole = new Program( + WormholeCoreBridgeSolanaIdl as WormholeCoreBridgeSolana, + DEFAULT_WORMHOLE_PROGRAM_ID, + this.provider + ); + } +} diff --git a/target_chains/solana/sdk/js/tsconfig.json b/target_chains/solana/sdk/js/tsconfig.json new file mode 100644 index 0000000000..272c2a0b39 --- /dev/null +++ b/target_chains/solana/sdk/js/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../../tsconfig.base.json", + "include": ["src"], + "exclude": ["node_modules", "**/__tests__/*"], + "compilerOptions": { + "outDir": "./lib" + } +} From dc687d108874d4d09fea26857a95ebb642d01b71 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 20 Feb 2024 15:46:03 +0000 Subject: [PATCH 16/64] Checkpoint --- .../PythSolanaReceiverConnection.test.ts | 4 ++++ target_chains/solana/sdk/js/src/index.ts | 17 +++++++++++------ target_chains/solana/sdk/js/tsconfig.json | 3 ++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts b/target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts index bd71318a78..0328161463 100644 --- a/target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts +++ b/target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts @@ -2,9 +2,13 @@ import { Connection, Keypair } from "@solana/web3.js"; import { PythSolanaReceiverConnection } from "../index"; import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; +const vaa = + "UE5BVQEAAAADuAEAAAADDQFBId3/+IeAS8A2nb5/HlZxBEQe4mHKjcoVENc/Rm7wyTAC5C9MnSMQsP4QvliB3eQpvY/2YvsDTnHLkYmJfpPYAQIU5tICRLdU24JxkNiPKvJEqZp6WNxhrLCXFI4srfiwXQPIRmqoja59k4mwFBaKJ8WlB+Uep/L1NK5nE5xCxS9CAAN2rWcOLRPIB1dz2t1xmCWBkN2DvDspSWyzKND4docILQapDiuAQVefjB31IunVqOZLKQZsu0DsvZElrtPBmEPWAAQWVCsi3KBxzTdVp9LlzJaIv7DVm2wMe9pHje/Ibkm55QlU2k1IwgZ/mqJQvfAbKddol56zpnwFfmKe2Pq6IdYlAQaAtEIThAvXWW25dOF+Wu7lFI+9AknEl/4y9Z7pJ3EiZUDzqmXHZeHRRJryT7pLAilJfLiHyO3bQNoC/BuVHqwdAAenAh59NVSdwZCCnzFwjoaNHbUqKmR2B8J6Kr7FuyPtQ0U4jhy0cyNzZhZJArwHT0dR4JLTpigKDuEeJObucYl0AQhBQc1oBd9B3mTnU2j5AeR3O00NvMr6eWlJRbanSHhG8Erfx17DIZZIAMr+Jj8EkqnrELYm658kgG9as4TrabG0AAoQKC1oyobtDj2GOhOGdYynAJwweb1ZoWpFIyX2GOWe0CqOZO0qPAZPbT614KvFZj2OdcAkPcaKMa4VWahCOod4AQuXxd6FbaoLRA1KUzXRUmm1kNQe0zhk+6k5FumSr1vOAgJMxtB1o6WAX2BMWelHNMUSuUtll6QaWQWrTVPYjP9gAA3BBxvoDFIEoMiD2mBwzEYl0W0B79AQMZvAOdSj+73UKDRGd979tFzq0lHF8IbkHEMX9UZzGQ+T9y2Z9sreYhZHAQ6N0g2/xqcEvEwVB8C+T+f4S7Yl0vB/mjnBwbsfVLzm2Umrup6sLYloPrmK3AJp/R6hqTsEXOsCCKruTdRG4wXEARDn+uyFB7ZiEjmIQhJYXqf5QRYwHrRCEoiCTh/pO+BJeUALaDjBSr5N+Cnl7FfvgOCikd8+R9qMsGPlOo2G5HoJABIV84lFRp8uKUXtwB46AU7wkfhHWeSshcwJbkyXg7dD1wxFJ+Hy28rfPAqV3gMv8LKVHY5Ee25gnZNyM3WhrCK3AGXUt+wAAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAACfDxZAUFVV1YAAAAAAAeERn8AACcQj8wU+urWYoD6dB6yyGkB9U+Tzl0BAFUA/2FJGpMREt3xvYFHzRtkE3X3n1glEm1mVICHRjT9Cs4AAABFLgfEuwAAAAAKVPIT////+AAAAABl1LfsAAAAAGXUt+wAAABE6Eig0AAAAAAKVzi9Cs/sAHK3m95h50ljeYl8psvjLLBdnkrmJFVyVxDlCrX7r0egOUachX25eerI62UL/69fEeRjq7S3E8aKpqwuIpgGG3xHDpwdFkJOCmilco3LfDVRkpnLvEanm+1TjcX5AyuCLGERMjGRTQdklq5XWjkOvfOaZetYB3XOYJCfHRh2z1YUtJZNQtTtkUME9Ty2OuU3Zpmz3DGwCXtae30yUSMgSml45JzQkIEzBGy71jiwnjUcmGpfnFqtJlGsLuv5yA5VoZr2mNH0"; test("Initialize PythSolanaReceiverConnection", () => { const pythSolanaReceiverConnection = new PythSolanaReceiverConnection({ connection: new Connection("https://api.mainnet-beta.solana.com"), wallet: new NodeWallet(new Keypair()), }); + + console.log(pythSolanaReceiverConnection.postPriceUpdate(vaa)); }); diff --git a/target_chains/solana/sdk/js/src/index.ts b/target_chains/solana/sdk/js/src/index.ts index 95c4ff4fbe..394629eb50 100644 --- a/target_chains/solana/sdk/js/src/index.ts +++ b/target_chains/solana/sdk/js/src/index.ts @@ -1,17 +1,16 @@ import { AnchorProvider, Program, Wallet } from "@coral-xyz/anchor"; import { Connection } from "@solana/web3.js"; -import { - PythSolanaReceiver, - IDL as PythSolanaReceiverIdl, -} from "../../../idl/pyth_solana_receiver"; +import { PythSolanaReceiver } from "./idl/pyth_solana_receiver"; +import Idl from "./idl/pyth_solana_receiver.json"; import { WormholeCoreBridgeSolana, IDL as WormholeCoreBridgeSolanaIdl, -} from "../../../idl/wormhole_core_bridge_solana"; +} from "./idl/wormhole_core_bridge_solana"; import { DEFAULT_RECEIVER_PROGRAM_ID, DEFAULT_WORMHOLE_PROGRAM_ID, } from "./address"; +import { PublicKey, SystemProgram } from "@solana/web3.js"; export class PythSolanaReceiverConnection { readonly connection: Connection; @@ -31,7 +30,7 @@ export class PythSolanaReceiverConnection { this.wallet = wallet; this.provider = new AnchorProvider(this.connection, this.wallet, {}); this.receiver = new Program( - PythSolanaReceiverIdl as PythSolanaReceiver, + Idl as PythSolanaReceiver, DEFAULT_RECEIVER_PROGRAM_ID, this.provider ); @@ -41,4 +40,10 @@ export class PythSolanaReceiverConnection { this.provider ); } + + async postPriceUpdate(vaa: string): Promise { + const vaa2 = Buffer.from(vaa, "base64"); + console.log(vaa2.toString("hex")); + return SystemProgram.programId; + } } diff --git a/target_chains/solana/sdk/js/tsconfig.json b/target_chains/solana/sdk/js/tsconfig.json index 272c2a0b39..f369de9068 100644 --- a/target_chains/solana/sdk/js/tsconfig.json +++ b/target_chains/solana/sdk/js/tsconfig.json @@ -1,8 +1,9 @@ { "extends": "../../../../tsconfig.base.json", - "include": ["src"], + "include": ["src/**/*.ts", "src/**/*.json"], "exclude": ["node_modules", "**/__tests__/*"], "compilerOptions": { + "rootDir": "src/", "outDir": "./lib" } } From 00459087ccce2bd2f2605ee9f576024d36973d97 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 20 Feb 2024 16:51:34 +0000 Subject: [PATCH 17/64] Gitignore --- target_chains/solana/sdk/js/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 target_chains/solana/sdk/js/.gitignore diff --git a/target_chains/solana/sdk/js/.gitignore b/target_chains/solana/sdk/js/.gitignore new file mode 100644 index 0000000000..491fc35975 --- /dev/null +++ b/target_chains/solana/sdk/js/.gitignore @@ -0,0 +1,2 @@ +node_modules +lib From d8757d52842e44e772d7c546fbe53e9dc3abe7ee Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 20 Feb 2024 16:57:19 +0000 Subject: [PATCH 18/64] Cleanup --- target_chains/solana/sdk/js/package.json | 3 +- target_chains/solana/sdk/js/src/index.ts | 35 ++++++++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/target_chains/solana/sdk/js/package.json b/target_chains/solana/sdk/js/package.json index 505afb1d40..4187a7ee86 100644 --- a/target_chains/solana/sdk/js/package.json +++ b/target_chains/solana/sdk/js/package.json @@ -43,6 +43,7 @@ }, "dependencies": { "@coral-xyz/anchor": "^0.29.0", - "@solana/web3.js": "^1.90.0" + "@solana/web3.js": "^1.90.0", + "@pythnetwork/price-service-sdk": "*" } } diff --git a/target_chains/solana/sdk/js/src/index.ts b/target_chains/solana/sdk/js/src/index.ts index 394629eb50..4f742a5d6f 100644 --- a/target_chains/solana/sdk/js/src/index.ts +++ b/target_chains/solana/sdk/js/src/index.ts @@ -1,5 +1,5 @@ import { AnchorProvider, Program, Wallet } from "@coral-xyz/anchor"; -import { Connection } from "@solana/web3.js"; +import { Connection, TransactionInstruction } from "@solana/web3.js"; import { PythSolanaReceiver } from "./idl/pyth_solana_receiver"; import Idl from "./idl/pyth_solana_receiver.json"; import { @@ -10,7 +10,9 @@ import { DEFAULT_RECEIVER_PROGRAM_ID, DEFAULT_WORMHOLE_PROGRAM_ID, } from "./address"; -import { PublicKey, SystemProgram } from "@solana/web3.js"; +import { PublicKey, Keypair } from "@solana/web3.js"; +import { parseAccumulatorUpdateData } from "@pythnetwork/price-service-sdk"; +import { VAA_SPLIT_INDEX, VAA_START } from "./constants"; export class PythSolanaReceiverConnection { readonly connection: Connection; @@ -42,8 +44,31 @@ export class PythSolanaReceiverConnection { } async postPriceUpdate(vaa: string): Promise { - const vaa2 = Buffer.from(vaa, "base64"); - console.log(vaa2.toString("hex")); - return SystemProgram.programId; + const accumulatorUpdateData = parseAccumulatorUpdateData( + Buffer.from(vaa, "base64") + ); + + const encodedVaaKeypair = new Keypair(); + const encodedVaaSize = accumulatorUpdateData.vaa.length + VAA_START; + + const firstTransactionInstructions: TransactionInstruction[] = [ + await this.wormhole.account.encodedVaa.createInstruction( + encodedVaaKeypair, + encodedVaaSize + ), + ]; + + await this.wormhole.methods + .writeEncodedVaa({ + index: 0, + data: accumulatorUpdateData.vaa.subarray(0, VAA_SPLIT_INDEX), + }) + .accounts({ + draftVaa: encodedVaaKeypair.publicKey, + }) + .preInstructions(firstTransactionInstructions) + .rpc(); + + return encodedVaaKeypair.publicKey; } } From b854868aaacfe2919a0ee71e66dd50de0dca4b8b Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 20 Feb 2024 16:57:39 +0000 Subject: [PATCH 19/64] Cleanup --- target_chains/solana/sdk/js/src/constants.ts | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 target_chains/solana/sdk/js/src/constants.ts diff --git a/target_chains/solana/sdk/js/src/constants.ts b/target_chains/solana/sdk/js/src/constants.ts new file mode 100644 index 0000000000..d3f3a33953 --- /dev/null +++ b/target_chains/solana/sdk/js/src/constants.ts @@ -0,0 +1,2 @@ +export const VAA_START = 46; +export const VAA_SPLIT_INDEX = 846; From 97f8bd0c3ae8df48417a02825eb9162143de7b20 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 20 Feb 2024 20:33:28 +0000 Subject: [PATCH 20/64] Continue building the sdk --- target_chains/solana/sdk/js/package.json | 4 +- target_chains/solana/sdk/js/src/address.ts | 23 + target_chains/solana/sdk/js/src/constants.ts | 2 + .../sdk/js/src/idl/pyth_solana_receiver.json | 602 +++ .../sdk/js/src/idl/pyth_solana_receiver.ts | 1081 ++++++ .../js/src/idl/wormhole_core_bridge_solana.ts | 3211 +++++++++++++++++ target_chains/solana/sdk/js/src/index.ts | 75 +- target_chains/solana/sdk/js/src/vaa.ts | 18 + 8 files changed, 5008 insertions(+), 8 deletions(-) create mode 100644 target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.json create mode 100644 target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.ts create mode 100644 target_chains/solana/sdk/js/src/idl/wormhole_core_bridge_solana.ts create mode 100644 target_chains/solana/sdk/js/src/vaa.ts diff --git a/target_chains/solana/sdk/js/package.json b/target_chains/solana/sdk/js/package.json index 4187a7ee86..e5bef105ef 100644 --- a/target_chains/solana/sdk/js/package.json +++ b/target_chains/solana/sdk/js/package.json @@ -43,7 +43,7 @@ }, "dependencies": { "@coral-xyz/anchor": "^0.29.0", - "@solana/web3.js": "^1.90.0", - "@pythnetwork/price-service-sdk": "*" + "@pythnetwork/price-service-sdk": "*", + "@solana/web3.js": "^1.90.0" } } diff --git a/target_chains/solana/sdk/js/src/address.ts b/target_chains/solana/sdk/js/src/address.ts index 4d3172e48c..d5f417b82d 100644 --- a/target_chains/solana/sdk/js/src/address.ts +++ b/target_chains/solana/sdk/js/src/address.ts @@ -6,3 +6,26 @@ export const DEFAULT_RECEIVER_PROGRAM_ID = new PublicKey( 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/src/constants.ts b/target_chains/solana/sdk/js/src/constants.ts index d3f3a33953..b67067a51e 100644 --- a/target_chains/solana/sdk/js/src/constants.ts +++ b/target_chains/solana/sdk/js/src/constants.ts @@ -1,2 +1,4 @@ export const VAA_START = 46; export const VAA_SPLIT_INDEX = 846; +export const VAA_SIGNATURE_SIZE = 66; +export const DEFAULT_TREASURY_ID = 0; diff --git a/target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.json b/target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.json new file mode 100644 index 0000000000..d21710d1a3 --- /dev/null +++ b/target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.json @@ -0,0 +1,602 @@ +{ + "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/src/idl/pyth_solana_receiver.ts b/target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.ts new file mode 100644 index 0000000000..46829eb5a4 --- /dev/null +++ b/target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.ts @@ -0,0 +1,1081 @@ +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: "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: "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/src/idl/wormhole_core_bridge_solana.ts b/target_chains/solana/sdk/js/src/idl/wormhole_core_bridge_solana.ts new file mode 100644 index 0000000000..239f106c6e --- /dev/null +++ b/target_chains/solana/sdk/js/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/src/index.ts b/target_chains/solana/sdk/js/src/index.ts index 4f742a5d6f..93090bc5af 100644 --- a/target_chains/solana/sdk/js/src/index.ts +++ b/target_chains/solana/sdk/js/src/index.ts @@ -1,5 +1,9 @@ -import { AnchorProvider, Program, Wallet } from "@coral-xyz/anchor"; -import { Connection, TransactionInstruction } from "@solana/web3.js"; +import { AnchorProvider, Program } from "@coral-xyz/anchor"; +import { + ComputeBudgetProgram, + Connection, + TransactionInstruction, +} from "@solana/web3.js"; import { PythSolanaReceiver } from "./idl/pyth_solana_receiver"; import Idl from "./idl/pyth_solana_receiver.json"; import { @@ -9,10 +13,15 @@ import { import { DEFAULT_RECEIVER_PROGRAM_ID, DEFAULT_WORMHOLE_PROGRAM_ID, + getConfigPda, + getGuardianSetPda, + getTreasuryPda, } from "./address"; import { PublicKey, Keypair } from "@solana/web3.js"; import { parseAccumulatorUpdateData } from "@pythnetwork/price-service-sdk"; -import { VAA_SPLIT_INDEX, VAA_START } from "./constants"; +import { DEFAULT_TREASURY_ID, VAA_SPLIT_INDEX, VAA_START } from "./constants"; +import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; +import { getGuardianSetIndex } from "./vaa"; export class PythSolanaReceiverConnection { readonly connection: Connection; @@ -51,14 +60,23 @@ export class PythSolanaReceiverConnection { const encodedVaaKeypair = new Keypair(); const encodedVaaSize = accumulatorUpdateData.vaa.length + VAA_START; + const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); + const firstTransactionInstructions: TransactionInstruction[] = [ await this.wormhole.account.encodedVaa.createInstruction( encodedVaaKeypair, encodedVaaSize ), + await this.wormhole.methods + .initEncodedVaa() + .accounts({ + encodedVaa: encodedVaaKeypair.publicKey, + }) + .instruction(), ]; - await this.wormhole.methods + // First transaction + const firstTransaction = await this.wormhole.methods .writeEncodedVaa({ index: 0, data: accumulatorUpdateData.vaa.subarray(0, VAA_SPLIT_INDEX), @@ -67,8 +85,53 @@ export class PythSolanaReceiverConnection { draftVaa: encodedVaaKeypair.publicKey, }) .preInstructions(firstTransactionInstructions) - .rpc(); + .signers([encodedVaaKeypair]) + .transaction(); - return encodedVaaKeypair.publicKey; + const priceUpdateKeypair = new Keypair(); + const secondTransactionInstructions: TransactionInstruction[] = [ + ComputeBudgetProgram.setComputeUnitLimit({ units: 600000 }), + await this.wormhole.methods + .writeEncodedVaa({ + index: VAA_SPLIT_INDEX, + data: accumulatorUpdateData.vaa.subarray(VAA_SPLIT_INDEX), + }) + .accounts({ + draftVaa: encodedVaaKeypair.publicKey, + }) + .instruction(), + await this.wormhole.methods + .verifyEncodedVaaV1() + .accounts({ + guardianSet: getGuardianSetPda(guardianSetIndex), + draftVaa: encodedVaaKeypair.publicKey, + }) + .instruction(), + ]; + + // Second transaction + const secondTransaction = await this.receiver.methods + .postUpdate({ + merklePriceUpdate: accumulatorUpdateData.updates[0], + treasuryId: DEFAULT_TREASURY_ID, + }) + .accounts({ + encodedVaa: encodedVaaKeypair.publicKey, + priceUpdateAccount: priceUpdateKeypair.publicKey, + treasury: getTreasuryPda(DEFAULT_TREASURY_ID), + config: getConfigPda(), + }) + .signers([priceUpdateKeypair]) + .preInstructions(secondTransactionInstructions) + .transaction(); + + await this.provider.sendAll( + [ + { tx: firstTransaction, signers: [encodedVaaKeypair] }, + { tx: secondTransaction, signers: [priceUpdateKeypair] }, + ], + { skipPreflight: true } + ); + return priceUpdateKeypair.publicKey; } } diff --git a/target_chains/solana/sdk/js/src/vaa.ts b/target_chains/solana/sdk/js/src/vaa.ts new file mode 100644 index 0000000000..f4a497509b --- /dev/null +++ b/target_chains/solana/sdk/js/src/vaa.ts @@ -0,0 +1,18 @@ +import { VAA_SIGNATURE_SIZE } from "./constants"; + +export function getGuardianSetIndex(vaa: Buffer) { + return vaa.readUInt32BE(1); +} + +export function trimSignatures(vaa: Buffer, n: number): Buffer { + const currentNumSignatures = vaa[5]; + if (n > currentNumSignatures) { + throw new Error( + "Resulting VAA can't have more signatures than the original VAA" + ); + } + return Buffer.concat([ + vaa.subarray(0, 5 + n * VAA_SIGNATURE_SIZE), + vaa.subarray(5 + currentNumSignatures * VAA_SIGNATURE_SIZE), + ]); +} From a5f0cae917c2784ab32db209a40b97091b988f2e Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 21 Feb 2024 12:06:15 +0000 Subject: [PATCH 21/64] build function --- target_chains/solana/sdk/js/src/index.ts | 28 ++++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/target_chains/solana/sdk/js/src/index.ts b/target_chains/solana/sdk/js/src/index.ts index 93090bc5af..891aada67e 100644 --- a/target_chains/solana/sdk/js/src/index.ts +++ b/target_chains/solana/sdk/js/src/index.ts @@ -2,6 +2,8 @@ import { AnchorProvider, Program } from "@coral-xyz/anchor"; import { ComputeBudgetProgram, Connection, + Signer, + Transaction, TransactionInstruction, } from "@solana/web3.js"; import { PythSolanaReceiver } from "./idl/pyth_solana_receiver"; @@ -39,7 +41,9 @@ export class PythSolanaReceiverConnection { }) { this.connection = connection; this.wallet = wallet; - this.provider = new AnchorProvider(this.connection, this.wallet, {}); + this.provider = new AnchorProvider(this.connection, this.wallet, { + commitment: "processed", + }); // To do make this configurable this.receiver = new Program( Idl as PythSolanaReceiver, DEFAULT_RECEIVER_PROGRAM_ID, @@ -52,7 +56,10 @@ export class PythSolanaReceiverConnection { ); } - async postPriceUpdate(vaa: string): Promise { + async buildPriceUpdate(vaa: string): Promise<{ + transactions: { tx: Transaction; signers: Signer[] }[]; + priceUpdateAddress: PublicKey; + }> { const accumulatorUpdateData = parseAccumulatorUpdateData( Buffer.from(vaa, "base64") ); @@ -125,13 +132,20 @@ export class PythSolanaReceiverConnection { .preInstructions(secondTransactionInstructions) .transaction(); - await this.provider.sendAll( - [ + return { + transactions: [ { tx: firstTransaction, signers: [encodedVaaKeypair] }, { tx: secondTransaction, signers: [priceUpdateKeypair] }, ], - { skipPreflight: true } - ); - return priceUpdateKeypair.publicKey; + priceUpdateAddress: priceUpdateKeypair.publicKey, + }; + } + + async postPriceUpdate(vaa: string): Promise { + let transactionsToSend = await this.buildPriceUpdate(vaa); + await this.provider.sendAll(transactionsToSend.transactions, { + skipPreflight: true, + }); + return transactionsToSend.priceUpdateAddress; } } From 832849654d26e3188be4ec6c628e6ae463ccf25d Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 21 Feb 2024 19:45:27 +0000 Subject: [PATCH 22/64] Remove files --- .../__tests__/PythSolanaReceiverConnection.test.ts | 14 -------------- target_chains/solana/sdk/js/src/index.ts | 4 ++-- 2 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts diff --git a/target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts b/target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts deleted file mode 100644 index 0328161463..0000000000 --- a/target_chains/solana/sdk/js/src/__tests__/PythSolanaReceiverConnection.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Connection, Keypair } from "@solana/web3.js"; -import { PythSolanaReceiverConnection } from "../index"; -import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; - -const vaa = - "UE5BVQEAAAADuAEAAAADDQFBId3/+IeAS8A2nb5/HlZxBEQe4mHKjcoVENc/Rm7wyTAC5C9MnSMQsP4QvliB3eQpvY/2YvsDTnHLkYmJfpPYAQIU5tICRLdU24JxkNiPKvJEqZp6WNxhrLCXFI4srfiwXQPIRmqoja59k4mwFBaKJ8WlB+Uep/L1NK5nE5xCxS9CAAN2rWcOLRPIB1dz2t1xmCWBkN2DvDspSWyzKND4docILQapDiuAQVefjB31IunVqOZLKQZsu0DsvZElrtPBmEPWAAQWVCsi3KBxzTdVp9LlzJaIv7DVm2wMe9pHje/Ibkm55QlU2k1IwgZ/mqJQvfAbKddol56zpnwFfmKe2Pq6IdYlAQaAtEIThAvXWW25dOF+Wu7lFI+9AknEl/4y9Z7pJ3EiZUDzqmXHZeHRRJryT7pLAilJfLiHyO3bQNoC/BuVHqwdAAenAh59NVSdwZCCnzFwjoaNHbUqKmR2B8J6Kr7FuyPtQ0U4jhy0cyNzZhZJArwHT0dR4JLTpigKDuEeJObucYl0AQhBQc1oBd9B3mTnU2j5AeR3O00NvMr6eWlJRbanSHhG8Erfx17DIZZIAMr+Jj8EkqnrELYm658kgG9as4TrabG0AAoQKC1oyobtDj2GOhOGdYynAJwweb1ZoWpFIyX2GOWe0CqOZO0qPAZPbT614KvFZj2OdcAkPcaKMa4VWahCOod4AQuXxd6FbaoLRA1KUzXRUmm1kNQe0zhk+6k5FumSr1vOAgJMxtB1o6WAX2BMWelHNMUSuUtll6QaWQWrTVPYjP9gAA3BBxvoDFIEoMiD2mBwzEYl0W0B79AQMZvAOdSj+73UKDRGd979tFzq0lHF8IbkHEMX9UZzGQ+T9y2Z9sreYhZHAQ6N0g2/xqcEvEwVB8C+T+f4S7Yl0vB/mjnBwbsfVLzm2Umrup6sLYloPrmK3AJp/R6hqTsEXOsCCKruTdRG4wXEARDn+uyFB7ZiEjmIQhJYXqf5QRYwHrRCEoiCTh/pO+BJeUALaDjBSr5N+Cnl7FfvgOCikd8+R9qMsGPlOo2G5HoJABIV84lFRp8uKUXtwB46AU7wkfhHWeSshcwJbkyXg7dD1wxFJ+Hy28rfPAqV3gMv8LKVHY5Ee25gnZNyM3WhrCK3AGXUt+wAAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAACfDxZAUFVV1YAAAAAAAeERn8AACcQj8wU+urWYoD6dB6yyGkB9U+Tzl0BAFUA/2FJGpMREt3xvYFHzRtkE3X3n1glEm1mVICHRjT9Cs4AAABFLgfEuwAAAAAKVPIT////+AAAAABl1LfsAAAAAGXUt+wAAABE6Eig0AAAAAAKVzi9Cs/sAHK3m95h50ljeYl8psvjLLBdnkrmJFVyVxDlCrX7r0egOUachX25eerI62UL/69fEeRjq7S3E8aKpqwuIpgGG3xHDpwdFkJOCmilco3LfDVRkpnLvEanm+1TjcX5AyuCLGERMjGRTQdklq5XWjkOvfOaZetYB3XOYJCfHRh2z1YUtJZNQtTtkUME9Ty2OuU3Zpmz3DGwCXtae30yUSMgSml45JzQkIEzBGy71jiwnjUcmGpfnFqtJlGsLuv5yA5VoZr2mNH0"; -test("Initialize PythSolanaReceiverConnection", () => { - const pythSolanaReceiverConnection = new PythSolanaReceiverConnection({ - connection: new Connection("https://api.mainnet-beta.solana.com"), - wallet: new NodeWallet(new Keypair()), - }); - - console.log(pythSolanaReceiverConnection.postPriceUpdate(vaa)); -}); diff --git a/target_chains/solana/sdk/js/src/index.ts b/target_chains/solana/sdk/js/src/index.ts index 891aada67e..e9995c17b5 100644 --- a/target_chains/solana/sdk/js/src/index.ts +++ b/target_chains/solana/sdk/js/src/index.ts @@ -43,7 +43,7 @@ export class PythSolanaReceiverConnection { this.wallet = wallet; this.provider = new AnchorProvider(this.connection, this.wallet, { commitment: "processed", - }); // To do make this configurable + }); // TO DO make this configurable this.receiver = new Program( Idl as PythSolanaReceiver, DEFAULT_RECEIVER_PROGRAM_ID, @@ -56,7 +56,7 @@ export class PythSolanaReceiverConnection { ); } - async buildPriceUpdate(vaa: string): Promise<{ + async buildPostPriceUpdate(vaa: string): Promise<{ transactions: { tx: Transaction; signers: Signer[] }[]; priceUpdateAddress: PublicKey; }> { From a0f95ea80a4f0ed795d21971fe7dd877d709c959 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 21 Feb 2024 19:50:02 +0000 Subject: [PATCH 23/64] Remove files --- .../solana/idl/pyth_solana_receiver.ts | 875 ----- .../solana/idl/wormhole_core_bridge_solana.ts | 3211 ----------------- 2 files changed, 4086 deletions(-) delete mode 100644 target_chains/solana/idl/pyth_solana_receiver.ts delete mode 100644 target_chains/solana/idl/wormhole_core_bridge_solana.ts diff --git a/target_chains/solana/idl/pyth_solana_receiver.ts b/target_chains/solana/idl/pyth_solana_receiver.ts deleted file mode 100644 index 8dc1bc03b3..0000000000 --- a/target_chains/solana/idl/pyth_solana_receiver.ts +++ /dev/null @@ -1,875 +0,0 @@ -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: []; - } - ]; - types: [ - { - 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"; - } - ]; - }; - } - ]; - 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: [], - }, - ], - types: [ - { - 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", - }, - ], - }, - }, - ], - 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/idl/wormhole_core_bridge_solana.ts b/target_chains/solana/idl/wormhole_core_bridge_solana.ts deleted file mode 100644 index 239f106c6e..0000000000 --- a/target_chains/solana/idl/wormhole_core_bridge_solana.ts +++ /dev/null @@ -1,3211 +0,0 @@ -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", - }, - ], -}; From 131d8ce184817fbe33990cf8a6bdf02f2311d257 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 21 Feb 2024 19:53:41 +0000 Subject: [PATCH 24/64] Rename --- target_chains/solana/sdk/js/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/solana/sdk/js/src/index.ts b/target_chains/solana/sdk/js/src/index.ts index e9995c17b5..a3e1469d46 100644 --- a/target_chains/solana/sdk/js/src/index.ts +++ b/target_chains/solana/sdk/js/src/index.ts @@ -142,7 +142,7 @@ export class PythSolanaReceiverConnection { } async postPriceUpdate(vaa: string): Promise { - let transactionsToSend = await this.buildPriceUpdate(vaa); + let transactionsToSend = await this.buildPostPriceUpdate(vaa); await this.provider.sendAll(transactionsToSend.transactions, { skipPreflight: true, }); From f3fc9ef6776c85d7fe08583e76c03223d798ba61 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Fri, 23 Feb 2024 19:37:29 +0000 Subject: [PATCH 25/64] Refactor : make transaction builder --- target_chains/solana/sdk/js/src/constants.ts | 2 +- target_chains/solana/sdk/js/src/index.ts | 149 +++++++++++++------ 2 files changed, 108 insertions(+), 43 deletions(-) diff --git a/target_chains/solana/sdk/js/src/constants.ts b/target_chains/solana/sdk/js/src/constants.ts index b67067a51e..c6dce06777 100644 --- a/target_chains/solana/sdk/js/src/constants.ts +++ b/target_chains/solana/sdk/js/src/constants.ts @@ -1,4 +1,4 @@ export const VAA_START = 46; -export const VAA_SPLIT_INDEX = 846; +export const VAA_SPLIT_INDEX = 800; export const VAA_SIGNATURE_SIZE = 66; export const DEFAULT_TREASURY_ID = 0; diff --git a/target_chains/solana/sdk/js/src/index.ts b/target_chains/solana/sdk/js/src/index.ts index a3e1469d46..ef1b22e1ea 100644 --- a/target_chains/solana/sdk/js/src/index.ts +++ b/target_chains/solana/sdk/js/src/index.ts @@ -3,8 +3,9 @@ import { ComputeBudgetProgram, Connection, Signer, - Transaction, TransactionInstruction, + TransactionMessage, + VersionedTransaction, } from "@solana/web3.js"; import { PythSolanaReceiver } from "./idl/pyth_solana_receiver"; import Idl from "./idl/pyth_solana_receiver.json"; @@ -25,6 +26,69 @@ import { DEFAULT_TREASURY_ID, VAA_SPLIT_INDEX, VAA_START } from "./constants"; import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; import { getGuardianSetIndex } from "./vaa"; +export class TransactionBuilder { + private transactionInstructions: { + instructions: TransactionInstruction[]; + signers: Signer[]; + }[] = []; + private pythSolanaReceiverConnection: PythSolanaReceiverConnection; + private encodedVaaAddress: PublicKey | undefined; + private priceUpdateAddress: PublicKey | undefined; + + constructor(pythSolanaReceiverConnection: PythSolanaReceiverConnection) { + this.pythSolanaReceiverConnection = pythSolanaReceiverConnection; + } + + async addPostPriceUpdate(vaa: string) { + const { transactions, priceUpdateAddress, encodedVaaAddress } = + await this.pythSolanaReceiverConnection.buildPostPriceUpdateInstructions( + vaa + ); + this.encodedVaaAddress = encodedVaaAddress; + this.priceUpdateAddress = priceUpdateAddress; + this.transactionInstructions.push(...transactions); + } + + async addArbitraryInstruction( + instruction: ( + priceUpdateAddress: PublicKey + ) => Promise, + signers: Signer[] + ) { + if (this.priceUpdateAddress === undefined) { + throw new Error( + "You need to call addPostPriceUpdate before calling addArbitraryInstruction" + ); + } + this.transactionInstructions[ + this.transactionInstructions.length - 1 + ].instructions.push(await instruction(this.priceUpdateAddress)); + this.transactionInstructions[ + this.transactionInstructions.length - 1 + ].signers.push(...signers); + } + + async getTransactions(): Promise< + { tx: VersionedTransaction; signers: Signer[] }[] + > { + const blockhash = ( + await this.pythSolanaReceiverConnection.connection.getLatestBlockhash() + ).blockhash; + return this.transactionInstructions.map(({ instructions, signers }) => { + return { + tx: new VersionedTransaction( + new TransactionMessage({ + recentBlockhash: blockhash, + instructions: instructions, + payerKey: this.pythSolanaReceiverConnection.wallet.publicKey, + }).compileToV0Message() + ), + signers: signers, + }; + }); + } +} + export class PythSolanaReceiverConnection { readonly connection: Connection; readonly wallet: Wallet; @@ -56,9 +120,17 @@ export class PythSolanaReceiverConnection { ); } - async buildPostPriceUpdate(vaa: string): Promise<{ - transactions: { tx: Transaction; signers: Signer[] }[]; + public getBuilder(): TransactionBuilder { + return new TransactionBuilder(this); + } + + async buildPostPriceUpdateInstructions(vaa: string): Promise<{ + transactions: { + instructions: TransactionInstruction[]; + signers: Signer[]; + }[]; priceUpdateAddress: PublicKey; + encodedVaaAddress: PublicKey; }> { const accumulatorUpdateData = parseAccumulatorUpdateData( Buffer.from(vaa, "base64") @@ -80,21 +152,18 @@ export class PythSolanaReceiverConnection { encodedVaa: encodedVaaKeypair.publicKey, }) .instruction(), + await this.wormhole.methods + .writeEncodedVaa({ + index: 0, + data: accumulatorUpdateData.vaa.subarray(0, VAA_SPLIT_INDEX), + }) + .accounts({ + draftVaa: encodedVaaKeypair.publicKey, + }) + .signers([encodedVaaKeypair]) + .instruction(), ]; - // First transaction - const firstTransaction = await this.wormhole.methods - .writeEncodedVaa({ - index: 0, - data: accumulatorUpdateData.vaa.subarray(0, VAA_SPLIT_INDEX), - }) - .accounts({ - draftVaa: encodedVaaKeypair.publicKey, - }) - .preInstructions(firstTransactionInstructions) - .signers([encodedVaaKeypair]) - .transaction(); - const priceUpdateKeypair = new Keypair(); const secondTransactionInstructions: TransactionInstruction[] = [ ComputeBudgetProgram.setComputeUnitLimit({ units: 600000 }), @@ -114,38 +183,34 @@ export class PythSolanaReceiverConnection { draftVaa: encodedVaaKeypair.publicKey, }) .instruction(), + await this.receiver.methods + .postUpdate({ + merklePriceUpdate: accumulatorUpdateData.updates[0], + treasuryId: DEFAULT_TREASURY_ID, + }) + .accounts({ + encodedVaa: encodedVaaKeypair.publicKey, + priceUpdateAccount: priceUpdateKeypair.publicKey, + treasury: getTreasuryPda(DEFAULT_TREASURY_ID), + config: getConfigPda(), + }) + .signers([priceUpdateKeypair]) + .instruction(), ]; - // Second transaction - const secondTransaction = await this.receiver.methods - .postUpdate({ - merklePriceUpdate: accumulatorUpdateData.updates[0], - treasuryId: DEFAULT_TREASURY_ID, - }) - .accounts({ - encodedVaa: encodedVaaKeypair.publicKey, - priceUpdateAccount: priceUpdateKeypair.publicKey, - treasury: getTreasuryPda(DEFAULT_TREASURY_ID), - config: getConfigPda(), - }) - .signers([priceUpdateKeypair]) - .preInstructions(secondTransactionInstructions) - .transaction(); - return { transactions: [ - { tx: firstTransaction, signers: [encodedVaaKeypair] }, - { tx: secondTransaction, signers: [priceUpdateKeypair] }, + { + instructions: firstTransactionInstructions, + signers: [encodedVaaKeypair], + }, + { + instructions: secondTransactionInstructions, + signers: [priceUpdateKeypair], + }, ], priceUpdateAddress: priceUpdateKeypair.publicKey, + encodedVaaAddress: encodedVaaKeypair.publicKey, }; } - - async postPriceUpdate(vaa: string): Promise { - let transactionsToSend = await this.buildPostPriceUpdate(vaa); - await this.provider.sendAll(transactionsToSend.transactions, { - skipPreflight: true, - }); - return transactionsToSend.priceUpdateAddress; - } } From db73a1fe378b8c8ed2fe5c7ba2601333e200ceaa Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Fri, 23 Feb 2024 19:40:30 +0000 Subject: [PATCH 26/64] Make commitment --- target_chains/solana/sdk/js/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target_chains/solana/sdk/js/src/index.ts b/target_chains/solana/sdk/js/src/index.ts index ef1b22e1ea..76b515dfc3 100644 --- a/target_chains/solana/sdk/js/src/index.ts +++ b/target_chains/solana/sdk/js/src/index.ts @@ -106,8 +106,8 @@ export class PythSolanaReceiverConnection { this.connection = connection; this.wallet = wallet; this.provider = new AnchorProvider(this.connection, this.wallet, { - commitment: "processed", - }); // TO DO make this configurable + commitment: connection.commitment, + }); this.receiver = new Program( Idl as PythSolanaReceiver, DEFAULT_RECEIVER_PROGRAM_ID, From 520e5fbe4ab4738d4e5c0ea490641ce9ed2de66a Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 26 Feb 2024 19:05:42 +0000 Subject: [PATCH 27/64] Move --- .../solana/sdk/js/{ => pyth_solana_receiver}/.gitignore | 0 .../solana/sdk/js/{ => pyth_solana_receiver}/package.json | 0 .../solana/sdk/js/{ => pyth_solana_receiver}/src/address.ts | 0 .../solana/sdk/js/{ => pyth_solana_receiver}/src/constants.ts | 0 .../src/idl/pyth_solana_receiver.json | 0 .../{ => pyth_solana_receiver}/src/idl/pyth_solana_receiver.ts | 0 .../src/idl/wormhole_core_bridge_solana.ts | 0 .../solana/sdk/js/{ => pyth_solana_receiver}/src/index.ts | 0 .../solana/sdk/js/{ => pyth_solana_receiver}/src/vaa.ts | 0 .../solana/sdk/js/{ => pyth_solana_receiver}/tsconfig.json | 2 +- 10 files changed, 1 insertion(+), 1 deletion(-) rename target_chains/solana/sdk/js/{ => pyth_solana_receiver}/.gitignore (100%) rename target_chains/solana/sdk/js/{ => pyth_solana_receiver}/package.json (100%) rename target_chains/solana/sdk/js/{ => pyth_solana_receiver}/src/address.ts (100%) rename target_chains/solana/sdk/js/{ => pyth_solana_receiver}/src/constants.ts (100%) rename target_chains/solana/sdk/js/{ => pyth_solana_receiver}/src/idl/pyth_solana_receiver.json (100%) rename target_chains/solana/sdk/js/{ => pyth_solana_receiver}/src/idl/pyth_solana_receiver.ts (100%) rename target_chains/solana/sdk/js/{ => pyth_solana_receiver}/src/idl/wormhole_core_bridge_solana.ts (100%) rename target_chains/solana/sdk/js/{ => pyth_solana_receiver}/src/index.ts (100%) rename target_chains/solana/sdk/js/{ => pyth_solana_receiver}/src/vaa.ts (100%) rename target_chains/solana/sdk/js/{ => pyth_solana_receiver}/tsconfig.json (77%) diff --git a/target_chains/solana/sdk/js/.gitignore b/target_chains/solana/sdk/js/pyth_solana_receiver/.gitignore similarity index 100% rename from target_chains/solana/sdk/js/.gitignore rename to target_chains/solana/sdk/js/pyth_solana_receiver/.gitignore diff --git a/target_chains/solana/sdk/js/package.json b/target_chains/solana/sdk/js/pyth_solana_receiver/package.json similarity index 100% rename from target_chains/solana/sdk/js/package.json rename to target_chains/solana/sdk/js/pyth_solana_receiver/package.json diff --git a/target_chains/solana/sdk/js/src/address.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/address.ts similarity index 100% rename from target_chains/solana/sdk/js/src/address.ts rename to target_chains/solana/sdk/js/pyth_solana_receiver/src/address.ts diff --git a/target_chains/solana/sdk/js/src/constants.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts similarity index 100% rename from target_chains/solana/sdk/js/src/constants.ts rename to target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts diff --git a/target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.json b/target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.json similarity index 100% rename from target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.json rename to target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.json diff --git a/target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.ts similarity index 100% rename from target_chains/solana/sdk/js/src/idl/pyth_solana_receiver.ts rename to target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.ts diff --git a/target_chains/solana/sdk/js/src/idl/wormhole_core_bridge_solana.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/wormhole_core_bridge_solana.ts similarity index 100% rename from target_chains/solana/sdk/js/src/idl/wormhole_core_bridge_solana.ts rename to target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/wormhole_core_bridge_solana.ts diff --git a/target_chains/solana/sdk/js/src/index.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts similarity index 100% rename from target_chains/solana/sdk/js/src/index.ts rename to target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts diff --git a/target_chains/solana/sdk/js/src/vaa.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts similarity index 100% rename from target_chains/solana/sdk/js/src/vaa.ts rename to target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts diff --git a/target_chains/solana/sdk/js/tsconfig.json b/target_chains/solana/sdk/js/pyth_solana_receiver/tsconfig.json similarity index 77% rename from target_chains/solana/sdk/js/tsconfig.json rename to target_chains/solana/sdk/js/pyth_solana_receiver/tsconfig.json index f369de9068..4a55b57b49 100644 --- a/target_chains/solana/sdk/js/tsconfig.json +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig.base.json", + "extends": "../../../../../tsconfig.base.json", "include": ["src/**/*.ts", "src/**/*.json"], "exclude": ["node_modules", "**/__tests__/*"], "compilerOptions": { From eedd8066af1a0b17f89f55736339f62f2b8562ab Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 27 Feb 2024 16:02:35 +0000 Subject: [PATCH 28/64] Progress --- package-lock.json | 427 ++++++------------ package.json | 1 + .../sdk/js/pyth_solana_receiver/package.json | 1 + .../js/pyth_solana_receiver/src/constants.ts | 1 + .../sdk/js/pyth_solana_receiver/src/index.ts | 243 ++++++---- .../sdk/js/solana_utils/src/transaction.ts | 16 +- 6 files changed, 295 insertions(+), 394 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3450953715..d62812d3e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "target_chains/ethereum/examples/oracle_swap/app", "target_chains/sui/sdk/js", "target_chains/sui/cli", + "target_chains/solana/sdk/js/solana_utils", "contract_manager" ], "dependencies": { @@ -527,36 +528,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 +566,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 +3369,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 +3794,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 +3863,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 +6415,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", @@ -13252,6 +13201,10 @@ "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 +16227,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 +21970,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 +34404,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 +34418,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 +58780,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 +58875,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 +59525,24 @@ "node": ">=10.0.0" } }, + "target_chains/solana/sdk/js/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 +59992,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 +60056,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 +61699,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 +70461,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 +70533,7 @@ "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "optional": true, "requires": {} } } @@ -71581,6 +71474,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 +73726,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 +78513,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 +81797,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 +81827,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 +88789,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 +88803,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 +96982,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 +97022,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 +105771,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 +106162,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 +106204,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 3ff440eb98..f40fb634d6 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "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/target_chains/solana/sdk/js/pyth_solana_receiver/package.json b/target_chains/solana/sdk/js/pyth_solana_receiver/package.json index e5bef105ef..a3a3251261 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/package.json +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/package.json @@ -44,6 +44,7 @@ "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/constants.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts index c6dce06777..ba9548633c 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts @@ -2,3 +2,4 @@ export const VAA_START = 46; export const VAA_SPLIT_INDEX = 800; export const VAA_SIGNATURE_SIZE = 66; export const DEFAULT_TREASURY_ID = 0; +export const DEFAULT_REDUCED_GUARDIAN_SET_SIZE = 5; 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 index 76b515dfc3..97f2a48b52 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -1,10 +1,8 @@ import { AnchorProvider, Program } from "@coral-xyz/anchor"; import { - ComputeBudgetProgram, Connection, Signer, TransactionInstruction, - TransactionMessage, VersionedTransaction, } from "@solana/web3.js"; import { PythSolanaReceiver } from "./idl/pyth_solana_receiver"; @@ -22,72 +20,15 @@ import { } from "./address"; import { PublicKey, Keypair } from "@solana/web3.js"; import { parseAccumulatorUpdateData } from "@pythnetwork/price-service-sdk"; -import { DEFAULT_TREASURY_ID, VAA_SPLIT_INDEX, VAA_START } from "./constants"; +import { + DEFAULT_REDUCED_GUARDIAN_SET_SIZE, + DEFAULT_TREASURY_ID, + VAA_SPLIT_INDEX, + VAA_START, +} from "./constants"; import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; -import { getGuardianSetIndex } from "./vaa"; - -export class TransactionBuilder { - private transactionInstructions: { - instructions: TransactionInstruction[]; - signers: Signer[]; - }[] = []; - private pythSolanaReceiverConnection: PythSolanaReceiverConnection; - private encodedVaaAddress: PublicKey | undefined; - private priceUpdateAddress: PublicKey | undefined; - - constructor(pythSolanaReceiverConnection: PythSolanaReceiverConnection) { - this.pythSolanaReceiverConnection = pythSolanaReceiverConnection; - } - - async addPostPriceUpdate(vaa: string) { - const { transactions, priceUpdateAddress, encodedVaaAddress } = - await this.pythSolanaReceiverConnection.buildPostPriceUpdateInstructions( - vaa - ); - this.encodedVaaAddress = encodedVaaAddress; - this.priceUpdateAddress = priceUpdateAddress; - this.transactionInstructions.push(...transactions); - } - - async addArbitraryInstruction( - instruction: ( - priceUpdateAddress: PublicKey - ) => Promise, - signers: Signer[] - ) { - if (this.priceUpdateAddress === undefined) { - throw new Error( - "You need to call addPostPriceUpdate before calling addArbitraryInstruction" - ); - } - this.transactionInstructions[ - this.transactionInstructions.length - 1 - ].instructions.push(await instruction(this.priceUpdateAddress)); - this.transactionInstructions[ - this.transactionInstructions.length - 1 - ].signers.push(...signers); - } - - async getTransactions(): Promise< - { tx: VersionedTransaction; signers: Signer[] }[] - > { - const blockhash = ( - await this.pythSolanaReceiverConnection.connection.getLatestBlockhash() - ).blockhash; - return this.transactionInstructions.map(({ instructions, signers }) => { - return { - tx: new VersionedTransaction( - new TransactionMessage({ - recentBlockhash: blockhash, - instructions: instructions, - payerKey: this.pythSolanaReceiverConnection.wallet.publicKey, - }).compileToV0Message() - ), - signers: signers, - }; - }); - } -} +import { getGuardianSetIndex, trimSignatures } from "./vaa"; +import { TransactionBuilder } from "@pythnetwork/solana-utils"; export class PythSolanaReceiverConnection { readonly connection: Connection; @@ -120,16 +61,90 @@ export class PythSolanaReceiverConnection { ); } - public getBuilder(): TransactionBuilder { - return new TransactionBuilder(this); + async withPriceUpdate( + vaa: string, + getInstructions: ( + priceFeedIdToPriceUpdateAccount: Record + ) => Promise<{ instruction: TransactionInstruction; signers: Signer[] }[]> + ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { + const builder = new TransactionBuilder( + this.wallet.publicKey, + this.connection + ); + const { instructions, priceFeedIdToPriceAccount, encodedVaaAddress } = + await this.buildPostPriceUpdateInstructions(vaa); + builder.addInstructions(instructions); + builder.addInstructions(await getInstructions(priceFeedIdToPriceAccount)); + builder.addInstruction(await this.buildCloseEncodedVaa(encodedVaaAddress)); + builder.addInstruction( + await this.buildClosePriceUpdate( + Object.values(priceFeedIdToPriceAccount)[0] + ) + ); + return builder.getVersionedTransactions(); + } + + async withPartiallyVerifiedPriceUpdate( + vaa: string, + getInstructions: ( + priceFeedIdToPriceUpdateAccount: Record + ) => Promise<{ instruction: TransactionInstruction; signers: Signer[] }[]> + ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { + const builder = new TransactionBuilder( + this.wallet.publicKey, + this.connection + ); + const { instructions, priceFeedIdToPriceAccount } = + await this.buildPostPriceUpdateAtomicInstructions(vaa); + builder.addInstructions(instructions); + builder.addInstructions(await getInstructions(priceFeedIdToPriceAccount)); + builder.addInstruction( + await this.buildClosePriceUpdate( + Object.values(priceFeedIdToPriceAccount)[0] + ) + ); + return builder.getVersionedTransactions(); + } + + async buildPostPriceUpdateAtomicInstructions(vaa: string): Promise<{ + instructions: [{ instruction: TransactionInstruction; signers: Signer[] }]; + priceFeedIdToPriceAccount: Record; + }> { + const accumulatorUpdateData = parseAccumulatorUpdateData( + Buffer.from(vaa, "base64") + ); + const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); + const trimmedVaa = trimSignatures( + accumulatorUpdateData.vaa, + DEFAULT_REDUCED_GUARDIAN_SET_SIZE + ); + const priceUpdateKeypair = new Keypair(); + return { + instructions: [ + { + instruction: await this.receiver.methods + .postUpdateAtomic({ + vaa: trimmedVaa, + merklePriceUpdate: accumulatorUpdateData.updates[0], + treasuryId: DEFAULT_TREASURY_ID, + }) + .accounts({ + priceUpdateAccount: priceUpdateKeypair.publicKey, + treasury: getTreasuryPda(DEFAULT_TREASURY_ID), + config: getConfigPda(), + guardianSet: getGuardianSetPda(guardianSetIndex), + }) + .instruction(), + signers: [priceUpdateKeypair], + }, + ], + priceFeedIdToPriceAccount: { 0: priceUpdateKeypair.publicKey }, + }; } async buildPostPriceUpdateInstructions(vaa: string): Promise<{ - transactions: { - instructions: TransactionInstruction[]; - signers: Signer[]; - }[]; - priceUpdateAddress: PublicKey; + instructions: { instruction: TransactionInstruction; signers: Signer[] }[]; + priceFeedIdToPriceAccount: Record; encodedVaaAddress: PublicKey; }> { const accumulatorUpdateData = parseAccumulatorUpdateData( @@ -141,18 +156,31 @@ export class PythSolanaReceiverConnection { const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); - const firstTransactionInstructions: TransactionInstruction[] = [ - await this.wormhole.account.encodedVaa.createInstruction( + const instructions: { + instruction: TransactionInstruction; + signers: Signer[]; + }[] = []; + + instructions.push({ + instruction: await this.wormhole.account.encodedVaa.createInstruction( encodedVaaKeypair, encodedVaaSize ), - await this.wormhole.methods + signers: [encodedVaaKeypair], + }); + + instructions.push({ + instruction: await this.wormhole.methods .initEncodedVaa() .accounts({ encodedVaa: encodedVaaKeypair.publicKey, }) .instruction(), - await this.wormhole.methods + signers: [], + }); + + instructions.push({ + instruction: await this.wormhole.methods .writeEncodedVaa({ index: 0, data: accumulatorUpdateData.vaa.subarray(0, VAA_SPLIT_INDEX), @@ -160,14 +188,14 @@ export class PythSolanaReceiverConnection { .accounts({ draftVaa: encodedVaaKeypair.publicKey, }) - .signers([encodedVaaKeypair]) .instruction(), - ]; + signers: [], + }); const priceUpdateKeypair = new Keypair(); - const secondTransactionInstructions: TransactionInstruction[] = [ - ComputeBudgetProgram.setComputeUnitLimit({ units: 600000 }), - await this.wormhole.methods + + instructions.push({ + instruction: await this.wormhole.methods .writeEncodedVaa({ index: VAA_SPLIT_INDEX, data: accumulatorUpdateData.vaa.subarray(VAA_SPLIT_INDEX), @@ -176,14 +204,22 @@ export class PythSolanaReceiverConnection { draftVaa: encodedVaaKeypair.publicKey, }) .instruction(), - await this.wormhole.methods + signers: [], + }); + + instructions.push({ + instruction: await this.wormhole.methods .verifyEncodedVaaV1() .accounts({ guardianSet: getGuardianSetPda(guardianSetIndex), draftVaa: encodedVaaKeypair.publicKey, }) .instruction(), - await this.receiver.methods + signers: [], + }); + + instructions.push({ + instruction: await this.receiver.methods .postUpdate({ merklePriceUpdate: accumulatorUpdateData.updates[0], treasuryId: DEFAULT_TREASURY_ID, @@ -194,23 +230,34 @@ export class PythSolanaReceiverConnection { treasury: getTreasuryPda(DEFAULT_TREASURY_ID), config: getConfigPda(), }) - .signers([priceUpdateKeypair]) .instruction(), - ]; + signers: [priceUpdateKeypair], + }); return { - transactions: [ - { - instructions: firstTransactionInstructions, - signers: [encodedVaaKeypair], - }, - { - instructions: secondTransactionInstructions, - signers: [priceUpdateKeypair], - }, - ], - priceUpdateAddress: priceUpdateKeypair.publicKey, + instructions, + priceFeedIdToPriceAccount: { 0: priceUpdateKeypair.publicKey }, encodedVaaAddress: encodedVaaKeypair.publicKey, }; } + + async buildCloseEncodedVaa( + encodedVaa: PublicKey + ): Promise<{ instruction: TransactionInstruction; signers: Signer[] }> { + const instruction = await this.wormhole.methods + .closeEncodedVaa() + .accounts({ encodedVaa }) + .instruction(); + return { instruction, signers: [] }; + } + + async buildClosePriceUpdate( + priceUpdateAccount: PublicKey + ): Promise<{ instruction: TransactionInstruction; signers: Signer[] }> { + const instruction = await this.receiver.methods + .reclaimRent() + .accounts({ priceUpdateAccount }) + .instruction(); + return { instruction, signers: [] }; + } } diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts index 38f8b0520e..77270a4200 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -81,7 +81,11 @@ export class TransactionBuilder { * 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(instruction: TransactionInstruction, signers: Signer[]) { + addInstruction(args: { + instruction: TransactionInstruction; + signers: Signer[]; + }) { + const { instruction, signers } = args; if (this.transactionInstructions.length === 0) { this.transactionInstructions.push({ instructions: [instruction], @@ -107,6 +111,14 @@ export class TransactionBuilder { }); } + addInstructions( + instructions: { instruction: TransactionInstruction; signers: Signer[] }[] + ) { + for (const { instruction, signers } of instructions) { + this.addInstruction({ instruction, signers }); + } + } + /** * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it */ @@ -148,7 +160,7 @@ export class TransactionBuilder { 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, []); + transactionBuilder.addInstruction({ instruction, signers: [] }); } return transactionBuilder.getLegacyTransactions().map(({ tx }) => { return tx; From 42c7cd076e73391bdac78a56ccbbf4f5c217edbc Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 27 Feb 2024 16:30:55 +0000 Subject: [PATCH 29/64] Checkpoint --- target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 index f4a497509b..02a6e40e23 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts @@ -11,8 +11,12 @@ export function trimSignatures(vaa: Buffer, n: number): Buffer { "Resulting VAA can't have more signatures than the original VAA" ); } - return Buffer.concat([ + + const trimmedVaa = Buffer.concat([ vaa.subarray(0, 5 + n * VAA_SIGNATURE_SIZE), vaa.subarray(5 + currentNumSignatures * VAA_SIGNATURE_SIZE), ]); + + trimmedVaa[5] = n; + return trimmedVaa; } From 07756b138b3573e6210a2c29c2fb3cca071648ae Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 27 Feb 2024 16:45:19 +0000 Subject: [PATCH 30/64] Ephemeral signers 2 --- .../sdk/js/pyth_solana_receiver/src/index.ts | 24 +++++++++---------- .../sdk/js/pyth_solana_receiver/src/vaa.ts | 2 +- .../solana/sdk/js/solana_utils/src/index.ts | 1 + .../sdk/js/solana_utils/src/transaction.ts | 14 +++++------ 4 files changed, 21 insertions(+), 20 deletions(-) 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 index 97f2a48b52..ff2a878719 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -28,12 +28,15 @@ import { } from "./constants"; import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; import { getGuardianSetIndex, trimSignatures } from "./vaa"; -import { TransactionBuilder } from "@pythnetwork/solana-utils"; +import { + TransactionBuilder, + InstructionWithEphemeralSigners, +} from "@pythnetwork/solana-utils"; export class PythSolanaReceiverConnection { readonly connection: Connection; readonly wallet: Wallet; - private readonly provider: AnchorProvider; + readonly provider: AnchorProvider; readonly receiver: Program; readonly wormhole: Program; @@ -65,7 +68,7 @@ export class PythSolanaReceiverConnection { vaa: string, getInstructions: ( priceFeedIdToPriceUpdateAccount: Record - ) => Promise<{ instruction: TransactionInstruction; signers: Signer[] }[]> + ) => Promise ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { const builder = new TransactionBuilder( this.wallet.publicKey, @@ -88,7 +91,7 @@ export class PythSolanaReceiverConnection { vaa: string, getInstructions: ( priceFeedIdToPriceUpdateAccount: Record - ) => Promise<{ instruction: TransactionInstruction; signers: Signer[] }[]> + ) => Promise ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { const builder = new TransactionBuilder( this.wallet.publicKey, @@ -107,7 +110,7 @@ export class PythSolanaReceiverConnection { } async buildPostPriceUpdateAtomicInstructions(vaa: string): Promise<{ - instructions: [{ instruction: TransactionInstruction; signers: Signer[] }]; + instructions: InstructionWithEphemeralSigners[]; priceFeedIdToPriceAccount: Record; }> { const accumulatorUpdateData = parseAccumulatorUpdateData( @@ -143,7 +146,7 @@ export class PythSolanaReceiverConnection { } async buildPostPriceUpdateInstructions(vaa: string): Promise<{ - instructions: { instruction: TransactionInstruction; signers: Signer[] }[]; + instructions: InstructionWithEphemeralSigners[]; priceFeedIdToPriceAccount: Record; encodedVaaAddress: PublicKey; }> { @@ -156,10 +159,7 @@ export class PythSolanaReceiverConnection { const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); - const instructions: { - instruction: TransactionInstruction; - signers: Signer[]; - }[] = []; + const instructions: InstructionWithEphemeralSigners[] = []; instructions.push({ instruction: await this.wormhole.account.encodedVaa.createInstruction( @@ -243,7 +243,7 @@ export class PythSolanaReceiverConnection { async buildCloseEncodedVaa( encodedVaa: PublicKey - ): Promise<{ instruction: TransactionInstruction; signers: Signer[] }> { + ): Promise { const instruction = await this.wormhole.methods .closeEncodedVaa() .accounts({ encodedVaa }) @@ -253,7 +253,7 @@ export class PythSolanaReceiverConnection { async buildClosePriceUpdate( priceUpdateAccount: PublicKey - ): Promise<{ instruction: TransactionInstruction; signers: Signer[] }> { + ): Promise { const instruction = await this.receiver.methods .reclaimRent() .accounts({ priceUpdateAccount }) 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 index 02a6e40e23..41f0cd240e 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts @@ -12,7 +12,7 @@ export function trimSignatures(vaa: Buffer, n: number): Buffer { ); } - const trimmedVaa = Buffer.concat([ + let trimmedVaa = Buffer.concat([ vaa.subarray(0, 5 + n * VAA_SIGNATURE_SIZE), vaa.subarray(5 + currentNumSignatures * VAA_SIGNATURE_SIZE), ]); diff --git a/target_chains/solana/sdk/js/solana_utils/src/index.ts b/target_chains/solana/sdk/js/solana_utils/src/index.ts index d9b3e9532c..23a84b59eb 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/index.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/index.ts @@ -2,4 +2,5 @@ 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 index 77270a4200..1f79b8a3b7 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -9,6 +9,11 @@ import { VersionedTransaction, } from "@solana/web3.js"; +export type InstructionWithEphemeralSigners = { + instruction: TransactionInstruction; + signers: Signer[]; +}; + /** * Get the size of a transaction that would contain the provided array of instructions */ @@ -81,10 +86,7 @@ export class TransactionBuilder { * 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: { - instruction: TransactionInstruction; - signers: Signer[]; - }) { + addInstruction(args: InstructionWithEphemeralSigners) { const { instruction, signers } = args; if (this.transactionInstructions.length === 0) { this.transactionInstructions.push({ @@ -111,9 +113,7 @@ export class TransactionBuilder { }); } - addInstructions( - instructions: { instruction: TransactionInstruction; signers: Signer[] }[] - ) { + addInstructions(instructions: InstructionWithEphemeralSigners[]) { for (const { instruction, signers } of instructions) { this.addInstruction({ instruction, signers }); } From 16c1f86742051eb934931c51e2b52c49c9ccfe89 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 27 Feb 2024 17:37:03 +0000 Subject: [PATCH 31/64] Checkpoint --- .../sdk/js/src/AccumulatorUpdateData.ts | 48 +++++++++++++++++++ .../__tests__/AccumulatorUpdateData.test.ts | 22 +++++++-- price_service/sdk/js/src/index.ts | 2 +- .../sdk/js/pyth_solana_receiver/src/index.ts | 30 +++++++----- 4 files changed, 86 insertions(+), 16 deletions(-) diff --git a/price_service/sdk/js/src/AccumulatorUpdateData.ts b/price_service/sdk/js/src/AccumulatorUpdateData.ts index ce083ef1b3..378b259150 100644 --- a/price_service/sdk/js/src/AccumulatorUpdateData.ts +++ b/price_service/sdk/js/src/AccumulatorUpdateData.ts @@ -1,13 +1,27 @@ +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[] }[]; }; +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 { return ( updateBytes.toString("hex").slice(0, 8) === ACCUMULATOR_MAGIC && @@ -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 diff --git a/price_service/sdk/js/src/__tests__/AccumulatorUpdateData.test.ts b/price_service/sdk/js/src/__tests__/AccumulatorUpdateData.test.ts index 98bf2c5a0b..4f0337b45e 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 7754225667..6421de4e9a 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/sdk/js/pyth_solana_receiver/src/index.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts index ff2a878719..3f171b7dd1 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -1,10 +1,5 @@ import { AnchorProvider, Program } from "@coral-xyz/anchor"; -import { - Connection, - Signer, - TransactionInstruction, - VersionedTransaction, -} from "@solana/web3.js"; +import { Connection, Signer, VersionedTransaction } from "@solana/web3.js"; import { PythSolanaReceiver } from "./idl/pyth_solana_receiver"; import Idl from "./idl/pyth_solana_receiver.json"; import { @@ -19,7 +14,10 @@ import { getTreasuryPda, } from "./address"; import { PublicKey, Keypair } from "@solana/web3.js"; -import { parseAccumulatorUpdateData } from "@pythnetwork/price-service-sdk"; +import { + parseAccumulatorUpdateData, + parsePriceFeedMessage, +} from "@pythnetwork/price-service-sdk"; import { DEFAULT_REDUCED_GUARDIAN_SET_SIZE, DEFAULT_TREASURY_ID, @@ -67,7 +65,7 @@ export class PythSolanaReceiverConnection { async withPriceUpdate( vaa: string, getInstructions: ( - priceFeedIdToPriceUpdateAccount: Record + priceFeedIdToPriceUpdateAccount: Record ) => Promise ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { const builder = new TransactionBuilder( @@ -90,7 +88,7 @@ export class PythSolanaReceiverConnection { async withPartiallyVerifiedPriceUpdate( vaa: string, getInstructions: ( - priceFeedIdToPriceUpdateAccount: Record + priceFeedIdToPriceUpdateAccount: Record ) => Promise ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { const builder = new TransactionBuilder( @@ -141,7 +139,12 @@ export class PythSolanaReceiverConnection { signers: [priceUpdateKeypair], }, ], - priceFeedIdToPriceAccount: { 0: priceUpdateKeypair.publicKey }, + priceFeedIdToPriceAccount: { + ["0x" + + parsePriceFeedMessage( + accumulatorUpdateData.updates[0].message + ).feedId.toString("hex")]: priceUpdateKeypair.publicKey, + }, }; } @@ -236,7 +239,12 @@ export class PythSolanaReceiverConnection { return { instructions, - priceFeedIdToPriceAccount: { 0: priceUpdateKeypair.publicKey }, + priceFeedIdToPriceAccount: { + ["0x" + + parsePriceFeedMessage( + accumulatorUpdateData.updates[0].message + ).feedId.toString("hex")]: priceUpdateKeypair.publicKey, + }, encodedVaaAddress: encodedVaaKeypair.publicKey, }; } From ef052fa8a57521b78f654bf7a33e6d7399d26538 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 27 Feb 2024 20:15:25 +0000 Subject: [PATCH 32/64] Checkpoint --- .../js/pyth_solana_receiver/src/constants.ts | 5 +- .../sdk/js/pyth_solana_receiver/src/index.ts | 140 ++++++++++-------- .../sdk/js/solana_utils/src/transaction.ts | 64 ++++++-- 3 files changed, 130 insertions(+), 79 deletions(-) diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts index ba9548633c..078b18ddc6 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts @@ -1,5 +1,8 @@ export const VAA_START = 46; -export const VAA_SPLIT_INDEX = 800; +export const VAA_SPLIT_INDEX = 792; export const VAA_SIGNATURE_SIZE = 66; export const DEFAULT_TREASURY_ID = 0; export const DEFAULT_REDUCED_GUARDIAN_SET_SIZE = 5; +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/index.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts index 3f171b7dd1..28488843a8 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -21,8 +21,11 @@ import { import { DEFAULT_REDUCED_GUARDIAN_SET_SIZE, DEFAULT_TREASURY_ID, + POST_UPDATE_ATOMIC_COMPUTE_BUDGET, + POST_UPDATE_COMPUTE_BUDGET, VAA_SPLIT_INDEX, VAA_START, + VERIFY_ENCODED_VAA_COMPUTE_BUDGET, } from "./constants"; import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; import { getGuardianSetIndex, trimSignatures } from "./vaa"; @@ -63,7 +66,7 @@ export class PythSolanaReceiverConnection { } async withPriceUpdate( - vaa: string, + priceUpdateData: string, getInstructions: ( priceFeedIdToPriceUpdateAccount: Record ) => Promise @@ -73,20 +76,22 @@ export class PythSolanaReceiverConnection { this.connection ); const { instructions, priceFeedIdToPriceAccount, encodedVaaAddress } = - await this.buildPostPriceUpdateInstructions(vaa); + await this.buildPostPriceUpdateInstructions(priceUpdateData); builder.addInstructions(instructions); builder.addInstructions(await getInstructions(priceFeedIdToPriceAccount)); builder.addInstruction(await this.buildCloseEncodedVaa(encodedVaaAddress)); - builder.addInstruction( - await this.buildClosePriceUpdate( - Object.values(priceFeedIdToPriceAccount)[0] + await Promise.all( + Object.values(priceFeedIdToPriceAccount).map(async (priceUpdateAccount) => + builder.addInstruction( + await this.buildClosePriceUpdate(priceUpdateAccount) + ) ) ); return builder.getVersionedTransactions(); } async withPartiallyVerifiedPriceUpdate( - vaa: string, + priceUpdateData: string, getInstructions: ( priceFeedIdToPriceUpdateAccount: Record ) => Promise @@ -96,65 +101,73 @@ export class PythSolanaReceiverConnection { this.connection ); const { instructions, priceFeedIdToPriceAccount } = - await this.buildPostPriceUpdateAtomicInstructions(vaa); + await this.buildPostPriceUpdateAtomicInstructions(priceUpdateData); builder.addInstructions(instructions); builder.addInstructions(await getInstructions(priceFeedIdToPriceAccount)); - builder.addInstruction( - await this.buildClosePriceUpdate( - Object.values(priceFeedIdToPriceAccount)[0] + await Promise.all( + Object.values(priceFeedIdToPriceAccount).map(async (priceUpdateAccount) => + builder.addInstruction( + await this.buildClosePriceUpdate(priceUpdateAccount) + ) ) ); return builder.getVersionedTransactions(); } - async buildPostPriceUpdateAtomicInstructions(vaa: string): Promise<{ + async buildPostPriceUpdateAtomicInstructions( + priceUpdateData: string + ): Promise<{ instructions: InstructionWithEphemeralSigners[]; priceFeedIdToPriceAccount: Record; }> { const accumulatorUpdateData = parseAccumulatorUpdateData( - Buffer.from(vaa, "base64") + Buffer.from(priceUpdateData, "base64") ); const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); const trimmedVaa = trimSignatures( accumulatorUpdateData.vaa, DEFAULT_REDUCED_GUARDIAN_SET_SIZE ); - const priceUpdateKeypair = new Keypair(); + + const priceFeedIdToPriceAccount: Record = {}; + const instructions: InstructionWithEphemeralSigners[] = []; + for (const update of accumulatorUpdateData.updates) { + const priceUpdateKeypair = new Keypair(); + instructions.push({ + instruction: await this.receiver.methods + .postUpdateAtomic({ + vaa: trimmedVaa, + merklePriceUpdate: accumulatorUpdateData.updates[0], + 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, + }); + priceFeedIdToPriceAccount[ + "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex") + ] = priceUpdateKeypair.publicKey; + } + return { - instructions: [ - { - instruction: await this.receiver.methods - .postUpdateAtomic({ - vaa: trimmedVaa, - merklePriceUpdate: accumulatorUpdateData.updates[0], - treasuryId: DEFAULT_TREASURY_ID, - }) - .accounts({ - priceUpdateAccount: priceUpdateKeypair.publicKey, - treasury: getTreasuryPda(DEFAULT_TREASURY_ID), - config: getConfigPda(), - guardianSet: getGuardianSetPda(guardianSetIndex), - }) - .instruction(), - signers: [priceUpdateKeypair], - }, - ], - priceFeedIdToPriceAccount: { - ["0x" + - parsePriceFeedMessage( - accumulatorUpdateData.updates[0].message - ).feedId.toString("hex")]: priceUpdateKeypair.publicKey, - }, + instructions, + priceFeedIdToPriceAccount, }; } - async buildPostPriceUpdateInstructions(vaa: string): Promise<{ + async buildPostPriceUpdateInstructions(priceUpdateData: string): Promise<{ instructions: InstructionWithEphemeralSigners[]; priceFeedIdToPriceAccount: Record; encodedVaaAddress: PublicKey; }> { const accumulatorUpdateData = parseAccumulatorUpdateData( - Buffer.from(vaa, "base64") + Buffer.from(priceUpdateData, "base64") ); const encodedVaaKeypair = new Keypair(); @@ -195,8 +208,6 @@ export class PythSolanaReceiverConnection { signers: [], }); - const priceUpdateKeypair = new Keypair(); - instructions.push({ instruction: await this.wormhole.methods .writeEncodedVaa({ @@ -219,32 +230,37 @@ export class PythSolanaReceiverConnection { }) .instruction(), signers: [], + computeUnits: VERIFY_ENCODED_VAA_COMPUTE_BUDGET, }); - instructions.push({ - instruction: await this.receiver.methods - .postUpdate({ - merklePriceUpdate: accumulatorUpdateData.updates[0], - treasuryId: DEFAULT_TREASURY_ID, - }) - .accounts({ - encodedVaa: encodedVaaKeypair.publicKey, - priceUpdateAccount: priceUpdateKeypair.publicKey, - treasury: getTreasuryPda(DEFAULT_TREASURY_ID), - config: getConfigPda(), - }) - .instruction(), - signers: [priceUpdateKeypair], - }); + const priceFeedIdToPriceAccount: Record = {}; + for (const update of accumulatorUpdateData.updates) { + const priceUpdateKeypair = new Keypair(); + instructions.push({ + instruction: await this.receiver.methods + .postUpdate({ + merklePriceUpdate: update, + treasuryId: DEFAULT_TREASURY_ID, + }) + .accounts({ + encodedVaa: encodedVaaKeypair.publicKey, + priceUpdateAccount: priceUpdateKeypair.publicKey, + treasury: getTreasuryPda(DEFAULT_TREASURY_ID), + config: getConfigPda(), + }) + .instruction(), + signers: [priceUpdateKeypair], + computeUnits: POST_UPDATE_COMPUTE_BUDGET, + }); + + priceFeedIdToPriceAccount[ + "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex") + ] = priceUpdateKeypair.publicKey; + } return { instructions, - priceFeedIdToPriceAccount: { - ["0x" + - parsePriceFeedMessage( - accumulatorUpdateData.updates[0].message - ).feedId.toString("hex")]: priceUpdateKeypair.publicKey, - }, + priceFeedIdToPriceAccount, encodedVaaAddress: encodedVaaKeypair.publicKey, }; } diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts index 1f79b8a3b7..8480d468db 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -1,4 +1,5 @@ import { + ComputeBudgetProgram, Connection, PACKET_DATA_SIZE, PublicKey, @@ -9,9 +10,14 @@ import { 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; }; /** @@ -73,6 +79,7 @@ export class TransactionBuilder { readonly transactionInstructions: { instructions: TransactionInstruction[]; signers: Signer[]; + computeUnits: number; }[] = []; readonly payer: PublicKey; readonly connection: Connection; @@ -87,18 +94,19 @@ export class TransactionBuilder { * where this instruction appears */ addInstruction(args: InstructionWithEphemeralSigners) { - const { instruction, signers } = args; + 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 + ]) <= PACKET_DATA_SIZE_WITH_ROOM_FOR_COMPUTE_BUDGET ) { this.transactionInstructions[ this.transactionInstructions.length - 1 @@ -106,16 +114,20 @@ export class TransactionBuilder { 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 } of instructions) { - this.addInstruction({ instruction, signers }); + for (const { instruction, signers, computeUnits } of instructions) { + this.addInstruction({ instruction, signers, computeUnits }); } } @@ -126,18 +138,38 @@ export class TransactionBuilder { { tx: VersionedTransaction; signers: Signer[] }[] > { const blockhash = (await this.connection.getLatestBlockhash()).blockhash; - return this.transactionInstructions.map(({ instructions, signers }) => { - return { - tx: new VersionedTransaction( - new TransactionMessage({ - recentBlockhash: blockhash, - instructions: instructions, - payerKey: this.payer, - }).compileToV0Message() - ), - signers: signers, - }; - }); + return this.transactionInstructions.map( + ({ instructions, signers, computeUnits }) => { + if (computeUnits > DEFAULT_COMPUTE_BUDGET_UNITS * instructions.length) { + return { + tx: new VersionedTransaction( + new TransactionMessage({ + recentBlockhash: blockhash, + instructions: [ + ...instructions, + ComputeBudgetProgram.setComputeUnitLimit({ + units: computeUnits, + }), + ], + payerKey: this.payer, + }).compileToV0Message() + ), + signers: signers, + }; + } else { + return { + tx: new VersionedTransaction( + new TransactionMessage({ + recentBlockhash: blockhash, + instructions: instructions, + payerKey: this.payer, + }).compileToV0Message() + ), + signers: signers, + }; + } + } + ); } /** From 160c011934ce5b9f07b1c623f754c004572ec75b Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Tue, 27 Feb 2024 20:27:26 +0000 Subject: [PATCH 33/64] Fix bug --- target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 41f0cd240e..6b6737cf77 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts @@ -13,8 +13,8 @@ export function trimSignatures(vaa: Buffer, n: number): Buffer { } let trimmedVaa = Buffer.concat([ - vaa.subarray(0, 5 + n * VAA_SIGNATURE_SIZE), - vaa.subarray(5 + currentNumSignatures * VAA_SIGNATURE_SIZE), + vaa.subarray(0, 6 + n * VAA_SIGNATURE_SIZE), + vaa.subarray(6 + currentNumSignatures * VAA_SIGNATURE_SIZE), ]); trimmedVaa[5] = n; From 2f7697c3903ed5162526e746fd9c5f671556869d Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 14:56:52 +0000 Subject: [PATCH 34/64] Cleanup idls --- .../sdk/js/src/AccumulatorUpdateData.ts | 6 +- .../src/idl/pyth_solana_receiver.ts | 128 +++++++++++++++++- .../sdk/js/pyth_solana_receiver/src/index.ts | 5 +- 3 files changed, 132 insertions(+), 7 deletions(-) diff --git a/price_service/sdk/js/src/AccumulatorUpdateData.ts b/price_service/sdk/js/src/AccumulatorUpdateData.ts index 378b259150..a9149e2bc5 100644 --- a/price_service/sdk/js/src/AccumulatorUpdateData.ts +++ b/price_service/sdk/js/src/AccumulatorUpdateData.ts @@ -8,7 +8,7 @@ const PRICE_FEED_MESSAGE_VARIANT = 0; export type AccumulatorUpdateData = { vaa: Buffer; - updates: { message: Buffer; proof: Buffer[] }[]; + updates: { message: Buffer; proof: number[][] }[]; }; export type PriceFeedMessage = { @@ -98,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/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 index 46829eb5a4..e42825fac0 100644 --- 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 @@ -282,7 +282,7 @@ export type PythSolanaReceiver = { ]; accounts: [ { - name: "config"; + name: "Config"; type: { kind: "struct"; fields: [ @@ -345,6 +345,68 @@ export type PythSolanaReceiver = { } ]; 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: { @@ -823,7 +885,7 @@ export const IDL: PythSolanaReceiver = { ], accounts: [ { - name: "config", + name: "Config", type: { kind: "struct", fields: [ @@ -886,6 +948,68 @@ export const IDL: PythSolanaReceiver = { }, ], 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: { 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 index 28488843a8..f07931b68b 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -1,7 +1,6 @@ import { AnchorProvider, Program } from "@coral-xyz/anchor"; import { Connection, Signer, VersionedTransaction } from "@solana/web3.js"; -import { PythSolanaReceiver } from "./idl/pyth_solana_receiver"; -import Idl from "./idl/pyth_solana_receiver.json"; +import { PythSolanaReceiver, IDL as Idl } from "./idl/pyth_solana_receiver"; import { WormholeCoreBridgeSolana, IDL as WormholeCoreBridgeSolanaIdl, @@ -137,7 +136,7 @@ export class PythSolanaReceiverConnection { instruction: await this.receiver.methods .postUpdateAtomic({ vaa: trimmedVaa, - merklePriceUpdate: accumulatorUpdateData.updates[0], + merklePriceUpdate: update, treasuryId: DEFAULT_TREASURY_ID, }) .accounts({ From 89eb8e8a6898ca75dba079f8708c9dfbc28bb43f Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 15:18:13 +0000 Subject: [PATCH 35/64] Compute units --- .../sdk/js/pyth_solana_receiver/src/index.ts | 11 ++- .../sdk/js/solana_utils/src/transaction.ts | 78 +++++++++++-------- 2 files changed, 53 insertions(+), 36 deletions(-) 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 index f07931b68b..a58faf3397 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -32,6 +32,7 @@ import { TransactionBuilder, InstructionWithEphemeralSigners, } from "@pythnetwork/solana-utils"; +import { priorityFeeConfig as PriorityFeeConfig } from "@pythnetwork/solana-utils/lib/transaction"; export class PythSolanaReceiverConnection { readonly connection: Connection; @@ -68,7 +69,8 @@ export class PythSolanaReceiverConnection { priceUpdateData: string, getInstructions: ( priceFeedIdToPriceUpdateAccount: Record - ) => Promise + ) => Promise, + priorityFeeConfig?: PriorityFeeConfig ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { const builder = new TransactionBuilder( this.wallet.publicKey, @@ -86,14 +88,15 @@ export class PythSolanaReceiverConnection { ) ) ); - return builder.getVersionedTransactions(); + return builder.getVersionedTransactions(priorityFeeConfig ?? {}); } async withPartiallyVerifiedPriceUpdate( priceUpdateData: string, getInstructions: ( priceFeedIdToPriceUpdateAccount: Record - ) => Promise + ) => Promise, + priorityFeeConfig?: PriorityFeeConfig ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { const builder = new TransactionBuilder( this.wallet.publicKey, @@ -110,7 +113,7 @@ export class PythSolanaReceiverConnection { ) ) ); - return builder.getVersionedTransactions(); + return builder.getVersionedTransactions(priorityFeeConfig ?? {}); } async buildPostPriceUpdateAtomicInstructions( diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts index 8480d468db..ada7d98e17 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -20,6 +20,10 @@ export type InstructionWithEphemeralSigners = { computeUnits?: number; }; +export type priorityFeeConfig = { + computeUnitPriceMicroLamports?: number; +}; + /** * Get the size of a transaction that would contain the provided array of instructions */ @@ -134,40 +138,39 @@ export class TransactionBuilder { /** * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it */ - async getVersionedTransactions(): Promise< - { tx: VersionedTransaction; signers: Signer[] }[] - > { + 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) { - return { - tx: new VersionedTransaction( - new TransactionMessage({ - recentBlockhash: blockhash, - instructions: [ - ...instructions, - ComputeBudgetProgram.setComputeUnitLimit({ - units: computeUnits, - }), - ], - payerKey: this.payer, - }).compileToV0Message() - ), - signers: signers, - }; - } else { - return { - tx: new VersionedTransaction( - new TransactionMessage({ - recentBlockhash: blockhash, - instructions: instructions, - payerKey: this.payer, - }).compileToV0Message() - ), - signers: signers, - }; + 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, + }; } ); } @@ -175,10 +178,21 @@ export class TransactionBuilder { /** * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it */ - getLegacyTransactions(): { tx: Transaction; signers: Signer[] }[] { + 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(...instructions), + tx: new Transaction().add(...instructionsWithComputeBudget), signers: signers, }; }); @@ -194,7 +208,7 @@ export class TransactionBuilder { for (const instruction of instructions) { transactionBuilder.addInstruction({ instruction, signers: [] }); } - return transactionBuilder.getLegacyTransactions().map(({ tx }) => { + return transactionBuilder.getLegacyTransactions({}).map(({ tx }) => { return tx; }); } From 0e362c577386af5b5fc505a0b5f45bb42606dc0a Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 15:20:24 +0000 Subject: [PATCH 36/64] Make program addresses configurable --- .../solana/sdk/js/pyth_solana_receiver/src/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 index a58faf3397..85e5e50085 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -44,9 +44,13 @@ export class PythSolanaReceiverConnection { 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; @@ -55,12 +59,12 @@ export class PythSolanaReceiverConnection { }); this.receiver = new Program( Idl as PythSolanaReceiver, - DEFAULT_RECEIVER_PROGRAM_ID, + receiverProgramId, this.provider ); this.wormhole = new Program( WormholeCoreBridgeSolanaIdl as WormholeCoreBridgeSolana, - DEFAULT_WORMHOLE_PROGRAM_ID, + wormholeProgramId, this.provider ); } From 99b76b6fb67bccdd4690d23169a343e238ea5e03 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 16:38:24 +0000 Subject: [PATCH 37/64] Handle arrays --- .../sdk/js/pyth_solana_receiver/src/index.ts | 311 ++++++++++-------- 1 file changed, 174 insertions(+), 137 deletions(-) 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 index 85e5e50085..4127f5ced2 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -1,6 +1,9 @@ import { AnchorProvider, Program } from "@coral-xyz/anchor"; import { Connection, Signer, VersionedTransaction } from "@solana/web3.js"; -import { PythSolanaReceiver, IDL as Idl } from "./idl/pyth_solana_receiver"; +import { + PythSolanaReceiver as PythSolanaReceiverProgram, + IDL as Idl, +} from "./idl/pyth_solana_receiver"; import { WormholeCoreBridgeSolana, IDL as WormholeCoreBridgeSolanaIdl, @@ -34,11 +37,11 @@ import { } from "@pythnetwork/solana-utils"; import { priorityFeeConfig as PriorityFeeConfig } from "@pythnetwork/solana-utils/lib/transaction"; -export class PythSolanaReceiverConnection { +export class PythSolanaReceiver { readonly connection: Connection; readonly wallet: Wallet; readonly provider: AnchorProvider; - readonly receiver: Program; + readonly receiver: Program; readonly wormhole: Program; constructor({ @@ -57,8 +60,8 @@ export class PythSolanaReceiverConnection { this.provider = new AnchorProvider(this.connection, this.wallet, { commitment: connection.commitment, }); - this.receiver = new Program( - Idl as PythSolanaReceiver, + this.receiver = new Program( + Idl as PythSolanaReceiverProgram, receiverProgramId, this.provider ); @@ -70,7 +73,7 @@ export class PythSolanaReceiverConnection { } async withPriceUpdate( - priceUpdateData: string, + priceUpdateDataArray: string[], getInstructions: ( priceFeedIdToPriceUpdateAccount: Record ) => Promise, @@ -80,15 +83,29 @@ export class PythSolanaReceiverConnection { this.wallet.publicKey, this.connection ); - const { instructions, priceFeedIdToPriceAccount, encodedVaaAddress } = - await this.buildPostPriceUpdateInstructions(priceUpdateData); + + const { + instructions, + priceFeedIdToPriceUpdateAccount: priceFeedIdToPriceUpdateAccount, + encodedVaaAddresses, + priceUpdateAddresses, + } = await this.buildPostPriceUpdateInstructions(priceUpdateDataArray); builder.addInstructions(instructions); - builder.addInstructions(await getInstructions(priceFeedIdToPriceAccount)); - builder.addInstruction(await this.buildCloseEncodedVaa(encodedVaaAddress)); + builder.addInstructions( + await getInstructions(priceFeedIdToPriceUpdateAccount) + ); + await Promise.all( - Object.values(priceFeedIdToPriceAccount).map(async (priceUpdateAccount) => + encodedVaaAddresses.map(async (encodedVaaAddress) => builder.addInstruction( - await this.buildClosePriceUpdate(priceUpdateAccount) + await this.buildCloseEncodedVaaInstruction(encodedVaaAddress) + ) + ) + ); + await Promise.all( + priceUpdateAddresses.map(async (priceUpdateAccount) => + builder.addInstruction( + await this.buildClosePriceUpdateInstruction(priceUpdateAccount) ) ) ); @@ -96,7 +113,7 @@ export class PythSolanaReceiverConnection { } async withPartiallyVerifiedPriceUpdate( - priceUpdateData: string, + priceUpdateDataArray: string[], getInstructions: ( priceFeedIdToPriceUpdateAccount: Record ) => Promise, @@ -106,14 +123,19 @@ export class PythSolanaReceiverConnection { this.wallet.publicKey, this.connection ); - const { instructions, priceFeedIdToPriceAccount } = - await this.buildPostPriceUpdateAtomicInstructions(priceUpdateData); + const { + instructions, + priceFeedIdToPriceUpdateAccount, + priceUpdateAddresses, + } = await this.buildPostPriceUpdateAtomicInstructions(priceUpdateDataArray); builder.addInstructions(instructions); - builder.addInstructions(await getInstructions(priceFeedIdToPriceAccount)); + builder.addInstructions( + await getInstructions(priceFeedIdToPriceUpdateAccount) + ); await Promise.all( - Object.values(priceFeedIdToPriceAccount).map(async (priceUpdateAccount) => + priceUpdateAddresses.map(async (priceUpdateAccount) => builder.addInstruction( - await this.buildClosePriceUpdate(priceUpdateAccount) + await this.buildClosePriceUpdateInstruction(priceUpdateAccount) ) ) ); @@ -121,157 +143,172 @@ export class PythSolanaReceiverConnection { } async buildPostPriceUpdateAtomicInstructions( - priceUpdateData: string + priceUpdateDataArray: string[] ): Promise<{ instructions: InstructionWithEphemeralSigners[]; - priceFeedIdToPriceAccount: Record; + priceFeedIdToPriceUpdateAccount: Record; + priceUpdateAddresses: PublicKey[]; }> { - const accumulatorUpdateData = parseAccumulatorUpdateData( - Buffer.from(priceUpdateData, "base64") - ); - const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); - const trimmedVaa = trimSignatures( - accumulatorUpdateData.vaa, - DEFAULT_REDUCED_GUARDIAN_SET_SIZE - ); - - const priceFeedIdToPriceAccount: Record = {}; const instructions: InstructionWithEphemeralSigners[] = []; - for (const update of accumulatorUpdateData.updates) { - const priceUpdateKeypair = new Keypair(); - instructions.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, - }); - priceFeedIdToPriceAccount[ - "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex") - ] = priceUpdateKeypair.publicKey; - } + const priceFeedIdToPriceUpdateAccount: Record = {}; + const priceUpdateAddresses: PublicKey[] = []; + for (const priceUpdateData of priceUpdateDataArray) { + const accumulatorUpdateData = parseAccumulatorUpdateData( + Buffer.from(priceUpdateData, "base64") + ); + const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); + const trimmedVaa = trimSignatures( + accumulatorUpdateData.vaa, + DEFAULT_REDUCED_GUARDIAN_SET_SIZE + ); + + for (const update of accumulatorUpdateData.updates) { + const priceUpdateKeypair = new Keypair(); + instructions.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; + priceUpdateAddresses.push(priceUpdateKeypair.publicKey); + } + } return { instructions, - priceFeedIdToPriceAccount, + priceFeedIdToPriceUpdateAccount, + priceUpdateAddresses, }; } - async buildPostPriceUpdateInstructions(priceUpdateData: string): Promise<{ + async buildPostPriceUpdateInstructions( + priceUpdateDataArray: string[] + ): Promise<{ instructions: InstructionWithEphemeralSigners[]; - priceFeedIdToPriceAccount: Record; - encodedVaaAddress: PublicKey; + priceFeedIdToPriceUpdateAccount: Record; + encodedVaaAddresses: PublicKey[]; + priceUpdateAddresses: PublicKey[]; }> { - const accumulatorUpdateData = parseAccumulatorUpdateData( - Buffer.from(priceUpdateData, "base64") - ); - - const encodedVaaKeypair = new Keypair(); - const encodedVaaSize = accumulatorUpdateData.vaa.length + VAA_START; - - const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); - const instructions: InstructionWithEphemeralSigners[] = []; + const priceFeedIdToPriceUpdateAccount: Record = {}; + const priceUpdateAddresses: PublicKey[] = []; + const encodedVaaAddresses: PublicKey[] = []; - instructions.push({ - instruction: await this.wormhole.account.encodedVaa.createInstruction( - encodedVaaKeypair, - encodedVaaSize - ), - signers: [encodedVaaKeypair], - }); + for (const priceUpdateData of priceUpdateDataArray) { + const accumulatorUpdateData = parseAccumulatorUpdateData( + Buffer.from(priceUpdateData, "base64") + ); - instructions.push({ - instruction: await this.wormhole.methods - .initEncodedVaa() - .accounts({ - encodedVaa: encodedVaaKeypair.publicKey, - }) - .instruction(), - signers: [], - }); + const encodedVaaKeypair = new Keypair(); + const encodedVaaSize = accumulatorUpdateData.vaa.length + VAA_START; - instructions.push({ - instruction: await this.wormhole.methods - .writeEncodedVaa({ - index: 0, - data: accumulatorUpdateData.vaa.subarray(0, VAA_SPLIT_INDEX), - }) - .accounts({ - draftVaa: encodedVaaKeypair.publicKey, - }) - .instruction(), - signers: [], - }); + const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); - instructions.push({ - instruction: await this.wormhole.methods - .writeEncodedVaa({ - index: VAA_SPLIT_INDEX, - data: accumulatorUpdateData.vaa.subarray(VAA_SPLIT_INDEX), - }) - .accounts({ - draftVaa: encodedVaaKeypair.publicKey, - }) - .instruction(), - signers: [], - }); + instructions.push({ + instruction: await this.wormhole.account.encodedVaa.createInstruction( + encodedVaaKeypair, + encodedVaaSize + ), + signers: [encodedVaaKeypair], + }); - instructions.push({ - instruction: await this.wormhole.methods - .verifyEncodedVaaV1() - .accounts({ - guardianSet: getGuardianSetPda(guardianSetIndex), - draftVaa: encodedVaaKeypair.publicKey, - }) - .instruction(), - signers: [], - computeUnits: VERIFY_ENCODED_VAA_COMPUTE_BUDGET, - }); + instructions.push({ + instruction: await this.wormhole.methods + .initEncodedVaa() + .accounts({ + encodedVaa: encodedVaaKeypair.publicKey, + }) + .instruction(), + signers: [], + }); - const priceFeedIdToPriceAccount: Record = {}; - for (const update of accumulatorUpdateData.updates) { - const priceUpdateKeypair = new Keypair(); instructions.push({ - instruction: await this.receiver.methods - .postUpdate({ - merklePriceUpdate: update, - treasuryId: DEFAULT_TREASURY_ID, + instruction: await this.wormhole.methods + .writeEncodedVaa({ + index: 0, + data: accumulatorUpdateData.vaa.subarray(0, VAA_SPLIT_INDEX), }) .accounts({ - encodedVaa: encodedVaaKeypair.publicKey, - priceUpdateAccount: priceUpdateKeypair.publicKey, - treasury: getTreasuryPda(DEFAULT_TREASURY_ID), - config: getConfigPda(), + draftVaa: encodedVaaKeypair.publicKey, }) .instruction(), - signers: [priceUpdateKeypair], - computeUnits: POST_UPDATE_COMPUTE_BUDGET, + signers: [], }); - priceFeedIdToPriceAccount[ - "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex") - ] = priceUpdateKeypair.publicKey; + instructions.push({ + instruction: await this.wormhole.methods + .writeEncodedVaa({ + index: VAA_SPLIT_INDEX, + data: accumulatorUpdateData.vaa.subarray(VAA_SPLIT_INDEX), + }) + .accounts({ + draftVaa: encodedVaaKeypair.publicKey, + }) + .instruction(), + signers: [], + }); + + instructions.push({ + instruction: await this.wormhole.methods + .verifyEncodedVaaV1() + .accounts({ + guardianSet: getGuardianSetPda(guardianSetIndex), + draftVaa: encodedVaaKeypair.publicKey, + }) + .instruction(), + signers: [], + computeUnits: VERIFY_ENCODED_VAA_COMPUTE_BUDGET, + }); + + for (const update of accumulatorUpdateData.updates) { + const priceUpdateKeypair = new Keypair(); + instructions.push({ + instruction: await this.receiver.methods + .postUpdate({ + merklePriceUpdate: update, + treasuryId: DEFAULT_TREASURY_ID, + }) + .accounts({ + encodedVaa: encodedVaaKeypair.publicKey, + 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; + priceUpdateAddresses.push(priceUpdateKeypair.publicKey); + } } return { instructions, - priceFeedIdToPriceAccount, - encodedVaaAddress: encodedVaaKeypair.publicKey, + priceFeedIdToPriceUpdateAccount, + encodedVaaAddresses, + priceUpdateAddresses, }; } - async buildCloseEncodedVaa( + async buildCloseEncodedVaaInstruction( encodedVaa: PublicKey ): Promise { const instruction = await this.wormhole.methods @@ -281,7 +318,7 @@ export class PythSolanaReceiverConnection { return { instruction, signers: [] }; } - async buildClosePriceUpdate( + async buildClosePriceUpdateInstruction( priceUpdateAccount: PublicKey ): Promise { const instruction = await this.receiver.methods From 117a1660dfe667cab5713838dc19e4eacc652b22 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 16:54:30 +0000 Subject: [PATCH 38/64] Handle arrays --- .../sdk/js/pyth_solana_receiver/src/index.ts | 91 ++++++++----------- 1 file changed, 40 insertions(+), 51 deletions(-) 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 index 4127f5ced2..2def8c2e23 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -85,30 +85,15 @@ export class PythSolanaReceiver { ); const { - instructions, + postInstructions: instructions, priceFeedIdToPriceUpdateAccount: priceFeedIdToPriceUpdateAccount, - encodedVaaAddresses, - priceUpdateAddresses, + cleanupInstructions, } = await this.buildPostPriceUpdateInstructions(priceUpdateDataArray); builder.addInstructions(instructions); builder.addInstructions( await getInstructions(priceFeedIdToPriceUpdateAccount) ); - - await Promise.all( - encodedVaaAddresses.map(async (encodedVaaAddress) => - builder.addInstruction( - await this.buildCloseEncodedVaaInstruction(encodedVaaAddress) - ) - ) - ); - await Promise.all( - priceUpdateAddresses.map(async (priceUpdateAccount) => - builder.addInstruction( - await this.buildClosePriceUpdateInstruction(priceUpdateAccount) - ) - ) - ); + builder.addInstructions(cleanupInstructions); return builder.getVersionedTransactions(priorityFeeConfig ?? {}); } @@ -124,34 +109,28 @@ export class PythSolanaReceiver { this.connection ); const { - instructions, + postInstructions: instructions, priceFeedIdToPriceUpdateAccount, - priceUpdateAddresses, + cleanupInstructions, } = await this.buildPostPriceUpdateAtomicInstructions(priceUpdateDataArray); builder.addInstructions(instructions); builder.addInstructions( await getInstructions(priceFeedIdToPriceUpdateAccount) ); - await Promise.all( - priceUpdateAddresses.map(async (priceUpdateAccount) => - builder.addInstruction( - await this.buildClosePriceUpdateInstruction(priceUpdateAccount) - ) - ) - ); + builder.addInstructions(cleanupInstructions); return builder.getVersionedTransactions(priorityFeeConfig ?? {}); } async buildPostPriceUpdateAtomicInstructions( priceUpdateDataArray: string[] ): Promise<{ - instructions: InstructionWithEphemeralSigners[]; + postInstructions: InstructionWithEphemeralSigners[]; priceFeedIdToPriceUpdateAccount: Record; - priceUpdateAddresses: PublicKey[]; + cleanupInstructions: InstructionWithEphemeralSigners[]; }> { - const instructions: InstructionWithEphemeralSigners[] = []; + const postInstructions: InstructionWithEphemeralSigners[] = []; const priceFeedIdToPriceUpdateAccount: Record = {}; - const priceUpdateAddresses: PublicKey[] = []; + const cleanupInstructions: InstructionWithEphemeralSigners[] = []; for (const priceUpdateData of priceUpdateDataArray) { const accumulatorUpdateData = parseAccumulatorUpdateData( @@ -165,7 +144,7 @@ export class PythSolanaReceiver { for (const update of accumulatorUpdateData.updates) { const priceUpdateKeypair = new Keypair(); - instructions.push({ + postInstructions.push({ instruction: await this.receiver.methods .postUpdateAtomic({ vaa: trimmedVaa, @@ -185,28 +164,31 @@ export class PythSolanaReceiver { priceFeedIdToPriceUpdateAccount[ "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex") ] = priceUpdateKeypair.publicKey; - priceUpdateAddresses.push(priceUpdateKeypair.publicKey); + + cleanupInstructions.push( + await this.buildClosePriceUpdateInstruction( + priceUpdateKeypair.publicKey + ) + ); } } return { - instructions, + postInstructions, priceFeedIdToPriceUpdateAccount, - priceUpdateAddresses, + cleanupInstructions, }; } async buildPostPriceUpdateInstructions( priceUpdateDataArray: string[] ): Promise<{ - instructions: InstructionWithEphemeralSigners[]; + postInstructions: InstructionWithEphemeralSigners[]; priceFeedIdToPriceUpdateAccount: Record; - encodedVaaAddresses: PublicKey[]; - priceUpdateAddresses: PublicKey[]; + cleanupInstructions: InstructionWithEphemeralSigners[]; }> { - const instructions: InstructionWithEphemeralSigners[] = []; + const postInstructions: InstructionWithEphemeralSigners[] = []; const priceFeedIdToPriceUpdateAccount: Record = {}; - const priceUpdateAddresses: PublicKey[] = []; - const encodedVaaAddresses: PublicKey[] = []; + const cleanupInstructions: InstructionWithEphemeralSigners[] = []; for (const priceUpdateData of priceUpdateDataArray) { const accumulatorUpdateData = parseAccumulatorUpdateData( @@ -218,7 +200,7 @@ export class PythSolanaReceiver { const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); - instructions.push({ + postInstructions.push({ instruction: await this.wormhole.account.encodedVaa.createInstruction( encodedVaaKeypair, encodedVaaSize @@ -226,7 +208,7 @@ export class PythSolanaReceiver { signers: [encodedVaaKeypair], }); - instructions.push({ + postInstructions.push({ instruction: await this.wormhole.methods .initEncodedVaa() .accounts({ @@ -236,7 +218,7 @@ export class PythSolanaReceiver { signers: [], }); - instructions.push({ + postInstructions.push({ instruction: await this.wormhole.methods .writeEncodedVaa({ index: 0, @@ -249,7 +231,7 @@ export class PythSolanaReceiver { signers: [], }); - instructions.push({ + postInstructions.push({ instruction: await this.wormhole.methods .writeEncodedVaa({ index: VAA_SPLIT_INDEX, @@ -262,7 +244,7 @@ export class PythSolanaReceiver { signers: [], }); - instructions.push({ + postInstructions.push({ instruction: await this.wormhole.methods .verifyEncodedVaaV1() .accounts({ @@ -274,9 +256,13 @@ export class PythSolanaReceiver { computeUnits: VERIFY_ENCODED_VAA_COMPUTE_BUDGET, }); + cleanupInstructions.push( + await this.buildCloseEncodedVaaInstruction(encodedVaaKeypair.publicKey) + ); + for (const update of accumulatorUpdateData.updates) { const priceUpdateKeypair = new Keypair(); - instructions.push({ + postInstructions.push({ instruction: await this.receiver.methods .postUpdate({ merklePriceUpdate: update, @@ -296,15 +282,18 @@ export class PythSolanaReceiver { priceFeedIdToPriceUpdateAccount[ "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex") ] = priceUpdateKeypair.publicKey; - priceUpdateAddresses.push(priceUpdateKeypair.publicKey); + cleanupInstructions.push( + await this.buildClosePriceUpdateInstruction( + priceUpdateKeypair.publicKey + ) + ); } } return { - instructions, + postInstructions, priceFeedIdToPriceUpdateAccount, - encodedVaaAddresses, - priceUpdateAddresses, + cleanupInstructions, }; } From 2e044b08e1f946e7cfd60e285127d479cad72b98 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 17:05:19 +0000 Subject: [PATCH 39/64] Move PythSolanaReceiver --- .../src/PythSolanaReceiver.ts | 319 +++++++++++++++++ .../sdk/js/pyth_solana_receiver/src/index.ts | 320 +----------------- 2 files changed, 320 insertions(+), 319 deletions(-) create mode 100644 target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts 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 0000000000..2def8c2e23 --- /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 { + DEFAULT_REDUCED_GUARDIAN_SET_SIZE, + DEFAULT_TREASURY_ID, + POST_UPDATE_ATOMIC_COMPUTE_BUDGET, + POST_UPDATE_COMPUTE_BUDGET, + VAA_SPLIT_INDEX, + VAA_START, + VERIFY_ENCODED_VAA_COMPUTE_BUDGET, +} from "./constants"; +import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; +import { getGuardianSetIndex, trimSignatures } from "./vaa"; +import { + TransactionBuilder, + InstructionWithEphemeralSigners, +} from "@pythnetwork/solana-utils"; +import { priorityFeeConfig as PriorityFeeConfig } from "@pythnetwork/solana-utils/lib/transaction"; + +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 builder = new TransactionBuilder( + this.wallet.publicKey, + this.connection + ); + + const { + postInstructions: instructions, + priceFeedIdToPriceUpdateAccount: priceFeedIdToPriceUpdateAccount, + cleanupInstructions, + } = await this.buildPostPriceUpdateInstructions(priceUpdateDataArray); + builder.addInstructions(instructions); + builder.addInstructions( + await getInstructions(priceFeedIdToPriceUpdateAccount) + ); + builder.addInstructions(cleanupInstructions); + return builder.getVersionedTransactions(priorityFeeConfig ?? {}); + } + + async withPartiallyVerifiedPriceUpdate( + priceUpdateDataArray: string[], + getInstructions: ( + priceFeedIdToPriceUpdateAccount: Record + ) => Promise, + priorityFeeConfig?: PriorityFeeConfig + ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { + const builder = new TransactionBuilder( + this.wallet.publicKey, + this.connection + ); + const { + postInstructions: instructions, + priceFeedIdToPriceUpdateAccount, + cleanupInstructions, + } = await this.buildPostPriceUpdateAtomicInstructions(priceUpdateDataArray); + builder.addInstructions(instructions); + builder.addInstructions( + await getInstructions(priceFeedIdToPriceUpdateAccount) + ); + builder.addInstructions(cleanupInstructions); + return builder.getVersionedTransactions(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, + DEFAULT_REDUCED_GUARDIAN_SET_SIZE + ); + + 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 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 encodedVaaKeypair = new Keypair(); + const encodedVaaSize = accumulatorUpdateData.vaa.length + VAA_START; + + const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); + + postInstructions.push({ + instruction: await this.wormhole.account.encodedVaa.createInstruction( + encodedVaaKeypair, + encodedVaaSize + ), + signers: [encodedVaaKeypair], + }); + + postInstructions.push({ + instruction: await this.wormhole.methods + .initEncodedVaa() + .accounts({ + encodedVaa: encodedVaaKeypair.publicKey, + }) + .instruction(), + signers: [], + }); + + postInstructions.push({ + instruction: await this.wormhole.methods + .writeEncodedVaa({ + index: 0, + data: accumulatorUpdateData.vaa.subarray(0, VAA_SPLIT_INDEX), + }) + .accounts({ + draftVaa: encodedVaaKeypair.publicKey, + }) + .instruction(), + signers: [], + }); + + postInstructions.push({ + instruction: await this.wormhole.methods + .writeEncodedVaa({ + index: VAA_SPLIT_INDEX, + data: accumulatorUpdateData.vaa.subarray(VAA_SPLIT_INDEX), + }) + .accounts({ + draftVaa: encodedVaaKeypair.publicKey, + }) + .instruction(), + signers: [], + }); + + 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) + ); + + 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: encodedVaaKeypair.publicKey, + 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/index.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts index 2def8c2e23..f411fa8b8b 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -1,319 +1 @@ -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 { - DEFAULT_REDUCED_GUARDIAN_SET_SIZE, - DEFAULT_TREASURY_ID, - POST_UPDATE_ATOMIC_COMPUTE_BUDGET, - POST_UPDATE_COMPUTE_BUDGET, - VAA_SPLIT_INDEX, - VAA_START, - VERIFY_ENCODED_VAA_COMPUTE_BUDGET, -} from "./constants"; -import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; -import { getGuardianSetIndex, trimSignatures } from "./vaa"; -import { - TransactionBuilder, - InstructionWithEphemeralSigners, -} from "@pythnetwork/solana-utils"; -import { priorityFeeConfig as PriorityFeeConfig } from "@pythnetwork/solana-utils/lib/transaction"; - -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 builder = new TransactionBuilder( - this.wallet.publicKey, - this.connection - ); - - const { - postInstructions: instructions, - priceFeedIdToPriceUpdateAccount: priceFeedIdToPriceUpdateAccount, - cleanupInstructions, - } = await this.buildPostPriceUpdateInstructions(priceUpdateDataArray); - builder.addInstructions(instructions); - builder.addInstructions( - await getInstructions(priceFeedIdToPriceUpdateAccount) - ); - builder.addInstructions(cleanupInstructions); - return builder.getVersionedTransactions(priorityFeeConfig ?? {}); - } - - async withPartiallyVerifiedPriceUpdate( - priceUpdateDataArray: string[], - getInstructions: ( - priceFeedIdToPriceUpdateAccount: Record - ) => Promise, - priorityFeeConfig?: PriorityFeeConfig - ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { - const builder = new TransactionBuilder( - this.wallet.publicKey, - this.connection - ); - const { - postInstructions: instructions, - priceFeedIdToPriceUpdateAccount, - cleanupInstructions, - } = await this.buildPostPriceUpdateAtomicInstructions(priceUpdateDataArray); - builder.addInstructions(instructions); - builder.addInstructions( - await getInstructions(priceFeedIdToPriceUpdateAccount) - ); - builder.addInstructions(cleanupInstructions); - return builder.getVersionedTransactions(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, - DEFAULT_REDUCED_GUARDIAN_SET_SIZE - ); - - 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 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 encodedVaaKeypair = new Keypair(); - const encodedVaaSize = accumulatorUpdateData.vaa.length + VAA_START; - - const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); - - postInstructions.push({ - instruction: await this.wormhole.account.encodedVaa.createInstruction( - encodedVaaKeypair, - encodedVaaSize - ), - signers: [encodedVaaKeypair], - }); - - postInstructions.push({ - instruction: await this.wormhole.methods - .initEncodedVaa() - .accounts({ - encodedVaa: encodedVaaKeypair.publicKey, - }) - .instruction(), - signers: [], - }); - - postInstructions.push({ - instruction: await this.wormhole.methods - .writeEncodedVaa({ - index: 0, - data: accumulatorUpdateData.vaa.subarray(0, VAA_SPLIT_INDEX), - }) - .accounts({ - draftVaa: encodedVaaKeypair.publicKey, - }) - .instruction(), - signers: [], - }); - - postInstructions.push({ - instruction: await this.wormhole.methods - .writeEncodedVaa({ - index: VAA_SPLIT_INDEX, - data: accumulatorUpdateData.vaa.subarray(VAA_SPLIT_INDEX), - }) - .accounts({ - draftVaa: encodedVaaKeypair.publicKey, - }) - .instruction(), - signers: [], - }); - - 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) - ); - - 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: encodedVaaKeypair.publicKey, - 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: [] }; - } -} +export { PythSolanaReceiver } from "./PythSolanaReceiver"; From 66bdbc7cecdad5b75638c6665cf38f7f944d51a1 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 17:23:45 +0000 Subject: [PATCH 40/64] Cleanup constants --- .../src/PythSolanaReceiver.ts | 140 +++++++++--------- .../js/pyth_solana_receiver/src/constants.ts | 4 +- .../sdk/js/pyth_solana_receiver/src/vaa.ts | 57 ++++++- 3 files changed, 129 insertions(+), 72 deletions(-) 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 index 2def8c2e23..3f216ad05b 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts @@ -25,12 +25,15 @@ import { DEFAULT_TREASURY_ID, POST_UPDATE_ATOMIC_COMPUTE_BUDGET, POST_UPDATE_COMPUTE_BUDGET, - VAA_SPLIT_INDEX, - VAA_START, VERIFY_ENCODED_VAA_COMPUTE_BUDGET, } from "./constants"; import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; -import { getGuardianSetIndex, trimSignatures } from "./vaa"; +import { + buildEncodedVaaCreateInstruction, + buildWriteEncodedVaaWithSplit, + getGuardianSetIndex, + trimSignatures, +} from "./vaa"; import { TransactionBuilder, InstructionWithEphemeralSigners, @@ -179,6 +182,64 @@ export class PythSolanaReceiver { }; } + 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<{ @@ -195,70 +256,13 @@ export class PythSolanaReceiver { Buffer.from(priceUpdateData, "base64") ); - const encodedVaaKeypair = new Keypair(); - const encodedVaaSize = accumulatorUpdateData.vaa.length + VAA_START; - - const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); - - postInstructions.push({ - instruction: await this.wormhole.account.encodedVaa.createInstruction( - encodedVaaKeypair, - encodedVaaSize - ), - signers: [encodedVaaKeypair], - }); - - postInstructions.push({ - instruction: await this.wormhole.methods - .initEncodedVaa() - .accounts({ - encodedVaa: encodedVaaKeypair.publicKey, - }) - .instruction(), - signers: [], - }); - - postInstructions.push({ - instruction: await this.wormhole.methods - .writeEncodedVaa({ - index: 0, - data: accumulatorUpdateData.vaa.subarray(0, VAA_SPLIT_INDEX), - }) - .accounts({ - draftVaa: encodedVaaKeypair.publicKey, - }) - .instruction(), - signers: [], - }); - - postInstructions.push({ - instruction: await this.wormhole.methods - .writeEncodedVaa({ - index: VAA_SPLIT_INDEX, - data: accumulatorUpdateData.vaa.subarray(VAA_SPLIT_INDEX), - }) - .accounts({ - draftVaa: encodedVaaKeypair.publicKey, - }) - .instruction(), - signers: [], - }); - - 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) - ); + 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(); @@ -269,7 +273,7 @@ export class PythSolanaReceiver { treasuryId: DEFAULT_TREASURY_ID, }) .accounts({ - encodedVaa: encodedVaaKeypair.publicKey, + encodedVaa, priceUpdateAccount: priceUpdateKeypair.publicKey, treasury: getTreasuryPda(DEFAULT_TREASURY_ID), config: getConfigPda(), diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts index 078b18ddc6..ef0bcb6139 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts @@ -1,8 +1,6 @@ -export const VAA_START = 46; -export const VAA_SPLIT_INDEX = 792; -export const VAA_SIGNATURE_SIZE = 66; export const DEFAULT_TREASURY_ID = 0; export const DEFAULT_REDUCED_GUARDIAN_SET_SIZE = 5; + 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/vaa.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts index 6b6737cf77..ee3ed8e9e0 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts @@ -1,4 +1,11 @@ -import { VAA_SIGNATURE_SIZE } from "./constants"; +import { Keypair, PublicKey, TransactionInstruction } 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 function getGuardianSetIndex(vaa: Buffer) { return vaa.readUInt32BE(1); @@ -20,3 +27,51 @@ export function trimSignatures(vaa: Buffer, n: number): Buffer { 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: [], + }, + ]; +} From 0068d5f717c50b1dc40478ee72a93b5920731e6a Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 17:24:36 +0000 Subject: [PATCH 41/64] Contants --- target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index ee3ed8e9e0..1e1b29c763 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts @@ -1,4 +1,4 @@ -import { Keypair, PublicKey, TransactionInstruction } from "@solana/web3.js"; +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"; From dcdd39f9c066f526a65fe23fea88b579665e5be1 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 17:28:59 +0000 Subject: [PATCH 42/64] Refactor constants --- .../js/pyth_solana_receiver/src/PythSolanaReceiver.ts | 11 ++++------- .../src/{constants.ts => compute_budget.ts} | 3 --- .../solana/sdk/js/pyth_solana_receiver/src/vaa.ts | 6 +++++- 3 files changed, 9 insertions(+), 11 deletions(-) rename target_chains/solana/sdk/js/pyth_solana_receiver/src/{constants.ts => compute_budget.ts} (64%) 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 index 3f216ad05b..8f274f7d60 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts @@ -21,12 +21,10 @@ import { parsePriceFeedMessage, } from "@pythnetwork/price-service-sdk"; import { - DEFAULT_REDUCED_GUARDIAN_SET_SIZE, - DEFAULT_TREASURY_ID, POST_UPDATE_ATOMIC_COMPUTE_BUDGET, POST_UPDATE_COMPUTE_BUDGET, VERIFY_ENCODED_VAA_COMPUTE_BUDGET, -} from "./constants"; +} from "./compute_budget"; import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; import { buildEncodedVaaCreateInstruction, @@ -40,6 +38,8 @@ import { } from "@pythnetwork/solana-utils"; import { priorityFeeConfig as PriorityFeeConfig } from "@pythnetwork/solana-utils/lib/transaction"; +export const DEFAULT_TREASURY_ID = 0; + export class PythSolanaReceiver { readonly connection: Connection; readonly wallet: Wallet; @@ -140,10 +140,7 @@ export class PythSolanaReceiver { Buffer.from(priceUpdateData, "base64") ); const guardianSetIndex = getGuardianSetIndex(accumulatorUpdateData.vaa); - const trimmedVaa = trimSignatures( - accumulatorUpdateData.vaa, - DEFAULT_REDUCED_GUARDIAN_SET_SIZE - ); + const trimmedVaa = trimSignatures(accumulatorUpdateData.vaa); for (const update of accumulatorUpdateData.updates) { const priceUpdateKeypair = new Keypair(); diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/compute_budget.ts similarity index 64% rename from target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts rename to target_chains/solana/sdk/js/pyth_solana_receiver/src/compute_budget.ts index ef0bcb6139..914f4df7bd 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/constants.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/compute_budget.ts @@ -1,6 +1,3 @@ -export const DEFAULT_TREASURY_ID = 0; -export const DEFAULT_REDUCED_GUARDIAN_SET_SIZE = 5; - 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/vaa.ts b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts index 1e1b29c763..0adb7af264 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts @@ -6,12 +6,16 @@ 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: number): Buffer { +export function trimSignatures( + vaa: Buffer, + n = DEFAULT_REDUCED_GUARDIAN_SET_SIZE +): Buffer { const currentNumSignatures = vaa[5]; if (n > currentNumSignatures) { throw new Error( From 9a13bb62872acd4b5036b768c85024bc783732e8 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 17:43:58 +0000 Subject: [PATCH 43/64] Gitignore refactor --- target_chains/solana/.gitignore | 1 + target_chains/solana/sdk/js/pyth_solana_receiver/.gitignore | 2 -- target_chains/solana/sdk/js/solana_utils/.gitignore | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 target_chains/solana/sdk/js/pyth_solana_receiver/.gitignore delete mode 100644 target_chains/solana/sdk/js/solana_utils/.gitignore diff --git a/target_chains/solana/.gitignore b/target_chains/solana/.gitignore index d243ecc136..250cb88d92 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/.gitignore b/target_chains/solana/sdk/js/pyth_solana_receiver/.gitignore deleted file mode 100644 index 491fc35975..0000000000 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -lib diff --git a/target_chains/solana/sdk/js/solana_utils/.gitignore b/target_chains/solana/sdk/js/solana_utils/.gitignore deleted file mode 100644 index 491fc35975..0000000000 --- a/target_chains/solana/sdk/js/solana_utils/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -lib From 3a2562c1d0eeabe2d4f125146aba6ac9ef218529 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 17:47:48 +0000 Subject: [PATCH 44/64] package lock --- package-lock.json | 148 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/package-lock.json b/package-lock.json index d62812d3e1..ed04eda0f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "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": { @@ -13197,6 +13198,10 @@ "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 @@ -59525,7 +59530,90 @@ "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": { @@ -71408,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": { From 6b2d09bb027518d19436eee35c45be48018af196 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 17:49:27 +0000 Subject: [PATCH 45/64] Cleanup idl --- .../src/idl/pyth_solana_receiver.json | 602 ------------------ 1 file changed, 602 deletions(-) delete mode 100644 target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.json diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.json b/target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.json deleted file mode 100644 index d21710d1a3..0000000000 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/idl/pyth_solana_receiver.json +++ /dev/null @@ -1,602 +0,0 @@ -{ - "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" - } - ] -} From ecd2c4dc7df7707db4afaddf973bc7bef5280b45 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 17:56:29 +0000 Subject: [PATCH 46/64] Add useful static --- .../src/PythSolanaReceiver.ts | 2 +- .../sdk/js/solana_utils/src/transaction.ts | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) 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 index 8f274f7d60..4bca3b9d67 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts @@ -36,7 +36,7 @@ import { TransactionBuilder, InstructionWithEphemeralSigners, } from "@pythnetwork/solana-utils"; -import { priorityFeeConfig as PriorityFeeConfig } from "@pythnetwork/solana-utils/lib/transaction"; +import { PriorityFeeConfig } from "@pythnetwork/solana-utils/lib/transaction"; export const DEFAULT_TREASURY_ID = 0; diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts index ada7d98e17..0303f6f36c 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -20,7 +20,7 @@ export type InstructionWithEphemeralSigners = { computeUnits?: number; }; -export type priorityFeeConfig = { +export type PriorityFeeConfig = { computeUnitPriceMicroLamports?: number; }; @@ -139,7 +139,7 @@ export class TransactionBuilder { * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it */ async getVersionedTransactions( - args: priorityFeeConfig + args: PriorityFeeConfig ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { const blockhash = (await this.connection.getLatestBlockhash()).blockhash; @@ -179,7 +179,7 @@ export class TransactionBuilder { * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it */ getLegacyTransactions( - args: priorityFeeConfig + args: PriorityFeeConfig ): { tx: Transaction; signers: Signer[] }[] { return this.transactionInstructions.map(({ instructions, signers }) => { const instructionsWithComputeBudget = args.computeUnitPriceMicroLamports @@ -212,4 +212,17 @@ export class TransactionBuilder { return tx; }); } + + static async batchIntoVersionedTransactions( + payer: PublicKey, + connection: Connection, + instructions: InstructionWithEphemeralSigners[], + priorityFeeConfig: PriorityFeeConfig + ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { + const transactionBuilder = new TransactionBuilder(payer, connection); + for (const instruction of instructions) { + transactionBuilder.addInstructions(instructions); + } + return transactionBuilder.getVersionedTransactions(priorityFeeConfig); + } } From 4b0094a5809110e700257927ccdbecf40d11605e Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 17:57:01 +0000 Subject: [PATCH 47/64] Add useful static --- target_chains/solana/sdk/js/solana_utils/src/transaction.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts index 0303f6f36c..52e32fd46a 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -220,9 +220,7 @@ export class TransactionBuilder { priorityFeeConfig: PriorityFeeConfig ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { const transactionBuilder = new TransactionBuilder(payer, connection); - for (const instruction of instructions) { - transactionBuilder.addInstructions(instructions); - } + transactionBuilder.addInstructions(instructions); return transactionBuilder.getVersionedTransactions(priorityFeeConfig); } } From 5926166be3da1efa05f27eb3608f1b1b0fc02f7c Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 18:04:29 +0000 Subject: [PATCH 48/64] Add useful static --- .../sdk/js/pyth_solana_receiver/package.json | 1 - .../src/PythSolanaReceiver.ts | 41 +++++++++---------- .../sdk/js/pyth_solana_receiver/src/index.ts | 1 + 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/package.json b/target_chains/solana/sdk/js/pyth_solana_receiver/package.json index a3a3251261..1cf07c5051 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/package.json +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/package.json @@ -17,7 +17,6 @@ "access": "public" }, "scripts": { - "test": "jest", "build": "tsc", "format": "prettier --write \"src/**/*.ts\"", "lint": "eslint src/", 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 index 4bca3b9d67..c0ec4218c4 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts @@ -82,22 +82,21 @@ export class PythSolanaReceiver { ) => Promise, priorityFeeConfig?: PriorityFeeConfig ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { - const builder = new TransactionBuilder( - this.wallet.publicKey, - this.connection - ); - const { - postInstructions: instructions, + postInstructions, priceFeedIdToPriceUpdateAccount: priceFeedIdToPriceUpdateAccount, cleanupInstructions, } = await this.buildPostPriceUpdateInstructions(priceUpdateDataArray); - builder.addInstructions(instructions); - builder.addInstructions( - await getInstructions(priceFeedIdToPriceUpdateAccount) + return TransactionBuilder.batchIntoVersionedTransactions( + this.wallet.publicKey, + this.connection, + [ + ...postInstructions, + ...(await getInstructions(priceFeedIdToPriceUpdateAccount)), + ...cleanupInstructions, + ], + priorityFeeConfig ?? {} ); - builder.addInstructions(cleanupInstructions); - return builder.getVersionedTransactions(priorityFeeConfig ?? {}); } async withPartiallyVerifiedPriceUpdate( @@ -107,21 +106,21 @@ export class PythSolanaReceiver { ) => Promise, priorityFeeConfig?: PriorityFeeConfig ): Promise<{ tx: VersionedTransaction; signers: Signer[] }[]> { - const builder = new TransactionBuilder( - this.wallet.publicKey, - this.connection - ); const { - postInstructions: instructions, + postInstructions, priceFeedIdToPriceUpdateAccount, cleanupInstructions, } = await this.buildPostPriceUpdateAtomicInstructions(priceUpdateDataArray); - builder.addInstructions(instructions); - builder.addInstructions( - await getInstructions(priceFeedIdToPriceUpdateAccount) + return TransactionBuilder.batchIntoVersionedTransactions( + this.wallet.publicKey, + this.connection, + [ + ...postInstructions, + ...(await getInstructions(priceFeedIdToPriceUpdateAccount)), + ...cleanupInstructions, + ], + priorityFeeConfig ?? {} ); - builder.addInstructions(cleanupInstructions); - return builder.getVersionedTransactions(priorityFeeConfig ?? {}); } async buildPostPriceUpdateAtomicInstructions( 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 index f411fa8b8b..f4f83959ae 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -1 +1,2 @@ export { PythSolanaReceiver } from "./PythSolanaReceiver"; +export { TransactionBuilder } from "@pythnetwork/solana-utils"; From 6290cab51be753603ed21b85a76588380cd40a08 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 18:13:35 +0000 Subject: [PATCH 49/64] Lint --- target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 0adb7af264..b7a08ac7bf 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts @@ -23,7 +23,7 @@ export function trimSignatures( ); } - let trimmedVaa = Buffer.concat([ + const trimmedVaa = Buffer.concat([ vaa.subarray(0, 6 + n * VAA_SIGNATURE_SIZE), vaa.subarray(6 + currentNumSignatures * VAA_SIGNATURE_SIZE), ]); From b92e8cec7894751aaa4a66f8b353a638d48e354d Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 28 Feb 2024 18:24:23 +0000 Subject: [PATCH 50/64] Add lint config --- .../solana/sdk/js/pyth_solana_receiver/.eslintrc.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 target_chains/solana/sdk/js/pyth_solana_receiver/.eslintrc.js 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 0000000000..bb71486c02 --- /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", + }, +}; From 00554e951fa918f97a21d3f7e00cab95e35310cf Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 29 Feb 2024 14:06:31 +0000 Subject: [PATCH 51/64] Docs --- .../src/PythSolanaReceiver.ts | 4 +- .../sdk/js/pyth_solana_receiver/src/vaa.ts | 60 ++++++++++++++++--- 2 files changed, 55 insertions(+), 9 deletions(-) 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 index c0ec4218c4..200bb97545 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts @@ -28,7 +28,7 @@ import { import { Wallet } from "@coral-xyz/anchor/dist/cjs/provider"; import { buildEncodedVaaCreateInstruction, - buildWriteEncodedVaaWithSplit, + buildWriteEncodedVaaWithSplitInstructions, getGuardianSetIndex, trimSignatures, } from "./vaa"; @@ -206,7 +206,7 @@ export class PythSolanaReceiver { }); postInstructions.push( - ...(await buildWriteEncodedVaaWithSplit( + ...(await buildWriteEncodedVaaWithSplitInstructions( this.wormhole, vaa, encodedVaaKeypair.publicKey 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 index b7a08ac7bf..5bfb6f85be 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts @@ -3,15 +3,34 @@ 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; - +/** + * Get the index of the guardian set that signed a VAA + */ export function getGuardianSetIndex(vaa: Buffer) { return vaa.readUInt32BE(1); } +/** + * The default number of signatures to keep in a VAA when using `trimSignatures`. + * This number was chosen as the maximum number of signatures so that the VAA's contents can be posted in a single Solana transaction. + */ +export const DEFAULT_REDUCED_GUARDIAN_SET_SIZE = 5; + +/** + * The size of a guardian signature in a VAA. + * + * It is 66 bytes long, the first byte is the guardian index and the next 65 bytes are the signature (including a recovery id). + */ +export const VAA_SIGNATURE_SIZE = 66; + +/** + * Trim the number of signatures of a VAA. + * + * @returns the same VAA as the input, but with `n` signatures instead of the original number of signatures. + * + * A Wormhole VAA typically has a number of signatures equal to two thirds of the number of guardians. However, + * this function is useful to make VAAs smaller to post their contents in a single Solana transaction. + */ export function trimSignatures( vaa: Buffer, n = DEFAULT_REDUCED_GUARDIAN_SET_SIZE @@ -32,11 +51,21 @@ export function trimSignatures( return trimmedVaa; } +/** + * The start of the VAA bytes in an encoded VAA account. Before this offset, the account contains a header. + */ +export const VAA_START = 46; + +/** + * Build an instruction to create an encoded VAA account. + * + * This is the first step to post a VAA to the Wormhole program. + */ export async function buildEncodedVaaCreateInstruction( wormhole: Program, vaa: Buffer, encodedVaaKeypair: Keypair -) { +): Promise { const encodedVaaSize = vaa.length + VAA_START; return { instruction: await wormhole.account.encodedVaa.createInstruction( @@ -47,7 +76,24 @@ export async function buildEncodedVaaCreateInstruction( }; } -export async function buildWriteEncodedVaaWithSplit( +/** + * When we are writing to an encoded VAA account, we do it in two instructions. + * + * We first write the first `VAA_SPLIT_INDEX` bytes and then the rest. + * + * This number was chosen as the biggest number such that we can still call `createInstruction`, `initEncodedVaa` and `writeEncodedVaa` in a single Solana transaction. + */ +export const VAA_SPLIT_INDEX = 792; + +/** + * Build a set of instructions to write a VAA to an encoded VAA account + * This functions returns 2 instructions and splits the VAA in an opinionated way, so that the whole process of posting a VAA can be efficiently packed in the 2 transactions: + * + * TX 1 : `createInstruction` + `initEncodedVaa` + `writeEncodedVaa_1` + * + * TX 2 : `writeEncodedVaa_2` + `verifyEncodedVaaV1` + */ +export async function buildWriteEncodedVaaWithSplitInstructions( wormhole: Program, vaa: Buffer, draftVaa: PublicKey From a5c0fc47313e28cfefb73705c09563c20a2a8a4a Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 29 Feb 2024 17:33:14 +0000 Subject: [PATCH 52/64] Comments --- .../src/PythSolanaReceiver.ts | 86 +++++++++++++++++-- .../js/pyth_solana_receiver/src/address.ts | 41 +++++++-- .../src/compute_budget.ts | 9 ++ .../sdk/js/pyth_solana_receiver/src/vaa.ts | 2 +- .../sdk/js/solana_utils/src/transaction.ts | 54 ++++++++++-- 5 files changed, 170 insertions(+), 22 deletions(-) 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 index 200bb97545..809ce36ec0 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts @@ -10,6 +10,7 @@ import { } from "./idl/wormhole_core_bridge_solana"; import { DEFAULT_RECEIVER_PROGRAM_ID, + DEFAULT_TREASURY_ID, DEFAULT_WORMHOLE_PROGRAM_ID, getConfigPda, getGuardianSetPda, @@ -38,8 +39,14 @@ import { } from "@pythnetwork/solana-utils"; import { PriorityFeeConfig } from "@pythnetwork/solana-utils/lib/transaction"; -export const DEFAULT_TREASURY_ID = 0; - +/** + * A class to interact with the Pyth Solana Receiver program. + * + * This class provides helpful methods to: + * - Post price updates from Pythnet to the Pyth Solana Receiver program + * - Consume price updates in a consumer program + * - Cleanup price update accounts to recover rent + */ export class PythSolanaReceiver { readonly connection: Connection; readonly wallet: Wallet; @@ -75,6 +82,13 @@ export class PythSolanaReceiver { ); } + /** + * Build a series of transactions that post price updates to the Pyth Solana Receiver program, consume them in a consumer program and cleanup the encoded vaa accounts and price update accounts. + * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates. + * @param getInstructions a function that given a map of price feed IDs to price update accounts, returns a series of instructions to consume the price updates in a consumer program. This function is a way for the user to indicate which accounts in their instruction need to be "replaced" with price update accounts. + * @param priorityFeeConfig a configuration for the compute unit price to use for the transactions. + * @returns an array of transactions and their corresponding ephemeral signers + */ async withPriceUpdate( priceUpdateDataArray: string[], getInstructions: ( @@ -99,6 +113,17 @@ export class PythSolanaReceiver { ); } + /** + * Build a series of transactions that post partially verified price updates to the Pyth Solana Receiver program, consume them in a consumer program and cleanup the price update accounts. + * + * Partially verified price updates are price updates where not all the guardian signatures have been verified. By default this methods checks `DEFAULT_REDUCED_GUARDIAN_SET_SIZE` signatures when posting the VAA. + * If you are a on-chain program developer, make sure you understand the risks of consuming partially verified price updates here: {@link https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/solana/pyth_solana_receiver_state/src/price_update.rs}. + * + * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates. + * @param getInstructions a function that given a map of price feed IDs to price update accounts, returns a series of instructions to consume the price updates in a consumer program. This function is a way for the user to indicate which accounts in their instruction need to be "replaced" with price update accounts. + * @param priorityFeeConfig a configuration for the compute unit price to use for the transactions. + * @returns an array of transactions and their corresponding ephemeral signers + */ async withPartiallyVerifiedPriceUpdate( priceUpdateDataArray: string[], getInstructions: ( @@ -123,6 +148,17 @@ export class PythSolanaReceiver { ); } + /** + * Build a series of helper instructions that post price updates to the Pyth Solana Receiver program and another series to clean up the price update accounts. + * + * This function uses partially verified price updates. Partially verified price updates are price updates where not all the guardian signatures have been verified. By default this methods checks `DEFAULT_REDUCED_GUARDIAN_SET_SIZE` signatures when posting the VAA. + * If you are a on-chain program developer, make sure you understand the risks of consuming partially verified price updates here: {@link https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/solana/pyth_solana_receiver_state/src/price_update.rs}. + * + * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates. + * @returns `postInstructions`: the instructions to post the price updates, these should be called before consuming the price updates + * @returns `priceFeedIdToPriceUpdateAccount`: this is a map of price feed IDs to Solana address. Given a price feed ID, you can use this map to find the account where `postInstructions` will post the price update. + * @returns `cleanupInstructions`: the instructions to clean up the price update accounts, these should be called after consuming the price updates + */ async buildPostPriceUpdateAtomicInstructions( priceUpdateDataArray: string[] ): Promise<{ @@ -152,9 +188,15 @@ export class PythSolanaReceiver { }) .accounts({ priceUpdateAccount: priceUpdateKeypair.publicKey, - treasury: getTreasuryPda(DEFAULT_TREASURY_ID), - config: getConfigPda(), - guardianSet: getGuardianSetPda(guardianSetIndex), + treasury: getTreasuryPda( + DEFAULT_TREASURY_ID, + this.receiver.programId + ), + config: getConfigPda(this.receiver.programId), + guardianSet: getGuardianSetPda( + guardianSetIndex, + this.wormhole.programId + ), }) .instruction(), signers: [priceUpdateKeypair], @@ -178,6 +220,14 @@ export class PythSolanaReceiver { }; } + /** + * Build a series of helper instructions that post a VAA in an encoded VAA account. This function is bespoke for posting Pyth VAAs and might not work for other usecases. + * + * @param vaa a Wormhole VAA + * @returns `postInstructions`: the instructions to post the VAA + * @returns `encodedVaaAddress`: the address of the encoded VAA account where the VAA will be posted + * @returns `cleanupInstructions`: the instructions to clean up the encoded VAA account + */ async buildPostEncodedVaaInstructions(vaa: Buffer): Promise<{ postInstructions: InstructionWithEphemeralSigners[]; encodedVaaAddress: PublicKey; @@ -217,7 +267,10 @@ export class PythSolanaReceiver { instruction: await this.wormhole.methods .verifyEncodedVaaV1() .accounts({ - guardianSet: getGuardianSetPda(guardianSetIndex), + guardianSet: getGuardianSetPda( + guardianSetIndex, + this.wormhole.programId + ), draftVaa: encodedVaaKeypair.publicKey, }) .instruction(), @@ -236,6 +289,14 @@ export class PythSolanaReceiver { }; } + /** + * Build a series of helper instructions that post price updates to the Pyth Solana Receiver program and another series to clean up the encoded vaa accounts and the price update accounts. + * + * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates. + * @returns `postInstructions`: the instructions to post the price updates, these should be called before consuming the price updates + * @returns `priceFeedIdToPriceUpdateAccount`: this is a map of price feed IDs to Solana address. Given a price feed ID, you can use this map to find the account where `postInstructions` will post the price update. + * @returns `cleanupInstructions`: the instructions to clean up the price update accounts, these should be called after consuming the price updates + */ async buildPostPriceUpdateInstructions( priceUpdateDataArray: string[] ): Promise<{ @@ -271,8 +332,11 @@ export class PythSolanaReceiver { .accounts({ encodedVaa, priceUpdateAccount: priceUpdateKeypair.publicKey, - treasury: getTreasuryPda(DEFAULT_TREASURY_ID), - config: getConfigPda(), + treasury: getTreasuryPda( + DEFAULT_TREASURY_ID, + this.receiver.programId + ), + config: getConfigPda(this.receiver.programId), }) .instruction(), signers: [priceUpdateKeypair], @@ -297,6 +361,9 @@ export class PythSolanaReceiver { }; } + /** + * Build an instruction to close an encoded VAA account, recovering the rent. + */ async buildCloseEncodedVaaInstruction( encodedVaa: PublicKey ): Promise { @@ -307,6 +374,9 @@ export class PythSolanaReceiver { return { instruction, signers: [] }; } + /** + * Build an instruction to close a price update account, recovering the rent. + */ async buildClosePriceUpdateInstruction( priceUpdateAccount: PublicKey ): Promise { 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 index d5f417b82d..5c99376d83 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/address.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/address.ts @@ -1,31 +1,60 @@ import { PublicKey } from "@solana/web3.js"; +/** + * The default Pyth Solana Receiver program ID. + * The program is deployed at this address on all SVM networks. + */ export const DEFAULT_RECEIVER_PROGRAM_ID = new PublicKey( "rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ" ); +/** + * The default Wormhole program ID. + * The program is deployed at this address on all SVM networks. + */ export const DEFAULT_WORMHOLE_PROGRAM_ID = new PublicKey( "HDwcJBJXjL9FpJ7UBsYBtaDjsBUhuLCUYoz3zr8SWWaQ" ); -export const getGuardianSetPda = (guardianSetIndex: number) => { +/** + * Returns the address of a guardian set account from the Wormhole program. + */ +export const getGuardianSetPda = ( + guardianSetIndex: number, + wormholeProgramId: PublicKey +) => { const guardianSetIndexBuf = Buffer.alloc(4); guardianSetIndexBuf.writeUInt32BE(guardianSetIndex, 0); return PublicKey.findProgramAddressSync( [Buffer.from("GuardianSet"), guardianSetIndexBuf], - DEFAULT_WORMHOLE_PROGRAM_ID + wormholeProgramId )[0]; }; -export const getTreasuryPda = (treasuryId: number) => { +/** + * The Pyth Solana Receiver has one treasury account for each u8 `treasuryId`. + * This is meant to avoid write-locks on the treasury account by load-balancing the writes across multiple accounts. + */ +export const DEFAULT_TREASURY_ID = 0; + +/** + * Returns the address of a treasury account from the Pyth Solana Receiver program. + */ +export const getTreasuryPda = ( + treasuryId: number, + receiverProgramId: PublicKey +) => { return PublicKey.findProgramAddressSync( [Buffer.from("treasury"), Buffer.from([treasuryId])], - DEFAULT_RECEIVER_PROGRAM_ID + receiverProgramId )[0]; }; -export const getConfigPda = () => { +/** + * Returns the address of the config account from the Pyth Solana Receiver program. + */ +export const getConfigPda = (receiverProgramId: PublicKey) => { return PublicKey.findProgramAddressSync( [Buffer.from("config")], - DEFAULT_RECEIVER_PROGRAM_ID + receiverProgramId )[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 index 914f4df7bd..93d5b9f339 100644 --- 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 @@ -1,3 +1,12 @@ +/** + * A hard-coded budget for the compute units required for the `verifyEncodedVaa` instruction in the Wormhole program. + */ export const VERIFY_ENCODED_VAA_COMPUTE_BUDGET = 400000; +/** + * A hard-coded budget for the compute units required for the `postUpdateAtomic` instruction in the Pyth Solana Receiver program. + */ export const POST_UPDATE_ATOMIC_COMPUTE_BUDGET = 400000; +/** + * A hard-coded budget for the compute units required for the `postUpdate` instruction in the Pyth Solana Receiver program. + */ export const POST_UPDATE_COMPUTE_BUDGET = 200000; 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 index 5bfb6f85be..74c43722e5 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts @@ -79,7 +79,7 @@ export async function buildEncodedVaaCreateInstruction( /** * When we are writing to an encoded VAA account, we do it in two instructions. * - * We first write the first `VAA_SPLIT_INDEX` bytes and then the rest. + * We first call `writeEncodedVaa` with the first `VAA_SPLIT_INDEX` bytes and then a second time with the rest of the bytes. * * This number was chosen as the biggest number such that we can still call `createInstruction`, `initEncodedVaa` and `writeEncodedVaa` in a single Solana 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 index 52e32fd46a..c877bc22fb 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -10,22 +10,55 @@ import { VersionedTransaction, } from "@solana/web3.js"; +/** + * If the transaction doesn't contain a `setComputeUnitLimit` instruction, the default compute budget is 200,000 units per instruction. + */ export const DEFAULT_COMPUTE_BUDGET_UNITS = 200000; +/** + * The maximum size of a Solana transaction, leaving some room for the compute budget instructions. + */ export const PACKET_DATA_SIZE_WITH_ROOM_FOR_COMPUTE_BUDGET = PACKET_DATA_SIZE - 52; +/** + * An instruction with some extra information that will be used to build transactions. + */ export type InstructionWithEphemeralSigners = { + /** The instruction */ instruction: TransactionInstruction; + /** The ephemeral signers that need to sign the transaction where this instruction will be */ signers: Signer[]; + /** The compute units that this instruction requires, useful if greater than `DEFAULT_COMPUTE_BUDGET_UNITS` */ computeUnits?: number; }; +/** + * The priority fee configuration for transactions + */ export type PriorityFeeConfig = { + /** This is the priority fee in micro lamports, it gets passed down to `setComputeUnitPrice` */ computeUnitPriceMicroLamports?: number; }; /** * Get the size of a transaction that would contain the provided array of instructions + * This is based on {@link https://solana.com/docs/core/transactions}. + * + * Each transaction has the following layout : + * + * - A compact array of all signatures + * - A 3-bytes message header + * - A compact array with all the account addresses + * - A recent blockhash + * - A compact array of instructions + * + * If the transaction is a `VersionedTransaction`, it also contains an extra byte at the beginning, indicating the version and an array of `MessageAddressTableLookup` at the end. + * We don't support Account Lookup Tables, so that array has a size of 0. + * + * Each instruction has the following layout : + * - One byte indicating the index of the program in the account addresses array + * - A compact array of indices into the account addresses array, indicating which accounts are used by the instruction + * - A compact array of serialized instruction data */ export function getSizeOfTransaction( instructions: TransactionInstruction[], @@ -56,15 +89,15 @@ export function getSizeOfTransaction( .reduce((a, b) => a + b, 0); return ( - 1 + - signers.size * 64 + + getSizeOfCompressedU16(signers.size) + + signers.size * 64 + // array of signatures 3 + getSizeOfCompressedU16(accounts.size) + - 32 * accounts.size + - 32 + + 32 * accounts.size + // array of account addresses + 32 + // recent blockhash getSizeOfCompressedU16(instructions.length) + instruction_sizes + - (versionedTransaction ? 1 + getSizeOfCompressedU16(0) : 0) + (versionedTransaction ? 1 + getSizeOfCompressedU16(0) : 0) // We don't support Account Lookup Tables ); } @@ -77,7 +110,7 @@ export function getSizeOfCompressedU16(n: number) { /** * 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. + * As you add instructions, it adds them to the current transaction until it's full, then it starts a new transaction. */ export class TransactionBuilder { readonly transactionInstructions: { @@ -88,6 +121,7 @@ export class TransactionBuilder { readonly payer: PublicKey; readonly connection: Connection; + /** Make a new `TransactionBuilder`. It requires a `payer` to populate the `payerKey` field and a connection to populate */ constructor(payer: PublicKey, connection: Connection) { this.payer = payer; this.connection = connection; @@ -136,7 +170,7 @@ export class TransactionBuilder { } /** - * Returns all the added instructions batched into transactions, plus for each transaction the ephemeral signers that need to sign it + * Returns all the added instructions batched into versioned transactions, plus for each transaction the ephemeral signers that need to sign it */ async getVersionedTransactions( args: PriorityFeeConfig @@ -198,6 +232,9 @@ export class TransactionBuilder { }); } + /** + * Returns a set of transactions that contain the provided instructions in the same order and with efficient batching + */ static batchIntoLegacyTransactions( instructions: TransactionInstruction[] ): Transaction[] { @@ -213,6 +250,9 @@ export class TransactionBuilder { }); } + /** + * Returns a set of versioned transactions that contain the provided instructions in the same order and with efficient batching + */ static async batchIntoVersionedTransactions( payer: PublicKey, connection: Connection, From afcfcf3bc49c56feecc53fdcac3d0fb0d650d6e9 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 29 Feb 2024 17:36:07 +0000 Subject: [PATCH 53/64] Docs --- .../solana/sdk/js/solana_utils/src/transaction.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts index c877bc22fb..66506f483f 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -96,13 +96,13 @@ export function getSizeOfTransaction( 32 * accounts.size + // array of account addresses 32 + // recent blockhash getSizeOfCompressedU16(instructions.length) + - instruction_sizes + - (versionedTransaction ? 1 + getSizeOfCompressedU16(0) : 0) // We don't support Account Lookup Tables + instruction_sizes + // array of instructions + (versionedTransaction ? 1 + getSizeOfCompressedU16(0) : 0) // we don't support Account Lookup Tables ); } /** - * Get the size of n in bytes when serialized as a CompressedU16 + * Get the size of n in bytes when serialized as a CompressedU16. Compact arrays use a CompactU16 to store the length of the array. */ export function getSizeOfCompressedU16(n: number) { return 1 + Number(n >= 128) + Number(n >= 16384); @@ -121,15 +121,14 @@ export class TransactionBuilder { readonly payer: PublicKey; readonly connection: Connection; - /** Make a new `TransactionBuilder`. It requires a `payer` to populate the `payerKey` field and a connection to populate */ + /** Make a new `TransactionBuilder`. It requires a `payer` to populate the `payerKey` field and a connection to populate `recentBlockhash` in the versioned transactions. */ 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 + * Add an `InstructionWithEphemeralSigners` to the builder. */ addInstruction(args: InstructionWithEphemeralSigners) { const { instruction, signers, computeUnits } = args; @@ -163,6 +162,9 @@ export class TransactionBuilder { }); } + /** + * Add multiple `InstructionWithEphemeralSigners` to the builder. + */ addInstructions(instructions: InstructionWithEphemeralSigners[]) { for (const { instruction, signers, computeUnits } of instructions) { this.addInstruction({ instruction, signers, computeUnits }); From 8e7d416a158725398bfe561de5743fa49f3699ec Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 29 Feb 2024 17:40:27 +0000 Subject: [PATCH 54/64] Don't touch this --- target_chains/solana/sdk/js/pyth_solana_receiver/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/package.json b/target_chains/solana/sdk/js/pyth_solana_receiver/package.json index 1cf07c5051..52045535ad 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/package.json +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/package.json @@ -20,6 +20,7 @@ "build": "tsc", "format": "prettier --write \"src/**/*.ts\"", "lint": "eslint src/", + "test": "jest src/ --passWithNoTests", "prepublishOnly": "npm run build && npm test && npm run lint", "preversion": "npm run lint", "version": "npm run format && git add -A src" From c05b409d443548a42e1a902f7b16aba97b112aa5 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 29 Feb 2024 18:48:38 +0000 Subject: [PATCH 55/64] Readme --- .../sdk/js/pyth_solana_receiver/README.md | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 target_chains/solana/sdk/js/pyth_solana_receiver/README.md diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md new file mode 100644 index 0000000000..b40f76ccde --- /dev/null +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md @@ -0,0 +1,61 @@ +# Pyth Solana Receiver JS SDK + +This is a Javascript SDK to interact with the Pyth Solana Receiver contract on Solana whose code lives [here](https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/solana). + +## Pull model + +The Pyth Solana Receiver allows users to consume Pyth price updates on a pull-basis. This means the user is responsible for pushing the price data on-chain whenever they want to interact with an app that requires a price update. + +Price updates get posted into price update accounts, owned by the Receiver contract. Once an update has been posted to a price update account, it can be used by anyone by simply passing the price update account as one of the accounts in a Solana instruction. +Price update accounts can be closed by whoever wrote them to recover the rent. + +## Example use + +```ts +import { Connection, PublicKey } from '@solana/web3.js'; +import { PriceServiceConnection } from '@pythnetwork/price-service-client'; +import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver'; +import { MyFirstPythApp, IDL } from './idl/my_first_pyth_app'; + + +const SOL_PRICE_FEED_ID = "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d" +const ETH_PRICE_FEED_ID = "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace" + +const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network/", { priceFeedRequestConfig: { binary: true } }); +const priceUpdateData = await priceServiceConnection.getLatestVaas([SOL_PRICE_FEED_ID, ETH_PRICE_FEED_ID]); // Fetch off-chain price update data + +const pythSolanaReceiver = new PythSolanaReceiver({ connection, wallet }); +const {postInstructions, cleanupInstructions, priceFeedIdToPriceUpdateAccount} = await pythSolanaReceiver.buildPostPriceUpdateInstructions(priceUpdateData); // Get instructions to post the price update data and to cleanup the accounts later + + +const myFirstPythApp = new Program(IDL as MyFirstPythApp, , PublicKey.unique(), {}) +const consumerInstruction = {instruction: await myFirstApp.methods.consume().accounts({ solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID] }).instruction(), signers: []} +consumed + +const transactions = await TransactionBuilder.batchIntoVersionedTransactions(wallet.publicKey, connection, [...postInstructions, consumerInstruction, ...cleanupInstructions], {}); // Put all the instructions together +await pythSolanaReceiver.provider.sendAll(transactions, { skipPreflight: true }); +``` + +Alternatively you can provide a `getInstructions` method that given the mapping from price feed id to price update account addresses, returns the intruction you want to execute. For example: + +```ts +import { Connection, PublicKey } from '@solana/web3.js'; +import { PriceServiceConnection } from '@pythnetwork/price-service-client'; +import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver'; +import { MyFirstPythApp, IDL } from './idl/my_first_pyth_app'; + + +const SOL_PRICE_FEED_ID = "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d" +const ETH_PRICE_FEED_ID = "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace" + +const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network/", { priceFeedRequestConfig: { binary: true } }); +const priceUpdateData = await priceServiceConnection.getLatestVaas([SOL_PRICE_FEED_ID, ETH_PRICE_FEED_ID]); // Fetch off-chain price update data + + +const myFirstPythApp = new Program(IDL as MyFirstPythApp, , PublicKey.unique(), {}) +const getInstructions = async (priceFeedIdToPriceUpdateAccount : Record) => { return [{instruction: await myFirstApp.methods.consume().accounts({ solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID] }).instruction(), signers: []}] }; + +const pythSolanaReceiver = new PythSolanaReceiver({ connection, wallet }); +const transactions = await pythSolanaReceiver.withPriceUpdate(priceUpdateData, getInstructions, {computeUnitPriceMicroLamports : 1}) +await pythSolanaReceiver.provider.sendAll(transactions, { skipPreflight: true }); +``` From 2e047f87d8b8761892ee12a6a3d47c4ffcd32d17 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 29 Feb 2024 18:51:55 +0000 Subject: [PATCH 56/64] Readme --- target_chains/solana/sdk/js/pyth_solana_receiver/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md index b40f76ccde..2ab87b12a7 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md @@ -4,7 +4,7 @@ This is a Javascript SDK to interact with the Pyth Solana Receiver contract on S ## Pull model -The Pyth Solana Receiver allows users to consume Pyth price updates on a pull-basis. This means the user is responsible for pushing the price data on-chain whenever they want to interact with an app that requires a price update. +The Pyth Solana Receiver allows users to consume Pyth price updates on a pull-basis. This means that the user is responsible for pushing the price data on-chain whenever they want to interact with an app that requires a price update. Price updates get posted into price update accounts, owned by the Receiver contract. Once an update has been posted to a price update account, it can be used by anyone by simply passing the price update account as one of the accounts in a Solana instruction. Price update accounts can be closed by whoever wrote them to recover the rent. @@ -36,7 +36,7 @@ const transactions = await TransactionBuilder.batchIntoVersionedTransactions(wal await pythSolanaReceiver.provider.sendAll(transactions, { skipPreflight: true }); ``` -Alternatively you can provide a `getInstructions` method that given the mapping from price feed id to price update account addresses, returns the intruction you want to execute. For example: +Alternatively you can provide a `getInstructions` method that given the mapping from price feed id to price update account addresses, returns the intructions you want to execute. For example: ```ts import { Connection, PublicKey } from '@solana/web3.js'; From 40adb6880744c1f5fcba177e8380725d1737c6b1 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 29 Feb 2024 18:58:55 +0000 Subject: [PATCH 57/64] Cleanup --- .../sdk/js/pyth_solana_receiver/README.md | 4 ++-- .../src/PythSolanaReceiver.ts | 23 ++++++++++++++----- .../sdk/js/pyth_solana_receiver/src/index.ts | 5 +++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md index 2ab87b12a7..2111640963 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md @@ -33,7 +33,7 @@ const consumerInstruction = {instruction: await myFirstApp.methods.consume().acc consumed const transactions = await TransactionBuilder.batchIntoVersionedTransactions(wallet.publicKey, connection, [...postInstructions, consumerInstruction, ...cleanupInstructions], {}); // Put all the instructions together -await pythSolanaReceiver.provider.sendAll(transactions, { skipPreflight: true }); +await pythSolanaReceiver.provider.sendAll(transactions); ``` Alternatively you can provide a `getInstructions` method that given the mapping from price feed id to price update account addresses, returns the intructions you want to execute. For example: @@ -57,5 +57,5 @@ const getInstructions = async (priceFeedIdToPriceUpdateAccount : Record { + return TransactionBuilder.batchIntoVersionedTransactions( + this.wallet.publicKey, + this.connection, + instructions, + priorityFeeConfig + ); + } } 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 index f4f83959ae..842396a89a 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/index.ts @@ -1,2 +1,5 @@ export { PythSolanaReceiver } from "./PythSolanaReceiver"; -export { TransactionBuilder } from "@pythnetwork/solana-utils"; +export { + TransactionBuilder, + InstructionWithEphemeralSigners, +} from "@pythnetwork/solana-utils"; From 0e68936ec2297a95d7179cbdc632559350a39f2b Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 29 Feb 2024 19:01:44 +0000 Subject: [PATCH 58/64] Readme --- target_chains/solana/sdk/js/pyth_solana_receiver/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md index 2111640963..aa657632aa 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md @@ -29,10 +29,10 @@ const {postInstructions, cleanupInstructions, priceFeedIdToPriceUpdateAccount} = const myFirstPythApp = new Program(IDL as MyFirstPythApp, , PublicKey.unique(), {}) -const consumerInstruction = {instruction: await myFirstApp.methods.consume().accounts({ solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID] }).instruction(), signers: []} +const consumerInstruction : InstructionWithEphemeralSigners = {instruction: await myFirstApp.methods.consume().accounts({ solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID] }).instruction(), signers: []} consumed -const transactions = await TransactionBuilder.batchIntoVersionedTransactions(wallet.publicKey, connection, [...postInstructions, consumerInstruction, ...cleanupInstructions], {}); // Put all the instructions together +const transactions = pythSolanaReceiver.batchIntoVersionedTransactions([...postInstructions, consumerInstruction, ...cleanupInstructions], {}); // Put all the instructions together await pythSolanaReceiver.provider.sendAll(transactions); ``` @@ -56,6 +56,6 @@ const myFirstPythApp = new Program(IDL as MyFirstPythApp, , Publ const getInstructions = async (priceFeedIdToPriceUpdateAccount : Record) => { return [{instruction: await myFirstApp.methods.consume().accounts({ solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID] }).instruction(), signers: []}] }; const pythSolanaReceiver = new PythSolanaReceiver({ connection, wallet }); -const transactions = await pythSolanaReceiver.withPriceUpdate(priceUpdateData, getInstructions, {computeUnitPriceMicroLamports : 1}) +const transactions = await pythSolanaReceiver.withPriceUpdate(priceUpdateData, getInstructions, {}) await pythSolanaReceiver.provider.sendAll(transactions); ``` From 7ae5c935085078c5f82cd259e678ada5b3ca12d2 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Thu, 29 Feb 2024 19:03:43 +0000 Subject: [PATCH 59/64] Fix --- target_chains/solana/sdk/js/pyth_solana_receiver/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md index aa657632aa..3562c6ed3c 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md @@ -36,7 +36,7 @@ const transactions = pythSolanaReceiver.batchIntoVersionedTransactions([...postI await pythSolanaReceiver.provider.sendAll(transactions); ``` -Alternatively you can provide a `getInstructions` method that given the mapping from price feed id to price update account addresses, returns the intructions you want to execute. For example: +Alternatively you can provide a `getInstructions` method that given the mapping from price feed id to price update account addresses, returns the intructions that you want to execute. For example: ```ts import { Connection, PublicKey } from '@solana/web3.js'; From 8bf4dbdff08954e5e77e854de0aea9f860de1556 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 13 Mar 2024 13:50:04 +0000 Subject: [PATCH 60/64] address readme comments --- .../sdk/js/pyth_solana_receiver/README.md | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md index 3562c6ed3c..b803407be3 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md @@ -1,10 +1,10 @@ # Pyth Solana Receiver JS SDK -This is a Javascript SDK to interact with the Pyth Solana Receiver contract on Solana whose code lives [here](https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/solana). +This is a Javascript SDK to interact with the Pyth Solana Receiver contract whose code lives [here](/target_chains/solana). ## Pull model -The Pyth Solana Receiver allows users to consume Pyth price updates on a pull-basis. This means that the user is responsible for pushing the price data on-chain whenever they want to interact with an app that requires a price update. +The Pyth Solana Receiver allows users to consume Pyth price updates on a pull basis. This means that the user is responsible for submitting the price data on-chain whenever they want to interact with an app that requires a price update. Price updates get posted into price update accounts, owned by the Receiver contract. Once an update has been posted to a price update account, it can be used by anyone by simply passing the price update account as one of the accounts in a Solana instruction. Price update accounts can be closed by whoever wrote them to recover the rent. @@ -24,22 +24,19 @@ const ETH_PRICE_FEED_ID = "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d66548 const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network/", { priceFeedRequestConfig: { binary: true } }); const priceUpdateData = await priceServiceConnection.getLatestVaas([SOL_PRICE_FEED_ID, ETH_PRICE_FEED_ID]); // Fetch off-chain price update data -const pythSolanaReceiver = new PythSolanaReceiver({ connection, wallet }); -const {postInstructions, cleanupInstructions, priceFeedIdToPriceUpdateAccount} = await pythSolanaReceiver.buildPostPriceUpdateInstructions(priceUpdateData); // Get instructions to post the price update data and to cleanup the accounts later - const myFirstPythApp = new Program(IDL as MyFirstPythApp, , PublicKey.unique(), {}) -const consumerInstruction : InstructionWithEphemeralSigners = {instruction: await myFirstApp.methods.consume().accounts({ solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID] }).instruction(), signers: []} -consumed +const getInstructions = async (priceFeedIdToPriceUpdateAccount: Record) => { return [{ instruction: await myFirstApp.methods.consume().accounts({ solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID] }).instruction(), signers: [] }] }; -const transactions = pythSolanaReceiver.batchIntoVersionedTransactions([...postInstructions, consumerInstruction, ...cleanupInstructions], {}); // Put all the instructions together +const pythSolanaReceiver = new PythSolanaReceiver({ connection, wallet }); +const transactions = await pythSolanaReceiver.withPriceUpdate(priceUpdateData, getInstructions, {}) await pythSolanaReceiver.provider.sendAll(transactions); ``` -Alternatively you can provide a `getInstructions` method that given the mapping from price feed id to price update account addresses, returns the intructions that you want to execute. For example: +Or, alternatively: ```ts -import { Connection, PublicKey } from '@solana/web3.js'; +import { PublicKey } from '@solana/web3.js'; import { PriceServiceConnection } from '@pythnetwork/price-service-client'; import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver'; import { MyFirstPythApp, IDL } from './idl/my_first_pyth_app'; @@ -51,11 +48,15 @@ const ETH_PRICE_FEED_ID = "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d66548 const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network/", { priceFeedRequestConfig: { binary: true } }); const priceUpdateData = await priceServiceConnection.getLatestVaas([SOL_PRICE_FEED_ID, ETH_PRICE_FEED_ID]); // Fetch off-chain price update data +const pythSolanaReceiver = new PythSolanaReceiver({ connection, wallet }); +const { postInstructions, cleanupInstructions, priceFeedIdToPriceUpdateAccount } = await pythSolanaReceiver.buildPostPriceUpdateInstructions(priceUpdateData); // Get instructions to post the price update data and to cleanup the accounts later + -const myFirstPythApp = new Program(IDL as MyFirstPythApp, , PublicKey.unique(), {}) -const getInstructions = async (priceFeedIdToPriceUpdateAccount : Record) => { return [{instruction: await myFirstApp.methods.consume().accounts({ solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID] }).instruction(), signers: []}] }; +const myFirstPythApp = new Program(IDL as MyFirstPythApp, PublicKey.unique(), {}) +const consumerInstruction: InstructionWithEphemeralSigners = { instruction: await myFirstPythApp.methods.consume().accounts({ solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID] }).instruction(), signers: [] } -const pythSolanaReceiver = new PythSolanaReceiver({ connection, wallet }); -const transactions = await pythSolanaReceiver.withPriceUpdate(priceUpdateData, getInstructions, {}) +const transactions = pythSolanaReceiver.batchIntoVersionedTransactions([...postInstructions, consumerInstruction, ...cleanupInstructions], {}); // Put all the instructions together await pythSolanaReceiver.provider.sendAll(transactions); ``` + + From 64f2daf9cff6fcbddae2203dc287b5d9a5b2f8a4 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 13 Mar 2024 13:55:53 +0000 Subject: [PATCH 61/64] from pyth, not pythnet --- target_chains/solana/README.md | 6 +++--- .../sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/target_chains/solana/README.md b/target_chains/solana/README.md index 720a06630b..8ef2164ca6 100644 --- a/target_chains/solana/README.md +++ b/target_chains/solana/README.md @@ -2,19 +2,19 @@ This folder contains: -- A Pyth receiver program to receive Pythnet price feeds on Solana in `programs/pyth-solana-receiver` +- A Pyth receiver program to receive Pyth price updates on Solana in `programs/pyth-solana-receiver` - A Cli that acts as a simple client to interact with the Pyth receiver program in `cli/` # Overview of the design -Receiving a price update from Pythnet involves two steps: +Posting a Pyth price update involves two steps: - First, verifying the VAA i.e. verifying the Wormhole guardians' signatures on the accumulator root that contains all the price updates for a given Pythnet slot. - Second, verifying the price update by providing an inclusion proof that proves the price update is part of the accumulator root that was verified in the first step. # Implementation -This contract offers two ways to post a price update from Pythnet onto Solana: +This contract offers two ways to post a Pyth price update onto Solana: - `post_update` allows you to do it in 2 transactions and checks all the Wormhole guardian signatures (the quorum is currently 13 signatures). It relies on the Wormhole contract to verify the signatures. - `post_update_atomic` allows you to do it in 1 transaction but only partially checks the Wormhole guardian signatures (5 signatures seems like the best it can currently do). Therefore it is less secure. It relies on a guardian set account from the Wormhole contract to check the signatures against the guardian keys. 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 index d8b2eae31d..9e5e28f7e4 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts @@ -43,7 +43,7 @@ import { * A class to interact with the Pyth Solana Receiver program. * * This class provides helpful methods to: - * - Post price updates from Pythnet to the Pyth Solana Receiver program + * - Post price updates from Pyth to the Pyth Solana Receiver program * - Consume price updates in a consumer program * - Cleanup price update accounts to recover rent */ From d0b632384b7226ed3cadc81797153e4e5170bfa1 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 13 Mar 2024 14:00:03 +0000 Subject: [PATCH 62/64] Add a couple more comments --- .../solana/sdk/js/solana_utils/src/transaction.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts index 1e3bb600a0..8169573cea 100644 --- a/target_chains/solana/sdk/js/solana_utils/src/transaction.ts +++ b/target_chains/solana/sdk/js/solana_utils/src/transaction.ts @@ -42,6 +42,9 @@ export type PriorityFeeConfig = { computeUnitPriceMicroLamports?: number; }; +/** + * A default priority fee configuration. Using a priority fee is helpful even when you're not writing to hot accounts. + */ export const DEFAULT_PRIORITY_FEE_CONFIG: PriorityFeeConfig = { computeUnitPriceMicroLamports: 50000, }; @@ -275,6 +278,9 @@ export class TransactionBuilder { return transactionBuilder.getVersionedTransactions(priorityFeeConfig); } + /** + * Add a priority fee to a legacy transaction + */ static addPriorityFee( transaction: Transaction, priorityFeeConfig: PriorityFeeConfig @@ -289,6 +295,9 @@ export class TransactionBuilder { } } +/** + * Send a set of transactions to the network + */ export async function sendTransactions( transactions: { tx: VersionedTransaction | Transaction; From 3d7abc108373d512609b9236ce7aa3ceff53a6c3 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 13 Mar 2024 14:10:32 +0000 Subject: [PATCH 63/64] Rename cleanup to close --- .../sdk/js/pyth_solana_receiver/README.md | 61 +++++++++++++------ .../src/PythSolanaReceiver.ts | 54 ++++++++-------- 2 files changed, 68 insertions(+), 47 deletions(-) diff --git a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md index b803407be3..d93752f3ec 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/README.md +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/README.md @@ -36,27 +36,48 @@ await pythSolanaReceiver.provider.sendAll(transactions); Or, alternatively: ```ts -import { PublicKey } from '@solana/web3.js'; -import { PriceServiceConnection } from '@pythnetwork/price-service-client'; -import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver'; -import { MyFirstPythApp, IDL } from './idl/my_first_pyth_app'; - - -const SOL_PRICE_FEED_ID = "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d" -const ETH_PRICE_FEED_ID = "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace" - -const priceServiceConnection = new PriceServiceConnection("https://hermes.pyth.network/", { priceFeedRequestConfig: { binary: true } }); -const priceUpdateData = await priceServiceConnection.getLatestVaas([SOL_PRICE_FEED_ID, ETH_PRICE_FEED_ID]); // Fetch off-chain price update data +import { PublicKey } from "@solana/web3.js"; +import { PriceServiceConnection } from "@pythnetwork/price-service-client"; +import { PythSolanaReceiver } from "@pythnetwork/pyth-solana-receiver"; +import { MyFirstPythApp, IDL } from "./idl/my_first_pyth_app"; + +const SOL_PRICE_FEED_ID = + "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d"; +const ETH_PRICE_FEED_ID = + "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"; + +const priceServiceConnection = new PriceServiceConnection( + "https://hermes.pyth.network/", + { priceFeedRequestConfig: { binary: true } } +); +const priceUpdateData = await priceServiceConnection.getLatestVaas([ + SOL_PRICE_FEED_ID, + ETH_PRICE_FEED_ID, +]); // Fetch off-chain price update data const pythSolanaReceiver = new PythSolanaReceiver({ connection, wallet }); -const { postInstructions, cleanupInstructions, priceFeedIdToPriceUpdateAccount } = await pythSolanaReceiver.buildPostPriceUpdateInstructions(priceUpdateData); // Get instructions to post the price update data and to cleanup the accounts later - - -const myFirstPythApp = new Program(IDL as MyFirstPythApp, PublicKey.unique(), {}) -const consumerInstruction: InstructionWithEphemeralSigners = { instruction: await myFirstPythApp.methods.consume().accounts({ solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID] }).instruction(), signers: [] } - -const transactions = pythSolanaReceiver.batchIntoVersionedTransactions([...postInstructions, consumerInstruction, ...cleanupInstructions], {}); // Put all the instructions together +const { postInstructions, closeInstructions, priceFeedIdToPriceUpdateAccount } = + await pythSolanaReceiver.buildPostPriceUpdateInstructions(priceUpdateData); // Get instructions to post the price update data and to close the accounts later + +const myFirstPythApp = new Program( + IDL as MyFirstPythApp, + PublicKey.unique(), + {} +); +const consumerInstruction: InstructionWithEphemeralSigners = { + instruction: await myFirstPythApp.methods + .consume() + .accounts({ + solPriceUpdate: priceFeedIdToPriceUpdateAccount[SOL_PRICE_FEED_ID], + ethPriceUpdate: priceFeedIdToPriceUpdateAccount[ETH_PRICE_FEED_ID], + }) + .instruction(), + signers: [], +}; + +const transactions = pythSolanaReceiver.batchIntoVersionedTransactions( + [...postInstructions, consumerInstruction, ...closeInstructions], + {} +); // Put all the instructions together await pythSolanaReceiver.provider.sendAll(transactions); ``` - - 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 index 9e5e28f7e4..70523d71bd 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/PythSolanaReceiver.ts @@ -43,9 +43,9 @@ import { * A class to interact with the Pyth Solana Receiver program. * * This class provides helpful methods to: - * - Post price updates from Pyth to the Pyth Solana Receiver program + * - Post price updates from Pythnet to the Pyth Solana Receiver program * - Consume price updates in a consumer program - * - Cleanup price update accounts to recover rent + * - Close price update and encoded vaa accounts to recover rent */ export class PythSolanaReceiver { readonly connection: Connection; @@ -83,7 +83,7 @@ export class PythSolanaReceiver { } /** - * Build a series of transactions that post price updates to the Pyth Solana Receiver program, consume them in a consumer program and cleanup the encoded vaa accounts and price update accounts. + * Build a series of transactions that post price updates to the Pyth Solana Receiver program, consume them in a consumer program and close the encoded vaa accounts and price update accounts. * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates. * @param getInstructions a function that given a map of price feed IDs to price update accounts, returns a series of instructions to consume the price updates in a consumer program. This function is a way for the user to indicate which accounts in their instruction need to be "replaced" with price update accounts. * @param priorityFeeConfig a configuration for the compute unit price to use for the transactions. @@ -99,20 +99,20 @@ export class PythSolanaReceiver { const { postInstructions, priceFeedIdToPriceUpdateAccount: priceFeedIdToPriceUpdateAccount, - cleanupInstructions, + closeInstructions, } = await this.buildPostPriceUpdateInstructions(priceUpdateDataArray); return this.batchIntoVersionedTransactions( [ ...postInstructions, ...(await getInstructions(priceFeedIdToPriceUpdateAccount)), - ...cleanupInstructions, + ...closeInstructions, ], priorityFeeConfig ?? {} ); } /** - * Build a series of transactions that post partially verified price updates to the Pyth Solana Receiver program, consume them in a consumer program and cleanup the price update accounts. + * Build a series of transactions that post partially verified price updates to the Pyth Solana Receiver program, consume them in a consumer program and close the price update accounts. * * Partially verified price updates are price updates where not all the guardian signatures have been verified. By default this methods checks `DEFAULT_REDUCED_GUARDIAN_SET_SIZE` signatures when posting the VAA. * If you are a on-chain program developer, make sure you understand the risks of consuming partially verified price updates here: {@link https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/solana/pyth_solana_receiver_state/src/price_update.rs}. @@ -132,20 +132,20 @@ export class PythSolanaReceiver { const { postInstructions, priceFeedIdToPriceUpdateAccount, - cleanupInstructions, + closeInstructions, } = await this.buildPostPriceUpdateAtomicInstructions(priceUpdateDataArray); return this.batchIntoVersionedTransactions( [ ...postInstructions, ...(await getInstructions(priceFeedIdToPriceUpdateAccount)), - ...cleanupInstructions, + ...closeInstructions, ], priorityFeeConfig ?? {} ); } /** - * Build a series of helper instructions that post price updates to the Pyth Solana Receiver program and another series to clean up the price update accounts. + * Build a series of helper instructions that post price updates to the Pyth Solana Receiver program and another series to close the price update accounts. * * This function uses partially verified price updates. Partially verified price updates are price updates where not all the guardian signatures have been verified. By default this methods checks `DEFAULT_REDUCED_GUARDIAN_SET_SIZE` signatures when posting the VAA. * If you are a on-chain program developer, make sure you understand the risks of consuming partially verified price updates here: {@link https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/solana/pyth_solana_receiver_state/src/price_update.rs}. @@ -153,18 +153,18 @@ export class PythSolanaReceiver { * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates. * @returns `postInstructions`: the instructions to post the price updates, these should be called before consuming the price updates * @returns `priceFeedIdToPriceUpdateAccount`: this is a map of price feed IDs to Solana address. Given a price feed ID, you can use this map to find the account where `postInstructions` will post the price update. - * @returns `cleanupInstructions`: the instructions to clean up the price update accounts, these should be called after consuming the price updates + * @returns `closeInstructions`: the instructions to close the price update accounts, these should be called after consuming the price updates */ async buildPostPriceUpdateAtomicInstructions( priceUpdateDataArray: string[] ): Promise<{ postInstructions: InstructionWithEphemeralSigners[]; priceFeedIdToPriceUpdateAccount: Record; - cleanupInstructions: InstructionWithEphemeralSigners[]; + closeInstructions: InstructionWithEphemeralSigners[]; }> { const postInstructions: InstructionWithEphemeralSigners[] = []; const priceFeedIdToPriceUpdateAccount: Record = {}; - const cleanupInstructions: InstructionWithEphemeralSigners[] = []; + const closeInstructions: InstructionWithEphemeralSigners[] = []; for (const priceUpdateData of priceUpdateDataArray) { const accumulatorUpdateData = parseAccumulatorUpdateData( @@ -202,7 +202,7 @@ export class PythSolanaReceiver { "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex") ] = priceUpdateKeypair.publicKey; - cleanupInstructions.push( + closeInstructions.push( await this.buildClosePriceUpdateInstruction( priceUpdateKeypair.publicKey ) @@ -212,7 +212,7 @@ export class PythSolanaReceiver { return { postInstructions, priceFeedIdToPriceUpdateAccount, - cleanupInstructions, + closeInstructions, }; } @@ -222,15 +222,15 @@ export class PythSolanaReceiver { * @param vaa a Wormhole VAA * @returns `postInstructions`: the instructions to post the VAA * @returns `encodedVaaAddress`: the address of the encoded VAA account where the VAA will be posted - * @returns `cleanupInstructions`: the instructions to clean up the encoded VAA account + * @returns `closeInstructions`: the instructions to close the encoded VAA account */ async buildPostEncodedVaaInstructions(vaa: Buffer): Promise<{ postInstructions: InstructionWithEphemeralSigners[]; encodedVaaAddress: PublicKey; - cleanupInstructions: InstructionWithEphemeralSigners[]; + closeInstructions: InstructionWithEphemeralSigners[]; }> { const postInstructions: InstructionWithEphemeralSigners[] = []; - const cleanupInstructions: InstructionWithEphemeralSigners[] = []; + const closeInstructions: InstructionWithEphemeralSigners[] = []; const encodedVaaKeypair = new Keypair(); const guardianSetIndex = getGuardianSetIndex(vaa); @@ -274,35 +274,35 @@ export class PythSolanaReceiver { computeUnits: VERIFY_ENCODED_VAA_COMPUTE_BUDGET, }); - cleanupInstructions.push( + closeInstructions.push( await this.buildCloseEncodedVaaInstruction(encodedVaaKeypair.publicKey) ); return { postInstructions, encodedVaaAddress: encodedVaaKeypair.publicKey, - cleanupInstructions, + closeInstructions, }; } /** - * Build a series of helper instructions that post price updates to the Pyth Solana Receiver program and another series to clean up the encoded vaa accounts and the price update accounts. + * Build a series of helper instructions that post price updates to the Pyth Solana Receiver program and another series to close the encoded vaa accounts and the price update accounts. * * @param priceUpdateDataArray the output of the `@pythnetwork/price-service-client`'s `PriceServiceConnection.getLatestVaas`. This is an array of verifiable price updates. * @returns `postInstructions`: the instructions to post the price updates, these should be called before consuming the price updates * @returns `priceFeedIdToPriceUpdateAccount`: this is a map of price feed IDs to Solana address. Given a price feed ID, you can use this map to find the account where `postInstructions` will post the price update. - * @returns `cleanupInstructions`: the instructions to clean up the price update accounts, these should be called after consuming the price updates + * @returns `closeInstructions`: the instructions to close the price update accounts, these should be called after consuming the price updates */ async buildPostPriceUpdateInstructions( priceUpdateDataArray: string[] ): Promise<{ postInstructions: InstructionWithEphemeralSigners[]; priceFeedIdToPriceUpdateAccount: Record; - cleanupInstructions: InstructionWithEphemeralSigners[]; + closeInstructions: InstructionWithEphemeralSigners[]; }> { const postInstructions: InstructionWithEphemeralSigners[] = []; const priceFeedIdToPriceUpdateAccount: Record = {}; - const cleanupInstructions: InstructionWithEphemeralSigners[] = []; + const closeInstructions: InstructionWithEphemeralSigners[] = []; for (const priceUpdateData of priceUpdateDataArray) { const accumulatorUpdateData = parseAccumulatorUpdateData( @@ -312,10 +312,10 @@ export class PythSolanaReceiver { const { postInstructions: postEncodedVaaInstructions, encodedVaaAddress: encodedVaa, - cleanupInstructions: postEncodedVaaCleanupInstructions, + closeInstructions: postEncodedVaacloseInstructions, } = await this.buildPostEncodedVaaInstructions(accumulatorUpdateData.vaa); postInstructions.push(...postEncodedVaaInstructions); - cleanupInstructions.push(...postEncodedVaaCleanupInstructions); + closeInstructions.push(...postEncodedVaacloseInstructions); for (const update of accumulatorUpdateData.updates) { const priceUpdateKeypair = new Keypair(); @@ -342,7 +342,7 @@ export class PythSolanaReceiver { priceFeedIdToPriceUpdateAccount[ "0x" + parsePriceFeedMessage(update.message).feedId.toString("hex") ] = priceUpdateKeypair.publicKey; - cleanupInstructions.push( + closeInstructions.push( await this.buildClosePriceUpdateInstruction( priceUpdateKeypair.publicKey ) @@ -353,7 +353,7 @@ export class PythSolanaReceiver { return { postInstructions, priceFeedIdToPriceUpdateAccount, - cleanupInstructions, + closeInstructions, }; } From 9165bbbc31fa56b5d4d5ae15765546e9a55b8f97 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 13 Mar 2024 14:13:51 +0000 Subject: [PATCH 64/64] Go go go --- .../solana/sdk/js/pyth_solana_receiver/src/vaa.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 index 74c43722e5..13163fba20 100644 --- a/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts +++ b/target_chains/solana/sdk/js/pyth_solana_receiver/src/vaa.ts @@ -77,11 +77,12 @@ export async function buildEncodedVaaCreateInstruction( } /** - * When we are writing to an encoded VAA account, we do it in two instructions. + * Writing the VAA to to an encoded VAA account is done in 2 instructions. * - * We first call `writeEncodedVaa` with the first `VAA_SPLIT_INDEX` bytes and then a second time with the rest of the bytes. + * The first one writes the first `VAA_SPLIT_INDEX` bytes and the second one writes the rest. * - * This number was chosen as the biggest number such that we can still call `createInstruction`, `initEncodedVaa` and `writeEncodedVaa` in a single Solana transaction. + * This number was chosen as the biggest number such that one can still call `createInstruction`, `initEncodedVaa` and `writeEncodedVaa` in a single Solana transaction. + * This way, the packing of the instructions to post an encoded vaa is more efficient. */ export const VAA_SPLIT_INDEX = 792;