feat(solana): js sdk (#1307)

* Do it

* Remove some duplicate code

* Cleanup

* Cleanup

* Cleanup import

* Correct description

* Fix path

* Cleanup deps

* Unique

* Works

* Continue

* Lint

* Lint config

* Fix ci

* Checkpoint

* Checkpoint

* Gitignore

* Cleanup

* Cleanup

* Continue building the sdk

* build function

* Remove files

* Remove files

* Rename

* Refactor : make transaction builder

* Make commitment

* Move

* Progress

* Checkpoint

* Ephemeral signers 2

* Checkpoint

* Checkpoint

* Fix bug

* Cleanup idls

* Compute units

* Make program addresses configurable

* Handle arrays

* Handle arrays

* Move PythSolanaReceiver

* Cleanup constants

* Contants

* Refactor constants

* Gitignore refactor

* package lock

* Cleanup idl

* Add useful static

* Add useful static

* Add useful static

* Lint

* Add lint config
This commit is contained in:
guibescos 2024-02-28 20:45:48 +00:00 committed by GitHub
parent 38350421f6
commit e986b69c9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 5682 additions and 396 deletions

View File

@ -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

View File

@ -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",

View File

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

View File

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

View File

@ -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 } = {}

575
package-lock.json generated
View File

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

View File

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

View File

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

View File

@ -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 () => {

View File

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

View File

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

View File

@ -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",
},
};

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -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",
},
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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