Guillermo Bescos 1 rok temu
rodzic
commit
2ebde4b4f3

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

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

+ 10 - 7
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)
     );
   }
 

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

@@ -25,6 +25,7 @@ import SquadsMesh, { getIxAuthorityPDA, getTxPDA } from "@sqds/mesh";
 import { MultisigAccount } from "@sqds/mesh/lib/types";
 import { mapKey } from "./remote_executor";
 import { WORMHOLE_ADDRESS } from "./wormhole";
+import { TransactionBuilder } from "@pythnetwork/solana-utils";
 
 export const MAX_EXECUTOR_PAYLOAD_SIZE = PACKET_DATA_SIZE - 687; // Bigger payloads won't fit in one addInstruction call when adding to the proposal
 export const MAX_INSTRUCTIONS_PER_PROPOSAL = 256 - 1;
@@ -256,7 +257,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<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

+ 239 - 294
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": {}
         }
       }

+ 1 - 0
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": {

+ 2 - 0
target_chains/solana/sdk/js/solana_utils/.gitignore

@@ -0,0 +1,2 @@
+node_modules
+lib

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

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

+ 48 - 0
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"
+  }
+}

+ 85 - 0
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)
+  );
+});

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

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

+ 143 - 0
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<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);
+}
+
+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;
+    });
+  }
+}

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

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