From b5cafeccc5f1b07f5fc5feae1acb6b595061774f Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Wed, 14 Jan 2026 15:46:17 -0800 Subject: [PATCH 1/2] feat(wasm-solana): add transaction parsing with BitGoJS compatibility Add WASM bindings for Solana transaction parsing and inspection: - Transaction.fromBase64() / fromBytes() for deserialization - Access to fee payer, recent blockhash, account keys - Instruction decoding with programId, accounts, and data - AccountMeta with isSigner/isWritable flags - Signature access by index (base58 or bytes) - Signable payload extraction for verification Add parseTransaction() for semantic instruction decoding: - Decodes System Program: Transfer, CreateAccount, NonceAdvance - Decodes Stake Program: Delegate, Deactivate, Withdraw, Authorize - Decodes ComputeBudget: SetComputeUnitLimit, SetPriorityFee - Decodes Token Program: Transfer, TransferChecked, CloseAccount - Decodes ATA Program: CreateAssociatedTokenAccount - Decodes Memo Program - Output format matches BitGoJS TxData interface BitGoJS compatibility tests verify identical output for: - Transfer with memo and durable nonce - Multi-transfer transactions (6 recipients) - Staking activate transactions - Token transfer transactions - Simple unsigned transfers Uses official Solana crates exclusively: - solana-transaction for Transaction type - solana-system-interface for SystemInstruction - solana-stake-interface for StakeInstruction - solana-compute-budget-interface for ComputeBudgetInstruction Removed ed25519-dalek dependency (-44KB WASM, -36KB gzipped). Replaces @solana/web3.js Transaction.from() in BitGoJS. Ticket: BTC-2929 --- packages/wasm-solana/Cargo.lock | 843 ++++++++++++++++-- packages/wasm-solana/Cargo.toml | 16 +- packages/wasm-solana/js/index.ts | 30 + packages/wasm-solana/js/parser.ts | 237 +++++ packages/wasm-solana/js/transaction.ts | 132 +++ .../wasm-solana/src/instructions/decode.rs | 331 +++++++ packages/wasm-solana/src/instructions/mod.rs | 9 + .../wasm-solana/src/instructions/types.rs | 236 +++++ packages/wasm-solana/src/keypair.rs | 24 +- packages/wasm-solana/src/lib.rs | 6 +- packages/wasm-solana/src/parser.rs | 199 +++++ packages/wasm-solana/src/transaction.rs | 163 ++++ packages/wasm-solana/src/wasm/mod.rs | 4 + packages/wasm-solana/src/wasm/parser.rs | 38 + packages/wasm-solana/src/wasm/transaction.rs | 154 ++++ packages/wasm-solana/test/bitgojs-compat.ts | 249 ++++++ packages/wasm-solana/test/parser.ts | 107 +++ packages/wasm-solana/test/transaction.ts | 127 +++ 18 files changed, 2790 insertions(+), 115 deletions(-) create mode 100644 packages/wasm-solana/js/parser.ts create mode 100644 packages/wasm-solana/js/transaction.ts create mode 100644 packages/wasm-solana/src/instructions/decode.rs create mode 100644 packages/wasm-solana/src/instructions/mod.rs create mode 100644 packages/wasm-solana/src/instructions/types.rs create mode 100644 packages/wasm-solana/src/parser.rs create mode 100644 packages/wasm-solana/src/transaction.rs create mode 100644 packages/wasm-solana/src/wasm/parser.rs create mode 100644 packages/wasm-solana/src/wasm/transaction.rs create mode 100644 packages/wasm-solana/test/bitgojs-compat.ts create mode 100644 packages/wasm-solana/test/parser.ts create mode 100644 packages/wasm-solana/test/transaction.ts diff --git a/packages/wasm-solana/Cargo.lock b/packages/wasm-solana/Cargo.lock index 27ddb0a..56b9f9f 100644 --- a/packages/wasm-solana/Cargo.lock +++ b/packages/wasm-solana/Cargo.lock @@ -20,10 +20,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "base64ct" -version = "1.8.3" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] [[package]] name = "bitflags" @@ -49,12 +58,71 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -84,10 +152,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "const-oid" -version = "0.9.6" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "cpufeatures" @@ -149,16 +217,6 @@ dependencies = [ "syn", ] -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "zeroize", -] - [[package]] name = "digest" version = "0.9.0" @@ -185,17 +243,7 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature 1.6.4", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature 2.2.0", + "signature", ] [[package]] @@ -205,7 +253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek 3.2.0", - "ed25519 1.5.3", + "ed25519", "rand", "serde", "sha2 0.9.9", @@ -213,18 +261,16 @@ dependencies = [ ] [[package]] -name = "ed25519-dalek" -version = "2.2.0" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek 4.1.3", - "ed25519 2.2.3", - "serde", - "sha2 0.10.9", - "subtle", - "zeroize", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" [[package]] name = "fiat-crypto" @@ -247,6 +293,15 @@ dependencies = [ "five8_core", ] +[[package]] +name = "five8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f76610e969fa1784327ded240f1e28a3fd9520c9cec93b636fcf62dd37f772" +dependencies = [ + "five8_core", +] + [[package]] name = "five8_const" version = "0.1.4" @@ -256,6 +311,15 @@ dependencies = [ "five8_core", ] +[[package]] +name = "five8_const" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0f1728185f277989ca573a402716ae0beaaea3f76a8ff87ef9dd8fb19436c5" +dependencies = [ + "five8_core", +] + [[package]] name = "five8_core" version = "0.1.2" @@ -296,6 +360,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hex" version = "0.4.3" @@ -311,6 +381,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "itoa" version = "1.0.17" @@ -327,6 +407,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.180" @@ -348,6 +434,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memchr" version = "2.7.6" @@ -434,22 +526,21 @@ dependencies = [ ] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "der", - "spki", + "zerocopy", ] [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "proc-macro-crate" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "zerocopy", + "toml_edit", ] [[package]] @@ -507,9 +598,6 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] [[package]] name = "rand_hc" @@ -575,6 +663,26 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -645,19 +753,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] -name = "signature" -version = "2.2.0" +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "solana-account-info" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "fc3397241392f5756925029acaa8515dc70fcbe3d8059d4885d7d6533baf64fd" dependencies = [ - "rand_core 0.6.4", + "solana-address 2.0.0", + "solana-program-error", + "solana-program-memory", ] [[package]] -name = "smallvec" -version = "1.15.1" +name = "solana-address" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "a2ecac8e1b7f74c2baa9e774c42817e3e75b20787134b76cc4d45e8a604488f5" +dependencies = [ + "solana-address 2.0.0", +] + +[[package]] +name = "solana-address" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37320fd2945c5d654b2c6210624a52d66c3f1f73b653ed211ab91a703b35bdd" +dependencies = [ + "five8 1.0.0", + "five8_const 1.0.0", + "serde", + "serde_derive", + "solana-atomic-u64 3.0.0", + "solana-define-syscall 4.0.1", + "solana-program-error", + "solana-sanitize 3.0.1", + "solana-sha256-hasher 3.1.0", +] [[package]] name = "solana-atomic-u64" @@ -668,6 +804,53 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "solana-atomic-u64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a933ff1e50aff72d02173cfcd7511bd8540b027ee720b75f353f594f834216d0" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "solana-clock" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb62e9381182459a4520b5fe7fb22d423cae736239a6427fc398a88743d0ed59" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-compute-budget-interface" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8432d2c4c22d0499aa06d62e4f7e333f81777b3d7c96050ae9e5cb71a8c3aee4" +dependencies = [ + "borsh", + "solana-instruction 2.3.3", + "solana-sdk-ids 2.2.1", +] + +[[package]] +name = "solana-cpi" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dea26709d867aada85d0d3617db0944215c8bb28d3745b912de7db13a23280c" +dependencies = [ + "solana-account-info", + "solana-define-syscall 4.0.1", + "solana-instruction 3.1.0", + "solana-program-error", + "solana-pubkey 4.0.0", + "solana-stable-layout", +] + [[package]] name = "solana-decode-error" version = "2.3.0" @@ -683,19 +866,93 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ae3e2abcf541c8122eafe9a625d4d194b4023c20adde1e251f94e056bb1aee2" +[[package]] +name = "solana-define-syscall" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9697086a4e102d28a156b8d6b521730335d6951bd39a5e766512bbe09007cee" + +[[package]] +name = "solana-define-syscall" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e5b1c0bc1d4a4d10c88a4100499d954c09d3fecfae4912c1a074dff68b1738" + +[[package]] +name = "solana-epoch-rewards" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b319a4ed70390af911090c020571f0ff1f4ec432522d05ab89f5c08080381995" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 3.1.0", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-epoch-schedule" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5481e72cc4d52c169db73e4c0cd16de8bc943078aac587ec4817a75cc6388f" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-fee-calculator" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a73cc03ca4bed871ca174558108835f8323e85917bb38b9c81c7af2ab853efe" +dependencies = [ + "log", + "serde", + "serde_derive", +] + [[package]] name = "solana-hash" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b96e9f0300fa287b545613f007dfe20043d7812bee255f418c1eb649c93b63" dependencies = [ - "five8", + "five8 0.2.1", "js-sys", - "solana-atomic-u64", - "solana-sanitize", + "solana-atomic-u64 2.2.1", + "solana-sanitize 2.2.1", "wasm-bindgen", ] +[[package]] +name = "solana-hash" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "337c246447142f660f778cf6cb582beba8e28deb05b3b24bfb9ffd7c562e5f41" +dependencies = [ + "solana-hash 4.0.1", +] + +[[package]] +name = "solana-hash" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5d48a6ee7b91fc7b998944ab026ed7b3e2fc8ee3bc58452644a86c2648152f" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "five8 1.0.0", + "serde", + "serde_derive", + "solana-atomic-u64 3.0.0", + "solana-sanitize 3.0.1", +] + [[package]] name = "solana-instruction" version = "2.3.3" @@ -705,27 +962,122 @@ dependencies = [ "getrandom 0.2.17", "js-sys", "num-traits", - "solana-define-syscall", - "solana-pubkey", + "solana-define-syscall 2.3.0", + "solana-pubkey 2.4.0", "wasm-bindgen", ] +[[package]] +name = "solana-instruction" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1b699a2c1518028a9982e255e0eca10c44d90006542d9d7f9f40dbce3f7c78" +dependencies = [ + "bincode", + "borsh", + "serde", + "serde_derive", + "solana-define-syscall 4.0.1", + "solana-instruction-error", + "solana-pubkey 4.0.0", +] + +[[package]] +name = "solana-instruction-error" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b04259e03c05faf38a8c24217b5cfe4c90572ae6184ab49cddb1584fdd756d3f" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-program-error", +] + [[package]] name = "solana-keypair" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd3f04aa1a05c535e93e121a95f66e7dcccf57e007282e8255535d24bf1e98bb" dependencies = [ - "ed25519-dalek 1.0.1", - "five8", + "ed25519-dalek", + "five8 0.2.1", "rand", - "solana-pubkey", + "solana-pubkey 2.4.0", "solana-seed-phrase", - "solana-signature", - "solana-signer", + "solana-signature 2.3.0", + "solana-signer 2.2.1", "wasm-bindgen", ] +[[package]] +name = "solana-last-restart-slot" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcda154ec827f5fc1e4da0af3417951b7e9b8157540f81f936c4a8b1156134d0" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-message" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85666605c9fd727f865ed381665db0a8fc29f984a030ecc1e40f43bfb2541623" +dependencies = [ + "bincode", + "lazy_static", + "serde", + "serde_derive", + "solana-address 1.1.0", + "solana-hash 3.1.0", + "solana-instruction 3.1.0", + "solana-sanitize 3.0.1", + "solana-sdk-ids 3.1.0", + "solana-short-vec", + "solana-transaction-error 3.0.0", +] + +[[package]] +name = "solana-msg" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "264275c556ea7e22b9d3f87d56305546a38d4eee8ec884f3b126236cb7dcbbb4" +dependencies = [ + "solana-define-syscall 3.0.0", +] + +[[package]] +name = "solana-program-entrypoint" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c9b0a1ff494e05f503a08b3d51150b73aa639544631e510279d6375f290997" +dependencies = [ + "solana-account-info", + "solana-define-syscall 4.0.1", + "solana-program-error", + "solana-pubkey 4.0.0", +] + +[[package]] +name = "solana-program-error" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1af32c995a7b692a915bb7414d5f8e838450cf7c70414e763d8abcae7b51f28" + +[[package]] +name = "solana-program-memory" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4068648649653c2c50546e9a7fb761791b5ab0cda054c771bb5808d3a4b9eb52" +dependencies = [ + "solana-define-syscall 4.0.1", +] + [[package]] name = "solana-pubkey" version = "2.4.0" @@ -733,25 +1085,92 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b62adb9c3261a052ca1f999398c388f1daf558a1b492f60a6d9e64857db4ff1" dependencies = [ "curve25519-dalek 4.1.3", - "five8", - "five8_const", + "five8 0.2.1", + "five8_const 0.1.4", "getrandom 0.2.17", "js-sys", "num-traits", - "solana-atomic-u64", + "solana-atomic-u64 2.2.1", "solana-decode-error", - "solana-define-syscall", - "solana-sanitize", - "solana-sha256-hasher", + "solana-define-syscall 2.3.0", + "solana-sanitize 2.2.1", + "solana-sha256-hasher 2.3.0", "wasm-bindgen", ] +[[package]] +name = "solana-pubkey" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8909d399deb0851aa524420beeb5646b115fd253ef446e35fe4504c904da3941" +dependencies = [ + "solana-address 1.1.0", +] + +[[package]] +name = "solana-pubkey" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f7104d456b58e1418c21a8581e89810278d1190f70f27ece7fc0b2c9282a57" +dependencies = [ + "solana-address 2.0.0", +] + +[[package]] +name = "solana-rent" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e860d5499a705369778647e97d760f7670adfb6fc8419dd3d568deccd46d5487" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro", + "solana-sysvar-id", +] + [[package]] name = "solana-sanitize" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" +[[package]] +name = "solana-sanitize" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" + +[[package]] +name = "solana-sdk-ids" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5d8b9cc68d5c88b062a33e23a6466722467dde0035152d8fb1afbcdf350a5f" +dependencies = [ + "solana-pubkey 2.4.0", +] + +[[package]] +name = "solana-sdk-ids" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def234c1956ff616d46c9dd953f251fa7096ddbaa6d52b165218de97882b7280" +dependencies = [ + "solana-address 2.0.0", +] + +[[package]] +name = "solana-sdk-macro" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6430000e97083460b71d9fbadc52a2ab2f88f53b3a4c5e58c5ae3640a0e8c00" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "solana-seed-phrase" version = "2.2.1" @@ -770,8 +1189,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa3feb32c28765f6aa1ce8f3feac30936f16c5c3f7eb73d63a5b8f6f8ecdc44" dependencies = [ "sha2 0.10.9", - "solana-define-syscall", - "solana-hash", + "solana-define-syscall 2.3.0", + "solana-hash 2.3.0", +] + +[[package]] +name = "solana-sha256-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7dc3011ea4c0334aaaa7e7128cb390ecf546b28d412e9bf2064680f57f588f" +dependencies = [ + "sha2 0.10.9", + "solana-define-syscall 4.0.1", + "solana-hash 4.0.1", +] + +[[package]] +name = "solana-short-vec" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79fb1809a32cfcf7d9c47b7070a92fa17cdb620ab5829e9a8a9ff9d138a7a175" +dependencies = [ + "serde_core", ] [[package]] @@ -780,9 +1219,22 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64c8ec8e657aecfc187522fc67495142c12f35e55ddeca8698edbb738b8dbd8c" dependencies = [ - "ed25519-dalek 1.0.1", - "five8", - "solana-sanitize", + "ed25519-dalek", + "five8 0.2.1", + "solana-sanitize 2.2.1", +] + +[[package]] +name = "solana-signature" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb8057cc0e9f7b5e89883d49de6f407df655bb6f3a71d0b7baf9986a2218fd9" +dependencies = [ + "five8 0.2.1", + "serde", + "serde-big-array", + "serde_derive", + "solana-sanitize 3.0.1", ] [[package]] @@ -791,9 +1243,153 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" dependencies = [ - "solana-pubkey", - "solana-signature", - "solana-transaction-error", + "solana-pubkey 2.4.0", + "solana-signature 2.3.0", + "solana-transaction-error 2.2.1", +] + +[[package]] +name = "solana-signer" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bfea97951fee8bae0d6038f39a5efcb6230ecdfe33425ac75196d1a1e3e3235" +dependencies = [ + "solana-pubkey 3.0.0", + "solana-signature 3.1.0", + "solana-transaction-error 3.0.0", +] + +[[package]] +name = "solana-slot-hashes" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80a293f952293281443c04f4d96afd9d547721923d596e92b4377ed2360f1746" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 3.1.0", + "solana-sdk-ids 3.1.0", + "solana-sysvar-id", +] + +[[package]] +name = "solana-slot-history" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f914f6b108f5bba14a280b458d023e3621c9973f27f015a4d755b50e88d89e97" +dependencies = [ + "bv", + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sysvar-id", +] + +[[package]] +name = "solana-stable-layout" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1da74507795b6e8fb60b7c7306c0c36e2c315805d16eaaf479452661234685ac" +dependencies = [ + "solana-instruction 3.1.0", + "solana-pubkey 3.0.0", +] + +[[package]] +name = "solana-stake-interface" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9bc26191b533f9a6e5a14cca05174119819ced680a80febff2f5051a713f0db" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-clock", + "solana-cpi", + "solana-instruction 3.1.0", + "solana-program-error", + "solana-pubkey 3.0.0", + "solana-system-interface", + "solana-sysvar", +] + +[[package]] +name = "solana-system-interface" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e1790547bfc3061f1ee68ea9d8dc6c973c02a163697b24263a8e9f2e6d4afa2" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-instruction 3.1.0", + "solana-msg", + "solana-program-error", + "solana-pubkey 3.0.0", +] + +[[package]] +name = "solana-sysvar" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6690d3dd88f15c21edff68eb391ef8800df7a1f5cec84ee3e8d1abf05affdf74" +dependencies = [ + "base64", + "bincode", + "lazy_static", + "serde", + "serde_derive", + "solana-account-info", + "solana-clock", + "solana-define-syscall 4.0.1", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-fee-calculator", + "solana-hash 4.0.1", + "solana-instruction 3.1.0", + "solana-last-restart-slot", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-pubkey 4.0.0", + "solana-rent", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro", + "solana-slot-hashes", + "solana-slot-history", + "solana-sysvar-id", +] + +[[package]] +name = "solana-sysvar-id" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17358d1e9a13e5b9c2264d301102126cf11a47fd394cdf3dec174fe7bc96e1de" +dependencies = [ + "solana-address 2.0.0", + "solana-sdk-ids 3.1.0", +] + +[[package]] +name = "solana-transaction" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ceb2efbf427a91b884709ffac4dac29117752ce1e37e9ae04977e450aa0bb76" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-address 2.0.0", + "solana-hash 4.0.1", + "solana-instruction 3.1.0", + "solana-instruction-error", + "solana-message", + "solana-sanitize 3.0.1", + "solana-sdk-ids 3.1.0", + "solana-short-vec", + "solana-signature 3.1.0", + "solana-signer 3.0.0", + "solana-transaction-error 3.0.0", ] [[package]] @@ -802,18 +1398,20 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" dependencies = [ - "solana-instruction", - "solana-sanitize", + "solana-instruction 2.3.3", + "solana-sanitize 2.2.1", ] [[package]] -name = "spki" -version = "0.7.3" +name = "solana-transaction-error" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "4222065402340d7e6aec9dc3e54d22992ddcf923d91edcd815443c2bfca3144a" dependencies = [ - "base64ct", - "der", + "serde", + "serde_derive", + "solana-instruction-error", + "solana-sanitize 3.0.1", ] [[package]] @@ -833,6 +1431,51 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + [[package]] name = "typenum" version = "1.19.0" @@ -967,14 +1610,21 @@ dependencies = [ name = "wasm-solana" version = "0.1.0" dependencies = [ - "ed25519-dalek 2.2.0", + "base64", + "bincode", + "borsh", "hex", "js-sys", "serde", + "serde-wasm-bindgen", "serde_json", + "solana-compute-budget-interface", "solana-keypair", - "solana-pubkey", - "solana-signer", + "solana-pubkey 2.4.0", + "solana-signer 2.2.1", + "solana-stake-interface", + "solana-system-interface", + "solana-transaction", "wasm-bindgen", "wasm-bindgen-test", ] @@ -1013,6 +1663,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + [[package]] name = "zerocopy" version = "0.8.33" diff --git a/packages/wasm-solana/Cargo.toml b/packages/wasm-solana/Cargo.toml index 3cb7dad..6e01d8c 100644 --- a/packages/wasm-solana/Cargo.toml +++ b/packages/wasm-solana/Cargo.toml @@ -12,17 +12,25 @@ all = "warn" [dependencies] wasm-bindgen = "0.2" js-sys = "0.3" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" # Solana SDK crates solana-pubkey = { version = "2.0", features = ["curve25519"] } solana-keypair = "2.0" solana-signer = "2.0" -# Ed25519 for deriving pubkey from 32-byte seed (solana-keypair expects 64-byte format) -ed25519-dalek = { version = "2.1", default-features = false, features = ["std"] } +solana-transaction = { version = "3.0", features = ["serde", "bincode"] } +# Instruction decoder interfaces (official Solana crates) +solana-system-interface = { version = "2.0", features = ["bincode"] } +solana-stake-interface = { version = "2.0", features = ["bincode"] } +solana-compute-budget-interface = { version = "2.0", features = ["borsh"] } +# Serialization +bincode = "1.3" +borsh = "1.5" +base64 = "0.22" +serde-wasm-bindgen = "0.6" [dev-dependencies] wasm-bindgen-test = "0.3" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" hex = "0.4" [profile.release] diff --git a/packages/wasm-solana/js/index.ts b/packages/wasm-solana/js/index.ts index 217c524..66d8bc1 100644 --- a/packages/wasm-solana/js/index.ts +++ b/packages/wasm-solana/js/index.ts @@ -6,7 +6,37 @@ void wasm; // Namespace exports for explicit imports export * as keypair from "./keypair.js"; export * as pubkey from "./pubkey.js"; +export * as transaction from "./transaction.js"; +export * as parser from "./parser.js"; // Top-level class exports for convenience export { Keypair } from "./keypair.js"; export { Pubkey } from "./pubkey.js"; +export { Transaction } from "./transaction.js"; + +// Top-level function exports +export { parseTransaction } from "./parser.js"; + +// Type exports +export type { AccountMeta, Instruction } from "./transaction.js"; +export type { + ParsedTransaction, + DurableNonce, + InstructionParams, + TransferParams, + CreateAccountParams, + NonceAdvanceParams, + CreateNonceAccountParams, + StakingActivateParams, + StakingDeactivateParams, + StakingWithdrawParams, + StakingDelegateParams, + StakingAuthorizeParams, + SetComputeUnitLimitParams, + SetPriorityFeeParams, + TokenTransferParams, + CreateAtaParams, + CloseAtaParams, + MemoParams, + UnknownInstructionParams, +} from "./parser.js"; diff --git a/packages/wasm-solana/js/parser.ts b/packages/wasm-solana/js/parser.ts new file mode 100644 index 0000000..c92d49f --- /dev/null +++ b/packages/wasm-solana/js/parser.ts @@ -0,0 +1,237 @@ +/** + * High-level transaction parsing. + * + * Provides types and functions for parsing Solana transactions into semantic data + * matching BitGoJS's TxData format. + */ + +import { ParserNamespace } from "./wasm/wasm_solana.js"; + +// ============================================================================= +// Instruction Types - matching BitGoJS InstructionParams +// ============================================================================= + +/** SOL transfer parameters */ +export interface TransferParams { + type: "Transfer"; + fromAddress: string; + toAddress: string; + amount: string; +} + +/** Create account parameters */ +export interface CreateAccountParams { + type: "CreateAccount"; + fromAddress: string; + newAddress: string; + amount: string; + space: number; + owner: string; +} + +/** Nonce advance parameters */ +export interface NonceAdvanceParams { + type: "NonceAdvance"; + walletNonceAddress: string; + authWalletAddress: string; +} + +/** Create nonce account parameters */ +export interface CreateNonceAccountParams { + type: "CreateNonceAccount"; + fromAddress: string; + nonceAddress: string; + authAddress: string; + amount: string; +} + +/** Staking activate parameters */ +export interface StakingActivateParams { + type: "StakingActivate"; + fromAddress: string; + stakingAddress: string; + amount: string; + validator: string; +} + +/** Staking deactivate parameters */ +export interface StakingDeactivateParams { + type: "StakingDeactivate"; + stakingAddress: string; + fromAddress: string; +} + +/** Staking withdraw parameters */ +export interface StakingWithdrawParams { + type: "StakingWithdraw"; + fromAddress: string; + stakingAddress: string; + amount: string; +} + +/** Staking delegate parameters */ +export interface StakingDelegateParams { + type: "StakingDelegate"; + stakingAddress: string; + fromAddress: string; + validator: string; +} + +/** Staking authorize parameters */ +export interface StakingAuthorizeParams { + type: "StakingAuthorize"; + stakingAddress: string; + oldAuthorizeAddress: string; + newAuthorizeAddress: string; + authorizeType: "Staker" | "Withdrawer"; +} + +/** Set compute unit limit parameters */ +export interface SetComputeUnitLimitParams { + type: "SetComputeUnitLimit"; + units: number; +} + +/** Set priority fee parameters */ +export interface SetPriorityFeeParams { + type: "SetPriorityFee"; + fee: number; +} + +/** Token transfer parameters */ +export interface TokenTransferParams { + type: "TokenTransfer"; + fromAddress: string; + toAddress: string; + amount: string; + sourceAddress: string; + tokenAddress?: string; +} + +/** Create associated token account parameters */ +export interface CreateAtaParams { + type: "CreateAssociatedTokenAccount"; + mintAddress: string; + ataAddress: string; + ownerAddress: string; + payerAddress: string; +} + +/** Close associated token account parameters */ +export interface CloseAtaParams { + type: "CloseAssociatedTokenAccount"; + accountAddress: string; + destinationAddress: string; + authorityAddress: string; +} + +/** Memo parameters */ +export interface MemoParams { + type: "Memo"; + memo: string; +} + +/** Account metadata for unknown instructions */ +export interface AccountMeta { + pubkey: string; + isSigner: boolean; + isWritable: boolean; +} + +/** Unknown instruction parameters */ +export interface UnknownInstructionParams { + type: "Unknown"; + programId: string; + accounts: AccountMeta[]; + data: string; // base64 encoded +} + +/** Union of all instruction parameter types */ +export type InstructionParams = + | TransferParams + | CreateAccountParams + | NonceAdvanceParams + | CreateNonceAccountParams + | StakingActivateParams + | StakingDeactivateParams + | StakingWithdrawParams + | StakingDelegateParams + | StakingAuthorizeParams + | SetComputeUnitLimitParams + | SetPriorityFeeParams + | TokenTransferParams + | CreateAtaParams + | CloseAtaParams + | MemoParams + | UnknownInstructionParams; + +// ============================================================================= +// ParsedTransaction - matching BitGoJS TxData +// ============================================================================= + +/** Durable nonce information */ +export interface DurableNonce { + walletNonceAddress: string; + authWalletAddress: string; +} + +/** + * A fully parsed Solana transaction with decoded instructions. + * + * This structure matches BitGoJS's TxData interface for seamless integration. + */ +export interface ParsedTransaction { + /** The fee payer address (base58) */ + feePayer: string; + + /** Number of required signatures */ + numSignatures: number; + + /** The blockhash or nonce value (base58) */ + nonce: string; + + /** If this is a durable nonce transaction, contains the nonce info */ + durableNonce?: DurableNonce; + + /** All decoded instructions with semantic types */ + instructionsData: InstructionParams[]; + + /** All signatures (base64 encoded) */ + signatures: string[]; + + /** All account keys (base58 strings) */ + accountKeys: string[]; +} + +// ============================================================================= +// parseTransaction function +// ============================================================================= + +/** + * Parse a serialized Solana transaction into structured data. + * + * This is the main entry point for transaction parsing. It deserializes the + * transaction bytes and decodes all instructions into semantic types. + * + * @param bytes - The raw transaction bytes (wire format) + * @returns A ParsedTransaction with all instructions decoded + * @throws Error if the transaction cannot be parsed + * + * @example + * ```typescript + * import { parseTransaction } from '@bitgo/wasm-solana'; + * + * const txBytes = Buffer.from(base64EncodedTx, 'base64'); + * const parsed = parseTransaction(txBytes); + * + * console.log(parsed.feePayer); + * for (const instr of parsed.instructionsData) { + * if (instr.type === 'Transfer') { + * console.log(`Transfer ${instr.amount} from ${instr.fromAddress} to ${instr.toAddress}`); + * } + * } + * ``` + */ +export function parseTransaction(bytes: Uint8Array): ParsedTransaction { + return ParserNamespace.parse_transaction(bytes) as ParsedTransaction; +} diff --git a/packages/wasm-solana/js/transaction.ts b/packages/wasm-solana/js/transaction.ts new file mode 100644 index 0000000..a29baf8 --- /dev/null +++ b/packages/wasm-solana/js/transaction.ts @@ -0,0 +1,132 @@ +import { WasmTransaction } from "./wasm/wasm_solana.js"; +import { Pubkey } from "./pubkey.js"; + +/** + * Account metadata for an instruction + */ +export interface AccountMeta { + /** The account public key as a base58 string */ + pubkey: string; + /** Whether this account is a signer */ + isSigner: boolean; + /** Whether this account is writable */ + isWritable: boolean; +} + +/** + * A decoded Solana instruction + */ +export interface Instruction { + /** The program ID (base58 string) that will execute this instruction */ + programId: string; + /** The accounts required by this instruction */ + accounts: AccountMeta[]; + /** The instruction data */ + data: Uint8Array; +} + +/** + * Solana Transaction wrapper for low-level deserialization and inspection. + * + * This class provides low-level access to transaction structure. + * For high-level semantic parsing with decoded instructions, use `parseTransaction()` instead. + * + * @example + * ```typescript + * import { Transaction, parseTransaction } from '@bitgo/wasm-solana'; + * + * // Low-level access: + * const tx = Transaction.fromBytes(txBytes); + * console.log(tx.feePayer); + * + * // High-level parsing (preferred): + * const parsed = parseTransaction(txBytes); + * console.log(parsed.instructionsData); // Decoded instruction types + * ``` + */ +export class Transaction { + private constructor(private _wasm: WasmTransaction) {} + + /** + * Deserialize a transaction from raw bytes + * @param bytes - The raw transaction bytes + * @returns A Transaction instance + */ + static fromBytes(bytes: Uint8Array): Transaction { + const wasm = WasmTransaction.from_bytes(bytes); + return new Transaction(wasm); + } + + /** + * Get the fee payer address as a base58 string + * Returns null if there are no account keys (shouldn't happen for valid transactions) + */ + get feePayer(): string | null { + return this._wasm.fee_payer ?? null; + } + + /** + * Get the recent blockhash as a base58 string + */ + get recentBlockhash(): string { + return this._wasm.recent_blockhash; + } + + /** + * Get the number of signatures in the transaction + */ + get numSignatures(): number { + return this._wasm.num_signatures; + } + + /** + * Get the signable message payload (what gets signed) + * This is the serialized message that signers sign + * @returns The message bytes + */ + signablePayload(): Uint8Array { + return this._wasm.signable_payload(); + } + + /** + * Serialize the transaction to bytes + * @returns The serialized transaction bytes + */ + toBytes(): Uint8Array { + return this._wasm.to_bytes(); + } + + /** + * Get all account keys as Pubkey instances + * @returns Array of account public keys + */ + accountKeys(): Pubkey[] { + const keys = Array.from(this._wasm.account_keys()) as string[]; + return keys.map((k) => Pubkey.fromBase58(k)); + } + + /** + * Get all signatures as byte arrays + * @returns Array of signature byte arrays + */ + signatures(): Uint8Array[] { + return Array.from(this._wasm.signatures()) as Uint8Array[]; + } + + /** + * Get all instructions in the transaction + * @returns Array of instructions with programId, accounts, and data + */ + instructions(): Instruction[] { + const rawInstructions = this._wasm.instructions(); + return Array.from(rawInstructions) as Instruction[]; + } + + /** + * Get the underlying WASM instance (internal use only) + * @internal + */ + get wasm(): WasmTransaction { + return this._wasm; + } +} diff --git a/packages/wasm-solana/src/instructions/decode.rs b/packages/wasm-solana/src/instructions/decode.rs new file mode 100644 index 0000000..81bc21d --- /dev/null +++ b/packages/wasm-solana/src/instructions/decode.rs @@ -0,0 +1,331 @@ +//! Instruction decoding using official Solana interface crates. + +use super::types::*; +use solana_compute_budget_interface::ComputeBudgetInstruction; +use solana_stake_interface::instruction::StakeInstruction; +use solana_system_interface::instruction::SystemInstruction; + +/// Context for decoding an instruction - provides account addresses. +pub struct InstructionContext<'a> { + pub program_id: &'a str, + pub accounts: &'a [String], + pub data: &'a [u8], +} + +/// Decode a single instruction into a ParsedInstruction. +pub fn decode_instruction(ctx: InstructionContext) -> ParsedInstruction { + match ctx.program_id { + SYSTEM_PROGRAM_ID => decode_system_instruction(ctx), + STAKE_PROGRAM_ID => decode_stake_instruction(ctx), + COMPUTE_BUDGET_PROGRAM_ID => decode_compute_budget_instruction(ctx), + MEMO_PROGRAM_ID => decode_memo_instruction(ctx), + TOKEN_PROGRAM_ID | TOKEN_2022_PROGRAM_ID => decode_token_instruction(ctx), + ATA_PROGRAM_ID => decode_ata_instruction(ctx), + _ => make_unknown(ctx), + } +} + +// ============================================================================= +// System Program Decoding +// ============================================================================= + +fn decode_system_instruction(ctx: InstructionContext) -> ParsedInstruction { + let Ok(instr) = bincode::deserialize::(ctx.data) else { + return make_unknown(ctx); + }; + + match instr { + SystemInstruction::Transfer { lamports } => { + if ctx.accounts.len() >= 2 { + ParsedInstruction::Transfer(TransferParams { + from_address: ctx.accounts[0].clone(), + to_address: ctx.accounts[1].clone(), + amount: lamports.to_string(), + }) + } else { + make_unknown(ctx) + } + } + SystemInstruction::CreateAccount { + lamports, + space, + owner, + } => { + if ctx.accounts.len() >= 2 { + ParsedInstruction::CreateAccount(CreateAccountParams { + from_address: ctx.accounts[0].clone(), + new_address: ctx.accounts[1].clone(), + amount: lamports.to_string(), + space, + owner: owner.to_string(), + }) + } else { + make_unknown(ctx) + } + } + SystemInstruction::AdvanceNonceAccount => { + if ctx.accounts.len() >= 3 { + ParsedInstruction::NonceAdvance(NonceAdvanceParams { + wallet_nonce_address: ctx.accounts[0].clone(), + auth_wallet_address: ctx.accounts[2].clone(), // authority is at index 2 + }) + } else { + make_unknown(ctx) + } + } + SystemInstruction::InitializeNonceAccount(authority) => { + // This is part of CreateNonceAccount flow, but we expose it separately + if ctx.accounts.len() >= 1 { + ParsedInstruction::CreateNonceAccount(CreateNonceAccountParams { + from_address: String::new(), // Not available in this instruction alone + nonce_address: ctx.accounts[0].clone(), + auth_address: authority.to_string(), + amount: String::new(), // Not available in this instruction alone + }) + } else { + make_unknown(ctx) + } + } + _ => make_unknown(ctx), + } +} + +// ============================================================================= +// Stake Program Decoding +// ============================================================================= + +fn decode_stake_instruction(ctx: InstructionContext) -> ParsedInstruction { + let Ok(instr) = bincode::deserialize::(ctx.data) else { + return make_unknown(ctx); + }; + + match instr { + StakeInstruction::DelegateStake => { + // Accounts: [0] stake, [1] vote, [2] clock, [3] stake_history, [4] config, [5] authority + if ctx.accounts.len() >= 6 { + ParsedInstruction::StakingDelegate(StakingDelegateParams { + staking_address: ctx.accounts[0].clone(), + from_address: ctx.accounts[5].clone(), // authority + validator: ctx.accounts[1].clone(), // vote account + }) + } else { + make_unknown(ctx) + } + } + StakeInstruction::Deactivate => { + // Accounts: [0] stake, [1] clock, [2] authority + if ctx.accounts.len() >= 3 { + ParsedInstruction::StakingDeactivate(StakingDeactivateParams { + staking_address: ctx.accounts[0].clone(), + from_address: ctx.accounts[2].clone(), + }) + } else { + make_unknown(ctx) + } + } + StakeInstruction::Withdraw(lamports) => { + // Accounts: [0] stake, [1] recipient, [2] clock, [3] stake_history, [4] authority + if ctx.accounts.len() >= 5 { + ParsedInstruction::StakingWithdraw(StakingWithdrawParams { + staking_address: ctx.accounts[0].clone(), + from_address: ctx.accounts[4].clone(), + amount: lamports.to_string(), + }) + } else { + make_unknown(ctx) + } + } + StakeInstruction::Initialize(authorized, _lockup) => { + // This is part of StakingActivate flow + // Accounts: [0] stake, [1] rent_sysvar + if ctx.accounts.len() >= 1 { + ParsedInstruction::StakingActivate(StakingActivateParams { + from_address: authorized.staker.to_string(), + staking_address: ctx.accounts[0].clone(), + amount: String::new(), // Amount comes from CreateAccount + validator: String::new(), // Validator comes from Delegate + }) + } else { + make_unknown(ctx) + } + } + StakeInstruction::Authorize(new_authority, stake_authorize) => { + // Accounts: [0] stake, [1] clock, [2] authority, [3] optional custodian + if ctx.accounts.len() >= 3 { + let auth_type = match stake_authorize { + solana_stake_interface::state::StakeAuthorize::Staker => "Staker", + solana_stake_interface::state::StakeAuthorize::Withdrawer => "Withdrawer", + }; + ParsedInstruction::StakingAuthorize(StakingAuthorizeParams { + staking_address: ctx.accounts[0].clone(), + old_authorize_address: ctx.accounts[2].clone(), + new_authorize_address: new_authority.to_string(), + authorize_type: auth_type.to_string(), + }) + } else { + make_unknown(ctx) + } + } + StakeInstruction::Split(_lamports) => { + // Accounts: [0] source stake, [1] dest stake, [2] authority + if ctx.accounts.len() >= 3 { + ParsedInstruction::StakingDeactivate(StakingDeactivateParams { + staking_address: ctx.accounts[0].clone(), + from_address: ctx.accounts[2].clone(), + }) + } else { + make_unknown(ctx) + } + } + _ => make_unknown(ctx), + } +} + +// ============================================================================= +// ComputeBudget Program Decoding +// ============================================================================= + +fn decode_compute_budget_instruction(ctx: InstructionContext) -> ParsedInstruction { + use borsh::BorshDeserialize; + + let Ok(instr) = ComputeBudgetInstruction::try_from_slice(ctx.data) else { + return make_unknown(ctx); + }; + + match instr { + ComputeBudgetInstruction::SetComputeUnitLimit(units) => { + ParsedInstruction::SetComputeUnitLimit(SetComputeUnitLimitParams { units }) + } + ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports) => { + ParsedInstruction::SetPriorityFee(SetPriorityFeeParams { + fee: micro_lamports, + }) + } + _ => make_unknown(ctx), + } +} + +// ============================================================================= +// Memo Program Decoding +// ============================================================================= + +fn decode_memo_instruction(ctx: InstructionContext) -> ParsedInstruction { + // Memo data is just UTF-8 text + if let Ok(memo) = std::str::from_utf8(ctx.data) { + ParsedInstruction::Memo(MemoParams { + memo: memo.to_string(), + }) + } else { + make_unknown(ctx) + } +} + +// ============================================================================= +// Token Program Decoding (basic) +// ============================================================================= + +fn decode_token_instruction(ctx: InstructionContext) -> ParsedInstruction { + // SPL Token instruction format: first byte is discriminator + if ctx.data.is_empty() { + return make_unknown(ctx); + } + + let discriminator = ctx.data[0]; + + match discriminator { + // TransferChecked = 12 + 12 => { + // Accounts: [0] source, [1] mint, [2] destination, [3] owner/delegate + if ctx.accounts.len() >= 4 { + // Amount is a u64 at bytes 1-8 + let amount = if ctx.data.len() >= 9 { + u64::from_le_bytes(ctx.data[1..9].try_into().unwrap_or([0; 8])) + } else { + 0 + }; + ParsedInstruction::TokenTransfer(TokenTransferParams { + from_address: ctx.accounts[3].clone(), // owner + to_address: ctx.accounts[2].clone(), // destination + amount: amount.to_string(), + source_address: ctx.accounts[0].clone(), + token_address: Some(ctx.accounts[1].clone()), // mint + }) + } else { + make_unknown(ctx) + } + } + // Transfer = 3 + 3 => { + // Accounts: [0] source, [1] destination, [2] owner/delegate + if ctx.accounts.len() >= 3 { + let amount = if ctx.data.len() >= 9 { + u64::from_le_bytes(ctx.data[1..9].try_into().unwrap_or([0; 8])) + } else { + 0 + }; + ParsedInstruction::TokenTransfer(TokenTransferParams { + from_address: ctx.accounts[2].clone(), + to_address: ctx.accounts[1].clone(), + amount: amount.to_string(), + source_address: ctx.accounts[0].clone(), + token_address: None, + }) + } else { + make_unknown(ctx) + } + } + // CloseAccount = 9 + 9 => { + // Accounts: [0] account, [1] destination, [2] owner + if ctx.accounts.len() >= 3 { + ParsedInstruction::CloseAssociatedTokenAccount(CloseAtaParams { + account_address: ctx.accounts[0].clone(), + destination_address: ctx.accounts[1].clone(), + authority_address: ctx.accounts[2].clone(), + }) + } else { + make_unknown(ctx) + } + } + _ => make_unknown(ctx), + } +} + +// ============================================================================= +// ATA Program Decoding +// ============================================================================= + +fn decode_ata_instruction(ctx: InstructionContext) -> ParsedInstruction { + // ATA program: Create instruction has no data (discriminator 0 or empty) + // Accounts: [0] payer, [1] ata, [2] owner, [3] mint, [4] system, [5] token + if ctx.accounts.len() >= 4 { + ParsedInstruction::CreateAssociatedTokenAccount(CreateAtaParams { + payer_address: ctx.accounts[0].clone(), + ata_address: ctx.accounts[1].clone(), + owner_address: ctx.accounts[2].clone(), + mint_address: ctx.accounts[3].clone(), + }) + } else { + make_unknown(ctx) + } +} + +// ============================================================================= +// Fallback +// ============================================================================= + +fn make_unknown(ctx: InstructionContext) -> ParsedInstruction { + ParsedInstruction::Unknown(UnknownInstructionParams { + program_id: ctx.program_id.to_string(), + accounts: ctx + .accounts + .iter() + .map(|a| AccountMeta { + pubkey: a.clone(), + is_signer: false, // We don't have this info at decode time + is_writable: false, // We don't have this info at decode time + }) + .collect(), + data: ctx.data.to_vec(), + }) +} diff --git a/packages/wasm-solana/src/instructions/mod.rs b/packages/wasm-solana/src/instructions/mod.rs new file mode 100644 index 0000000..e6832ab --- /dev/null +++ b/packages/wasm-solana/src/instructions/mod.rs @@ -0,0 +1,9 @@ +//! Internal instruction decoders using official Solana interface crates. +//! +//! This module is NOT publicly exposed. It's used internally by `parseTransaction`. + +mod decode; +mod types; + +pub(crate) use decode::{decode_instruction, InstructionContext}; +pub(crate) use types::*; diff --git a/packages/wasm-solana/src/instructions/types.rs b/packages/wasm-solana/src/instructions/types.rs new file mode 100644 index 0000000..4f0e7f9 --- /dev/null +++ b/packages/wasm-solana/src/instructions/types.rs @@ -0,0 +1,236 @@ +//! Parsed instruction types matching BitGoJS InstructionParams. +//! +//! These types are designed to serialize to JSON that matches the TypeScript +//! interfaces in sdk-coin-sol/src/lib/iface.ts. + +use serde::Serialize; + +/// Program IDs as base58 strings. +pub const SYSTEM_PROGRAM_ID: &str = "11111111111111111111111111111111"; +pub const STAKE_PROGRAM_ID: &str = "Stake11111111111111111111111111111111111111"; +pub const COMPUTE_BUDGET_PROGRAM_ID: &str = "ComputeBudget111111111111111111111111111111"; +pub const MEMO_PROGRAM_ID: &str = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; +pub const TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; +pub const TOKEN_2022_PROGRAM_ID: &str = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"; +pub const ATA_PROGRAM_ID: &str = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"; + +/// A parsed instruction with type discriminant and params. +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "type")] +pub enum ParsedInstruction { + // System Program instructions + Transfer(TransferParams), + CreateAccount(CreateAccountParams), + NonceAdvance(NonceAdvanceParams), + CreateNonceAccount(CreateNonceAccountParams), + + // Stake Program instructions + StakingActivate(StakingActivateParams), + StakingDeactivate(StakingDeactivateParams), + StakingWithdraw(StakingWithdrawParams), + StakingDelegate(StakingDelegateParams), + StakingAuthorize(StakingAuthorizeParams), + + // ComputeBudget instructions + SetComputeUnitLimit(SetComputeUnitLimitParams), + SetPriorityFee(SetPriorityFeeParams), + + // Token instructions (basic support) + TokenTransfer(TokenTransferParams), + CreateAssociatedTokenAccount(CreateAtaParams), + CloseAssociatedTokenAccount(CloseAtaParams), + + // Memo + Memo(MemoParams), + + // Fallback for unknown/custom instructions + Unknown(UnknownInstructionParams), +} + +// ============================================================================= +// System Program Params +// ============================================================================= + +#[derive(Debug, Clone, Serialize)] +pub struct TransferParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "toAddress")] + pub to_address: String, + pub amount: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CreateAccountParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "newAddress")] + pub new_address: String, + pub amount: String, + pub space: u64, + pub owner: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct NonceAdvanceParams { + #[serde(rename = "walletNonceAddress")] + pub wallet_nonce_address: String, + #[serde(rename = "authWalletAddress")] + pub auth_wallet_address: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CreateNonceAccountParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "nonceAddress")] + pub nonce_address: String, + #[serde(rename = "authAddress")] + pub auth_address: String, + pub amount: String, +} + +// ============================================================================= +// Stake Program Params +// ============================================================================= + +#[derive(Debug, Clone, Serialize)] +pub struct StakingActivateParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "stakingAddress")] + pub staking_address: String, + pub amount: String, + pub validator: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct StakingDeactivateParams { + #[serde(rename = "stakingAddress")] + pub staking_address: String, + #[serde(rename = "fromAddress")] + pub from_address: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct StakingWithdrawParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "stakingAddress")] + pub staking_address: String, + pub amount: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct StakingDelegateParams { + #[serde(rename = "stakingAddress")] + pub staking_address: String, + #[serde(rename = "fromAddress")] + pub from_address: String, + pub validator: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct StakingAuthorizeParams { + #[serde(rename = "stakingAddress")] + pub staking_address: String, + #[serde(rename = "oldAuthorizeAddress")] + pub old_authorize_address: String, + #[serde(rename = "newAuthorizeAddress")] + pub new_authorize_address: String, + #[serde(rename = "authorizeType")] + pub authorize_type: String, // "Staker" or "Withdrawer" +} + +// ============================================================================= +// ComputeBudget Params +// ============================================================================= + +#[derive(Debug, Clone, Serialize)] +pub struct SetComputeUnitLimitParams { + pub units: u32, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SetPriorityFeeParams { + pub fee: u64, +} + +// ============================================================================= +// Token Params +// ============================================================================= + +#[derive(Debug, Clone, Serialize)] +pub struct TokenTransferParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "toAddress")] + pub to_address: String, + pub amount: String, + #[serde(rename = "sourceAddress")] + pub source_address: String, + #[serde(rename = "tokenAddress", skip_serializing_if = "Option::is_none")] + pub token_address: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CreateAtaParams { + #[serde(rename = "mintAddress")] + pub mint_address: String, + #[serde(rename = "ataAddress")] + pub ata_address: String, + #[serde(rename = "ownerAddress")] + pub owner_address: String, + #[serde(rename = "payerAddress")] + pub payer_address: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CloseAtaParams { + #[serde(rename = "accountAddress")] + pub account_address: String, + #[serde(rename = "destinationAddress")] + pub destination_address: String, + #[serde(rename = "authorityAddress")] + pub authority_address: String, +} + +// ============================================================================= +// Memo & Unknown +// ============================================================================= + +#[derive(Debug, Clone, Serialize)] +pub struct MemoParams { + pub memo: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct UnknownInstructionParams { + #[serde(rename = "programId")] + pub program_id: String, + pub accounts: Vec, + #[serde(with = "base64_bytes")] + pub data: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct AccountMeta { + pub pubkey: String, + #[serde(rename = "isSigner")] + pub is_signer: bool, + #[serde(rename = "isWritable")] + pub is_writable: bool, +} + +/// Custom serializer for bytes as base64. +mod base64_bytes { + use base64::prelude::*; + use serde::{Serialize, Serializer}; + + pub fn serialize(bytes: &[u8], serializer: S) -> Result + where + S: Serializer, + { + BASE64_STANDARD.encode(bytes).serialize(serializer) + } +} diff --git a/packages/wasm-solana/src/keypair.rs b/packages/wasm-solana/src/keypair.rs index a8c5f80..b4c4034 100644 --- a/packages/wasm-solana/src/keypair.rs +++ b/packages/wasm-solana/src/keypair.rs @@ -20,27 +20,15 @@ pub trait KeypairExt { impl KeypairExt for Keypair { /// Create a keypair from a 32-byte secret key (Ed25519 seed). fn from_secret_key_bytes(secret_key: &[u8]) -> Result { - if secret_key.len() != 32 { - return Err(WasmSolanaError::new(&format!( + let bytes: [u8; 32] = secret_key.try_into().map_err(|_| { + WasmSolanaError::new(&format!( "Secret key must be 32 bytes, got {}", secret_key.len() - ))); - } + )) + })?; - // Generate public key from secret to create full 64-byte format - use ed25519_dalek::SigningKey; - let bytes: [u8; 32] = secret_key - .try_into() - .map_err(|_| WasmSolanaError::new("Failed to convert secret key to array"))?; - let signing_key = SigningKey::from_bytes(&bytes); - let pubkey_bytes = signing_key.verifying_key().to_bytes(); - - let mut full_secret = [0u8; 64]; - full_secret[..32].copy_from_slice(secret_key); - full_secret[32..].copy_from_slice(&pubkey_bytes); - - Keypair::try_from(full_secret.as_slice()) - .map_err(|e| WasmSolanaError::new(&format!("Invalid keypair: {}", e))) + // Use official solana-keypair method that handles 32-byte seeds + Ok(Keypair::new_from_array(bytes)) } /// Create a keypair from a 64-byte Solana secret key (secret + public concatenated). diff --git a/packages/wasm-solana/src/lib.rs b/packages/wasm-solana/src/lib.rs index fa67d30..d88c037 100644 --- a/packages/wasm-solana/src/lib.rs +++ b/packages/wasm-solana/src/lib.rs @@ -24,14 +24,18 @@ //! ``` mod error; +mod instructions; pub mod keypair; +mod parser; pub mod pubkey; +pub mod transaction; pub mod wasm; // Re-export core types at crate root pub use error::WasmSolanaError; pub use keypair::{Keypair, KeypairExt}; pub use pubkey::{Pubkey, PubkeyExt}; +pub use transaction::{Transaction, TransactionExt}; // Re-export WASM types -pub use wasm::{WasmKeypair, WasmPubkey}; +pub use wasm::{ParserNamespace, WasmKeypair, WasmPubkey, WasmTransaction}; diff --git a/packages/wasm-solana/src/parser.rs b/packages/wasm-solana/src/parser.rs new file mode 100644 index 0000000..6d484e4 --- /dev/null +++ b/packages/wasm-solana/src/parser.rs @@ -0,0 +1,199 @@ +//! High-level transaction parser. +//! +//! Provides a single `parse_transaction` function that deserializes transaction bytes +//! and decodes all instructions into semantic types matching BitGoJS's TxData format. + +use crate::instructions::{decode_instruction, InstructionContext, ParsedInstruction}; +use crate::transaction::{Transaction, TransactionExt}; +use serde::Serialize; + +/// A fully parsed Solana transaction with decoded instructions. +/// +/// This structure matches BitGoJS's `TxData` interface for seamless integration. +#[derive(Debug, Clone, Serialize)] +pub struct ParsedTransaction { + /// The fee payer address (base58). + #[serde(rename = "feePayer")] + pub fee_payer: String, + + /// Number of required signatures. + #[serde(rename = "numSignatures")] + pub num_signatures: u8, + + /// The blockhash or nonce (base58). + pub nonce: String, + + /// If this is a durable nonce transaction, contains the nonce info. + #[serde(rename = "durableNonce", skip_serializing_if = "Option::is_none")] + pub durable_nonce: Option, + + /// All decoded instructions. + #[serde(rename = "instructionsData")] + pub instructions_data: Vec, + + /// All signatures (as bytes, base64-encoded for JSON). + #[serde(with = "signatures_serde")] + pub signatures: Vec>, + + /// All account keys (base58 strings). + #[serde(rename = "accountKeys")] + pub account_keys: Vec, +} + +/// Durable nonce information for nonce-based transactions. +#[derive(Debug, Clone, Serialize)] +pub struct DurableNonce { + /// The nonce account address (base58). + #[serde(rename = "walletNonceAddress")] + pub wallet_nonce_address: String, + + /// The nonce authority address (base58). + #[serde(rename = "authWalletAddress")] + pub auth_wallet_address: String, +} + +/// Parse a serialized Solana transaction into structured data. +/// +/// # Arguments +/// * `bytes` - The raw transaction bytes (wire format) +/// +/// # Returns +/// A `ParsedTransaction` with all instructions decoded to semantic types. +pub fn parse_transaction(bytes: &[u8]) -> Result { + // Deserialize the transaction + let tx = Transaction::from_bytes(bytes).map_err(|e| e.to_string())?; + + let message = &tx.message; + + // Extract fee payer (first account key) + let fee_payer = message + .account_keys + .first() + .map(|k| k.to_string()) + .ok_or("Transaction has no account keys")?; + + // Extract all account keys as base58 strings + let account_keys: Vec = message.account_keys.iter().map(|k| k.to_string()).collect(); + + // Extract signatures as byte arrays + let signatures: Vec> = tx.signatures.iter().map(|s| s.as_ref().to_vec()).collect(); + + // Decode all instructions + let mut instructions_data = Vec::with_capacity(message.instructions.len()); + let mut durable_nonce = None; + + for (idx, instruction) in message.instructions.iter().enumerate() { + // Get program ID + let program_id = message + .account_keys + .get(instruction.program_id_index as usize) + .map(|k| k.to_string()) + .ok_or_else(|| format!("Invalid program_id_index in instruction {}", idx))?; + + // Resolve account indices to addresses + let accounts: Vec = instruction + .accounts + .iter() + .filter_map(|&i| message.account_keys.get(i as usize).map(|k| k.to_string())) + .collect(); + + // Decode the instruction + let ctx = InstructionContext { + program_id: &program_id, + accounts: &accounts, + data: &instruction.data, + }; + let parsed = decode_instruction(ctx); + + // Check if this is a NonceAdvance instruction (first instruction = durable nonce tx) + if idx == 0 { + if let ParsedInstruction::NonceAdvance(ref params) = parsed { + durable_nonce = Some(DurableNonce { + wallet_nonce_address: params.wallet_nonce_address.clone(), + auth_wallet_address: params.auth_wallet_address.clone(), + }); + } + } + + instructions_data.push(parsed); + } + + // The nonce is either the blockhash or, for durable nonce txs, still the blockhash + // (which is the nonce value from the nonce account) + let nonce = message.recent_blockhash.to_string(); + + Ok(ParsedTransaction { + fee_payer, + num_signatures: message.header.num_required_signatures, + nonce, + durable_nonce, + instructions_data, + signatures, + account_keys, + }) +} + +/// Serialize signatures as base64 strings for JSON output. +mod signatures_serde { + use base64::prelude::*; + use serde::{Serialize, Serializer}; + + pub fn serialize(signatures: &[Vec], serializer: S) -> Result + where + S: Serializer, + { + let encoded: Vec = signatures + .iter() + .map(|s| BASE64_STANDARD.encode(s)) + .collect(); + encoded.serialize(serializer) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use base64::prelude::*; + + // Test transaction from @solana/web3.js - a simple SOL transfer + const TEST_TX_BASE64: &str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDFVMqpim7tqEi2XL8R6KKkP0DYJvY3eiRXLlL1P9EjYgXKQC+k0FKnqyC4AZGJR7OhJXfpPP3NHOhS8t/6G7bLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1c7Oaj3RbyLIjU0/ZPpsmVfVUWAzc8g36fK5g6A0JoBAgIAAQwCAAAAoIYBAAAAAAA="; + + #[test] + fn test_parse_transfer_transaction() { + let bytes = BASE64_STANDARD.decode(TEST_TX_BASE64).unwrap(); + let parsed = parse_transaction(&bytes).unwrap(); + + // Check basic structure + assert_eq!(parsed.num_signatures, 1); + assert!(!parsed.fee_payer.is_empty()); + assert!(!parsed.nonce.is_empty()); + assert_eq!(parsed.instructions_data.len(), 1); + + // Check the instruction is a Transfer + match &parsed.instructions_data[0] { + ParsedInstruction::Transfer(params) => { + // Amount should be 100000 lamports (from the test tx) + assert_eq!(params.amount, "100000"); + } + other => panic!("Expected Transfer instruction, got {:?}", other), + } + } + + #[test] + fn test_parse_invalid_bytes() { + let result = parse_transaction(&[0, 1, 2, 3]); + assert!(result.is_err()); + } + + #[test] + fn test_parsed_transaction_serializes_to_json() { + let bytes = BASE64_STANDARD.decode(TEST_TX_BASE64).unwrap(); + let parsed = parse_transaction(&bytes).unwrap(); + + // Should serialize to valid JSON + let json = serde_json::to_string(&parsed).unwrap(); + assert!(json.contains("feePayer")); + assert!(json.contains("instructionsData")); + assert!(json.contains("Transfer")); + } +} diff --git a/packages/wasm-solana/src/transaction.rs b/packages/wasm-solana/src/transaction.rs new file mode 100644 index 0000000..339a93f --- /dev/null +++ b/packages/wasm-solana/src/transaction.rs @@ -0,0 +1,163 @@ +//! Solana transaction deserialization. +//! +//! Wraps `solana_transaction::Transaction` for WASM compatibility. +//! +//! # Wire Format +//! +//! Solana transactions use a compact binary format: +//! - Signatures (variable length array) +//! - Message (contains instructions, accounts, blockhash) +//! +//! This module deserializes base64-encoded transactions as used by +//! `@solana/web3.js` `Transaction.from()`. + +use crate::error::WasmSolanaError; + +/// Re-export the underlying Solana Transaction type. +pub use solana_transaction::Transaction; + +/// Extension trait for Transaction to add WASM-friendly methods. +pub trait TransactionExt { + /// Deserialize a transaction from base64-encoded wire format. + fn from_base64(base64_str: &str) -> Result; + + /// Deserialize a transaction from raw bytes (wire format). + fn from_bytes(bytes: &[u8]) -> Result; + + /// Get the fee payer address as base58 string. + fn fee_payer_string(&self) -> Option; + + /// Get the recent blockhash as base58 string. + fn blockhash_string(&self) -> String; + + /// Get the number of instructions. + fn num_instructions(&self) -> usize; + + /// Get the number of signatures. + fn num_signatures(&self) -> usize; + + /// Get the signable message bytes (what gets signed). + fn signable_payload(&self) -> Vec; + + /// Serialize transaction to bytes (wire format). + fn to_bytes(&self) -> Result, WasmSolanaError>; + + /// Serialize transaction to base64. + fn to_base64(&self) -> Result; +} + +impl TransactionExt for Transaction { + fn from_base64(base64_str: &str) -> Result { + // Decode base64 + use base64::prelude::*; + let bytes = BASE64_STANDARD + .decode(base64_str) + .map_err(|e| WasmSolanaError::new(&format!("Invalid base64: {}", e)))?; + + Self::from_bytes(&bytes) + } + + fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + .map_err(|e| WasmSolanaError::new(&format!("Failed to deserialize transaction: {}", e))) + } + + fn fee_payer_string(&self) -> Option { + self.message.account_keys.first().map(|p| p.to_string()) + } + + fn blockhash_string(&self) -> String { + self.message.recent_blockhash.to_string() + } + + fn num_instructions(&self) -> usize { + self.message.instructions.len() + } + + fn num_signatures(&self) -> usize { + self.signatures.len() + } + + fn signable_payload(&self) -> Vec { + self.message.serialize() + } + + fn to_bytes(&self) -> Result, WasmSolanaError> { + bincode::serialize(self) + .map_err(|e| WasmSolanaError::new(&format!("Failed to serialize transaction: {}", e))) + } + + fn to_base64(&self) -> Result { + use base64::prelude::*; + let bytes = self.to_bytes()?; + Ok(BASE64_STANDARD.encode(&bytes)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Test transaction from @solana/web3.js - a simple SOL transfer + // This is a real transaction serialized with Transaction.serialize() + const TEST_TX_BASE64: &str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDFVMqpim7tqEi2XL8R6KKkP0DYJvY3eiRXLlL1P9EjYgXKQC+k0FKnqyC4AZGJR7OhJXfpPP3NHOhS8t/6G7bLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1c7Oaj3RbyLIjU0/ZPpsmVfVUWAzc8g36fK5g6A0JoBAgIAAQwCAAAAoIYBAAAAAAA="; + + #[test] + fn test_deserialize_transaction() { + let tx = Transaction::from_base64(TEST_TX_BASE64).unwrap(); + + // Check we got valid data + assert!(tx.num_signatures() > 0); + assert!(tx.num_instructions() > 0); + } + + #[test] + fn test_fee_payer() { + let tx = Transaction::from_base64(TEST_TX_BASE64).unwrap(); + let fee_payer = tx.fee_payer_string(); + assert!(fee_payer.is_some()); + // Fee payer should be a valid base58 Solana address + let payer = fee_payer.unwrap(); + assert!(payer.len() >= 32 && payer.len() <= 44); + } + + #[test] + fn test_blockhash() { + let tx = Transaction::from_base64(TEST_TX_BASE64).unwrap(); + let blockhash = tx.blockhash_string(); + // Blockhash should be a valid base58 string + assert!(blockhash.len() >= 32 && blockhash.len() <= 44); + } + + #[test] + fn test_roundtrip() { + let tx = Transaction::from_base64(TEST_TX_BASE64).unwrap(); + let serialized = tx.to_base64().unwrap(); + + // Deserialize again + let tx2 = Transaction::from_base64(&serialized).unwrap(); + assert_eq!(tx.num_signatures(), tx2.num_signatures()); + assert_eq!(tx.num_instructions(), tx2.num_instructions()); + assert_eq!(tx.blockhash_string(), tx2.blockhash_string()); + } + + #[test] + fn test_signable_payload() { + let tx = Transaction::from_base64(TEST_TX_BASE64).unwrap(); + let payload = tx.signable_payload(); + // Message should have some content + assert!(!payload.is_empty()); + } + + #[test] + fn test_invalid_base64() { + let result = Transaction::from_base64("not valid base64!!!"); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_transaction() { + let result = Transaction::from_bytes(&[0, 1, 2, 3]); + assert!(result.is_err()); + } +} diff --git a/packages/wasm-solana/src/wasm/mod.rs b/packages/wasm-solana/src/wasm/mod.rs index 45092cd..a149e9c 100644 --- a/packages/wasm-solana/src/wasm/mod.rs +++ b/packages/wasm-solana/src/wasm/mod.rs @@ -1,5 +1,9 @@ mod keypair; +mod parser; mod pubkey; +mod transaction; pub use keypair::WasmKeypair; +pub use parser::ParserNamespace; pub use pubkey::WasmPubkey; +pub use transaction::WasmTransaction; diff --git a/packages/wasm-solana/src/wasm/parser.rs b/packages/wasm-solana/src/wasm/parser.rs new file mode 100644 index 0000000..cb4bdde --- /dev/null +++ b/packages/wasm-solana/src/wasm/parser.rs @@ -0,0 +1,38 @@ +//! WASM binding for high-level transaction parsing. +//! +//! Exposes a single `parseTransaction` function that returns fully decoded +//! transaction data matching BitGoJS's TxData format. + +use crate::parser; +use wasm_bindgen::prelude::*; + +/// Namespace for transaction parsing operations. +#[wasm_bindgen] +pub struct ParserNamespace; + +#[wasm_bindgen] +impl ParserNamespace { + /// Parse a serialized Solana transaction into structured data. + /// + /// Takes raw transaction bytes and returns a JSON object with: + /// - `feePayer`: The fee payer address (base58) + /// - `numSignatures`: Number of required signatures + /// - `nonce`: The blockhash/nonce value (base58) + /// - `durableNonce`: Optional durable nonce info (if tx uses nonce) + /// - `instructionsData`: Array of decoded instructions with semantic types + /// - `signatures`: Array of signatures (base64 encoded) + /// - `accountKeys`: Array of all account addresses (base58) + /// + /// Each instruction in `instructionsData` has a `type` field identifying the + /// instruction type (e.g., "Transfer", "StakingActivate", "TokenTransfer"). + /// + /// @param bytes - The raw transaction bytes (wire format) + /// @returns A ParsedTransaction object as JSON + #[wasm_bindgen] + pub fn parse_transaction(bytes: &[u8]) -> Result { + let parsed = parser::parse_transaction(bytes).map_err(|e| JsValue::from_str(&e))?; + + serde_wasm_bindgen::to_value(&parsed) + .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))) + } +} diff --git a/packages/wasm-solana/src/wasm/transaction.rs b/packages/wasm-solana/src/wasm/transaction.rs new file mode 100644 index 0000000..4d6c295 --- /dev/null +++ b/packages/wasm-solana/src/wasm/transaction.rs @@ -0,0 +1,154 @@ +//! WASM bindings for Solana transaction deserialization. +//! +//! Wraps `solana_transaction::Transaction` for JavaScript. +//! +//! Note: For semantic transaction parsing with decoded instructions, +//! use `ParserNamespace.parse_transaction()` instead. + +use crate::error::WasmSolanaError; +use crate::transaction::{Transaction, TransactionExt}; +use wasm_bindgen::prelude::*; + +/// WASM wrapper for Solana transactions. +/// +/// This type provides low-level access to transaction structure. +/// For high-level semantic parsing, use `ParserNamespace.parse_transaction()`. +#[wasm_bindgen] +pub struct WasmTransaction { + inner: Transaction, +} + +#[wasm_bindgen] +impl WasmTransaction { + /// Deserialize a transaction from raw bytes. + #[wasm_bindgen] + pub fn from_bytes(bytes: &[u8]) -> Result { + Transaction::from_bytes(bytes).map(|inner| WasmTransaction { inner }) + } + + /// Get the fee payer address as a base58 string. + /// + /// Returns `null` if there are no account keys (shouldn't happen for valid transactions). + #[wasm_bindgen(getter)] + pub fn fee_payer(&self) -> Option { + self.inner.fee_payer_string() + } + + /// Get the recent blockhash as a base58 string. + #[wasm_bindgen(getter)] + pub fn recent_blockhash(&self) -> String { + self.inner.blockhash_string() + } + + /// Get the number of instructions in the transaction. + #[wasm_bindgen(getter)] + pub fn num_instructions(&self) -> usize { + self.inner.num_instructions() + } + + /// Get the number of signatures in the transaction. + #[wasm_bindgen(getter)] + pub fn num_signatures(&self) -> usize { + self.inner.num_signatures() + } + + /// Get the signable message payload (what gets signed). + /// + /// This is the serialized message that signers sign. + #[wasm_bindgen] + pub fn signable_payload(&self) -> js_sys::Uint8Array { + let bytes = self.inner.signable_payload(); + js_sys::Uint8Array::from(&bytes[..]) + } + + /// Serialize the transaction to bytes. + #[wasm_bindgen] + pub fn to_bytes(&self) -> Result { + let bytes = self.inner.to_bytes()?; + Ok(js_sys::Uint8Array::from(&bytes[..])) + } + + /// Get all account keys as an array of base58 strings. + #[wasm_bindgen] + pub fn account_keys(&self) -> js_sys::Array { + let arr = js_sys::Array::new(); + for key in &self.inner.message.account_keys { + arr.push(&JsValue::from_str(&key.to_string())); + } + arr + } + + /// Get all signatures as an array of byte arrays. + /// + /// Each signature is returned as a Uint8Array. + #[wasm_bindgen] + pub fn signatures(&self) -> js_sys::Array { + let arr = js_sys::Array::new(); + for sig in &self.inner.signatures { + let bytes: &[u8] = sig.as_ref(); + arr.push(&js_sys::Uint8Array::from(bytes)); + } + arr + } + + /// Get all instructions as an array. + /// + /// Each instruction is a JS object with programId, accounts, and data. + #[wasm_bindgen] + pub fn instructions(&self) -> js_sys::Array { + let arr = js_sys::Array::new(); + let msg = &self.inner.message; + + for instruction in &msg.instructions { + let obj = js_sys::Object::new(); + + // Get the program ID + if let Some(program_id) = msg.account_keys.get(instruction.program_id_index as usize) { + let _ = + js_sys::Reflect::set(&obj, &"programId".into(), &program_id.to_string().into()); + } + + // Build accounts array with signer/writable flags + let accounts = js_sys::Array::new(); + for &account_index in &instruction.accounts { + if let Some(pubkey) = msg.account_keys.get(account_index as usize) { + let account_obj = js_sys::Object::new(); + + let _ = js_sys::Reflect::set( + &account_obj, + &"pubkey".into(), + &pubkey.to_string().into(), + ); + + // Use official Solana methods for signer/writable flags + let is_signer = msg.is_signer(account_index as usize); + let is_writable = msg.is_maybe_writable(account_index as usize, None); + let _ = + js_sys::Reflect::set(&account_obj, &"isSigner".into(), &is_signer.into()); + let _ = js_sys::Reflect::set( + &account_obj, + &"isWritable".into(), + &is_writable.into(), + ); + + accounts.push(&account_obj); + } + } + let _ = js_sys::Reflect::set(&obj, &"accounts".into(), &accounts); + + // Set instruction data + let data = js_sys::Uint8Array::from(&instruction.data[..]); + let _ = js_sys::Reflect::set(&obj, &"data".into(), &data); + + arr.push(&obj); + } + arr + } +} + +impl WasmTransaction { + /// Get the inner Transaction for internal Rust use. + pub fn inner(&self) -> &Transaction { + &self.inner + } +} diff --git a/packages/wasm-solana/test/bitgojs-compat.ts b/packages/wasm-solana/test/bitgojs-compat.ts new file mode 100644 index 0000000..7c72022 --- /dev/null +++ b/packages/wasm-solana/test/bitgojs-compat.ts @@ -0,0 +1,249 @@ +/** + * Compatibility tests using BitGoJS test fixtures. + * + * These tests verify that our parseTransaction output matches + * what BitGoJS's Transaction.toJson() produces. + */ +import * as assert from "assert"; +import { parseTransaction } from "../js/parser.js"; + +// Helper to decode base64 in tests +function base64ToBytes(base64: string): Uint8Array { + const binary = Buffer.from(base64, "base64"); + return new Uint8Array(binary); +} + +describe("BitGoJS Compatibility", () => { + describe("Transfer with memo and durable nonce", () => { + // From BitGoJS: test/resources/sol.ts - TRANSFER_UNSIGNED_TX_WITH_MEMO_AND_DURABLE_NONCE + const TX_BASE64 = + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAMGReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0Fv+hKJ+pxZaLwHGEyk2Svp5PfAC5ZEi/wYI1tPTHHhbqkYG1L37ZDq6w2tS3G+tFODYWdhMXF+kwlYEF+3o4nVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFSlNamSkhBk0k6HFg2jh8fDW13bySu4HkH6hAQQVEjQan1RcZLFaO4IqEX3PSl4jPA1wxRbIas0TYBi6pQAAA4zLa+S+r7Oi2P/ekQAXl/f2a+hWHVrYcWpX5BLO40IEDAwMBBQAEBAAAAAMCAAIMAgAAAOCTBAAAAAAABAAJdGVzdCBtZW1v"; + + // Expected values from BitGoJS test/unit/transaction.ts lines 33-60 + const EXPECTED = { + feePayer: "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe", + nonce: "GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi", + numSignatures: 1, // header.num_required_signatures + instructionsData: [ + { + type: "NonceAdvance", + walletNonceAddress: "8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh", + authWalletAddress: "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe", + }, + { + type: "Transfer", + fromAddress: "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe", + toAddress: "CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S", + amount: "300000", + }, + { + type: "Memo", + memo: "test memo", + }, + ], + }; + + it("should parse feePayer correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + assert.strictEqual(parsed.feePayer, EXPECTED.feePayer); + }); + + it("should parse nonce correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + assert.strictEqual(parsed.nonce, EXPECTED.nonce); + }); + + it("should parse numSignatures correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + assert.strictEqual(parsed.numSignatures, EXPECTED.numSignatures); + }); + + it("should detect durable nonce transaction", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + assert.ok(parsed.durableNonce, "Should detect durable nonce"); + assert.strictEqual( + parsed.durableNonce.walletNonceAddress, + EXPECTED.instructionsData[0].walletNonceAddress, + ); + assert.strictEqual( + parsed.durableNonce.authWalletAddress, + EXPECTED.instructionsData[0].authWalletAddress, + ); + }); + + it("should parse NonceAdvance instruction correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + const instr = parsed.instructionsData[0]; + + assert.strictEqual(instr.type, "NonceAdvance"); + if (instr.type === "NonceAdvance") { + assert.strictEqual( + instr.walletNonceAddress, + EXPECTED.instructionsData[0].walletNonceAddress, + ); + assert.strictEqual(instr.authWalletAddress, EXPECTED.instructionsData[0].authWalletAddress); + } + }); + + it("should parse Transfer instruction correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + const instr = parsed.instructionsData[1]; + + assert.strictEqual(instr.type, "Transfer"); + if (instr.type === "Transfer") { + assert.strictEqual(instr.fromAddress, EXPECTED.instructionsData[1].fromAddress); + assert.strictEqual(instr.toAddress, EXPECTED.instructionsData[1].toAddress); + assert.strictEqual(instr.amount, EXPECTED.instructionsData[1].amount); + } + }); + + it("should parse Memo instruction correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + const instr = parsed.instructionsData[2]; + + assert.strictEqual(instr.type, "Memo"); + if (instr.type === "Memo") { + assert.strictEqual(instr.memo, EXPECTED.instructionsData[2].memo); + } + }); + + it("should have correct number of instructions", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + assert.strictEqual(parsed.instructionsData.length, 3); + }); + }); + + describe("Multi transfer transaction", () => { + // From BitGoJS: test/resources/sol.ts - MULTI_TRANSFER_SIGNED + const TX_BASE64 = + "ARbBf3TOkZIuuO2ziM3aACNNdYKDcumvwrylryRXRabSipz6t4VY0ccLsH7v9v8o/k9TVaToi9eAKBR0C0NRzgYBAAMLReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0FLR9EoOL6wqR8uLpnq0nwpNHchcLqBetRGhm70JDF+8kze2o1mtPDaZbuLoBDbpF4Ym6uNOoiXV4Z/XzIP2qDiVfSSHY6HxxiRep+SggDoFZcJjEpbyDbmNXstOeVFqelv+hKJ+pxZaLwHGEyk2Svp5PfAC5ZEi/wYI1tPTHHhbqOP64stlmOImTCUdTdWfXmX4VEgLlAxGjAYzAqkGvGpqRgbUvftkOrrDa1Lcb60U4NhZ2ExcX6TCVgQX7ejidWvmf90gv+iLyF+MaUVKbB3PxFvBm0rWUtT2LJWOlSvUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUpTWpkpIQZNJOhxYNo4fHw1td28kruB5B+oQEEFRI0Gp9UXGSxWjuCKhF9z0peIzwNcMUWyGrNE2AYuqUAAAOMy2vkvq+zotj/3pEAF5f39mvoVh1a2HFqV+QSzuNCBCAgDBAoABAQAAAAIAgAGDAIAAADgkwQAAAAAAAgCAAIMAgAAAOCTBAAAAAAACAIABQwCAAAA4JMEAAAAAAAIAgAHDAIAAADgkwQAAAAAAAgCAAEMAgAAAOCTBAAAAAAACAIAAwwCAAAA4JMEAAAAAAAJAAl0ZXN0IG1lbW8="; + + // Expected values from BitGoJS test/unit/transaction.ts lines 63-141 + const EXPECTED_FEE_PAYER = "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe"; + const EXPECTED_NONCE = "GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi"; + const EXPECTED_TRANSFER_RECIPIENTS = [ + "CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S", + "6B55XMiaS6tUZw5Tt3G1RaXAqdrvN38yXVDJmWvKLkiM", + "C1UjpxcXNBpp1UyvYsuNBNZ5Da1G1i49g3yTvC23Ny7e", + "CpUYXh9xXoWfkBVaBQRZ8nAgDbT16GZeQdqveeBS1hmk", + "64s6NjmEokdhicHEd432X5Ut2EDfDmVqdvGh4rASn1gd", + "6nXxL2jMSdkgfHm13Twvn1gzRAPdrWnWLfu89PJL3Aqe", + ]; + + it("should parse multi-transfer with correct structure", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + assert.strictEqual(parsed.feePayer, EXPECTED_FEE_PAYER); + assert.strictEqual(parsed.nonce, EXPECTED_NONCE); + // 1 NonceAdvance + 6 Transfers + 1 Memo = 8 instructions + assert.strictEqual(parsed.instructionsData.length, 8); + }); + + it("should parse all transfer recipients correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + // Skip first instruction (NonceAdvance) and last (Memo) + const transfers = parsed.instructionsData.slice(1, 7); + assert.strictEqual(transfers.length, 6); + + for (let i = 0; i < transfers.length; i++) { + const transfer = transfers[i]; + assert.strictEqual(transfer.type, "Transfer", `Instruction ${i + 1} should be Transfer`); + if (transfer.type === "Transfer") { + assert.strictEqual(transfer.toAddress, EXPECTED_TRANSFER_RECIPIENTS[i]); + assert.strictEqual(transfer.amount, "300000"); + assert.strictEqual(transfer.fromAddress, EXPECTED_FEE_PAYER); + } + } + }); + + it("should have memo as last instruction", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + const lastInstr = parsed.instructionsData[parsed.instructionsData.length - 1]; + + assert.strictEqual(lastInstr.type, "Memo"); + if (lastInstr.type === "Memo") { + assert.strictEqual(lastInstr.memo, "test memo"); + } + }); + }); + + describe("Staking activate transaction", () => { + // From BitGoJS: test/resources/sol.ts - STAKING_ACTIVATE_SIGNED_TX + const TX_BASE64 = + "AgqGWxEJnQ6oPZd9ysQx+RoWZiNC5caG1vZfCKihyobmUMA/mj7tUVV3j02GUl25Cm7letLefgUz9WB+kXAe4ABUzgW/NnG7GeZGxTVAsEWxGK93sc/cNVFODjkf97ap2bugoN48UG3jBA0JvcNa35xPVrJVdB8VW8dWe/jfxSgMAgAHCUXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItBYnsvugEnYfm5Gbz5TLtMncgFHZ8JMpkxTTlJIzJovekAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALH5eXiaHEfRPEjYei8nFxDrf5MmCVEXMWKQvWwp2vXZBqHYF5E3VCqYNDe9/ip6slV/U1yKeHIraKSdwAAAAAAGodgXpQIFC2gHkebObbiOHltxUPYfxnkKTrTRAAAAAAan1RcYx3TJKFZjmGkdXraLXrijm0ttXHNVWyEAAAAABqfVFxksXFEhjMlMPUrxf1ja7gibof1E49vZigAAAAAGp9UXGTWE0P7tm7NDHRMga+VEKBtXuFZsxTdf9AAAAOMy2vkvq+zotj/3pEAF5f39mvoVh1a2HFqV+QSzuNCBAwICAAE0AAAAAOCTBAAAAAAAyAAAAAAAAAAGodgXkTdUKpg0N73+KnqyVX9TXIp4citopJ3AAAAAAAQCAQd0AAAAAEXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItBReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0EAAAAAAAAAAAAAAAAAAAAAReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0EEBgEDBggFAAQCAAAA"; + + it("should parse staking transaction structure", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + assert.strictEqual(parsed.feePayer, "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe"); + assert.ok(parsed.instructionsData.length >= 2, "Should have multiple instructions"); + + // Check that we can identify system and stake program instructions + const types = parsed.instructionsData.map((i) => i.type); + assert.ok( + types.includes("CreateAccount") || types.includes("StakingActivate"), + `Should have staking-related instructions, got: ${types.join(", ")}`, + ); + }); + }); + + describe("Token transfer transaction", () => { + // From BitGoJS: test/resources/sol.ts - TOKEN_TRANSFER_SIGNED_TX_WITH_MEMO_AND_DURABLE_NONCE + const TX_BASE64 = + "AV6dvFclQvoTuCoia6uKVEUuUnV6Vzuzoyrbn9r/hvlDupmR6Y+zRtKCyIoAu7Yn4SDswSP5ihpsRl+sla53rQABAAYKAGymKVqOJEQemBHH67uu8ISJV4rtwTejLrjw7VSeW6dv+hKJ+pxZaLwHGEyk2Svp5PfAC5ZEi/wYI1tPTHHhbpXS8VwMObd6fTnfCKrnxvwQ5LFhipVbiG+aiTNM1eFsqRgbUvftkOrrDa1Lcb60U4NhZ2ExcX6TCVgQX7ejidUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAA0QOJ+87lKPIIYR3MxzSzEJJUDLK41Y0QDy6qLO202l4FSlNamSkhBk0k6HFg2jh8fDW13bySu4HkH6hAQQVEjQan1RcZLFaO4IqEX3PSl4jPA1wxRbIas0TYBi6pQAAABt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnjMtr5L6vs6LY/96RABeX9/Zr6FYdWthxalfkEs7jQgQQEAwEIAAQEAAAABQAJA4CWmAAAAAAACQQCBgMACgzgkwQAAAAAAAkHAAl0ZXN0IG1lbW8="; + + it("should parse token transfer transaction", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + // Should have 4 instructions: NonceAdvance, SetComputeUnitPrice, TokenTransfer, and Memo + assert.strictEqual(parsed.instructionsData.length, 4); + + const types = parsed.instructionsData.map((i) => i.type); + assert.strictEqual(types[0], "NonceAdvance", "First should be NonceAdvance"); + assert.strictEqual(types[1], "SetPriorityFee", "Second should be SetPriorityFee"); + assert.strictEqual(types[2], "TokenTransfer", "Third should be TokenTransfer"); + assert.strictEqual(types[3], "Memo", "Fourth should be Memo"); + + // Check token transfer details + const tokenTransfer = parsed.instructionsData[2]; + if (tokenTransfer.type === "TokenTransfer") { + assert.strictEqual(tokenTransfer.amount, "300000"); + } + }); + }); + + describe("Simple unsigned transfer", () => { + // From BitGoJS: test/resources/sol.ts - RAW_TX_UNSIGNED + const TX_BASE64 = + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAIF1NAQCUWYPPTiKY7R/E6KZUKc6Cfr4EUtPm/5/SxQojC7/8v6bBS5ivQMOPXcf/+IbTe8TTN0fjWV33cOwFlm7v5/ZxIQXcf05+tDimmyGgnt1z0tG4opHSR2L2GlM6FGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGp9UXGSxWjuCKhF9z0peIzwNcMUWyGrNE2AYuqUAAAOghhIq8A3U5dDmSv3/3VTX6V+0obalzpFvB2Pemp8/uAgMDAgQABAQAAAADAgABDAIAAACghgEAAAAAAA=="; + + it("should parse basic unsigned transfer", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + // This is a durable nonce transaction with NonceAdvance + Transfer + assert.strictEqual(parsed.instructionsData.length, 2); + assert.strictEqual(parsed.instructionsData[0].type, "NonceAdvance"); + assert.strictEqual(parsed.instructionsData[1].type, "Transfer"); + + if (parsed.instructionsData[1].type === "Transfer") { + // 100000 lamports = 0x186a0 + assert.strictEqual(parsed.instructionsData[1].amount, "100000"); + } + }); + }); +}); diff --git a/packages/wasm-solana/test/parser.ts b/packages/wasm-solana/test/parser.ts new file mode 100644 index 0000000..42b5234 --- /dev/null +++ b/packages/wasm-solana/test/parser.ts @@ -0,0 +1,107 @@ +import * as assert from "assert"; +import { parseTransaction, type ParsedTransaction } from "../js/parser.js"; + +// Helper to decode base64 in tests +function base64ToBytes(base64: string): Uint8Array { + const binary = Buffer.from(base64, "base64"); + return new Uint8Array(binary); +} + +describe("parseTransaction", () => { + // Test transaction from @solana/web3.js - a simple SOL transfer (100000 lamports) + const TEST_TX_BASE64 = + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDFVMqpim7tqEi2XL8R6KKkP0DYJvY3eiRXLlL1P9EjYgXKQC+k0FKnqyC4AZGJR7OhJXfpPP3NHOhS8t/6G7bLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1c7Oaj3RbyLIjU0/ZPpsmVfVUWAzc8g36fK5g6A0JoBAgIAAQwCAAAAoIYBAAAAAAA="; + + const TEST_TX_BYTES = base64ToBytes(TEST_TX_BASE64); + + it("should parse a SOL transfer transaction", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + + // Check basic structure + assert.ok(parsed.feePayer); + assert.ok(parsed.nonce); + assert.strictEqual(parsed.numSignatures, 1); + assert.ok(parsed.instructionsData.length > 0); + assert.ok(parsed.signatures.length > 0); + assert.ok(parsed.accountKeys.length > 0); + }); + + it("should decode SOL transfer instruction correctly", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + + assert.strictEqual(parsed.instructionsData.length, 1); + const instr = parsed.instructionsData[0]; + + // Should be a Transfer instruction + assert.strictEqual(instr.type, "Transfer"); + + // Type guard to access Transfer-specific fields + if (instr.type === "Transfer") { + assert.ok(instr.fromAddress); + assert.ok(instr.toAddress); + // Amount should be 100000 lamports (from test tx) + assert.strictEqual(instr.amount, "100000"); + } + }); + + it("should include fee payer as first account key", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + + assert.strictEqual(parsed.feePayer, parsed.accountKeys[0]); + }); + + it("should have signatures as base64 strings", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + + assert.ok(parsed.signatures.length > 0); + // Signatures should be base64 encoded (string) + for (const sig of parsed.signatures) { + assert.strictEqual(typeof sig, "string"); + // Base64 of 64 bytes is 88 characters + assert.ok(sig.length > 0); + } + }); + + it("should reject invalid bytes", () => { + const invalidBytes = new Uint8Array([0, 1, 2, 3]); + assert.throws(() => parseTransaction(invalidBytes)); + }); + + it("should set durableNonce for nonce transactions", () => { + // This is a regular (non-nonce) transaction, so durableNonce should be undefined + const parsed = parseTransaction(TEST_TX_BYTES); + assert.strictEqual(parsed.durableNonce, undefined); + }); + + it("should serialize to valid JSON", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + const json = JSON.stringify(parsed); + + // Should be valid JSON + const reparsed = JSON.parse(json) as ParsedTransaction; + assert.strictEqual(reparsed.feePayer, parsed.feePayer); + assert.strictEqual(reparsed.instructionsData.length, parsed.instructionsData.length); + }); + + describe("instruction type discrimination", () => { + it("should have type field on all instructions", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + + for (const instr of parsed.instructionsData) { + assert.ok("type" in instr, "Instruction should have type field"); + assert.strictEqual(typeof instr.type, "string"); + } + }); + + it("Transfer instruction should have correct fields", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + const transfer = parsed.instructionsData[0]; + + if (transfer.type === "Transfer") { + assert.ok("fromAddress" in transfer); + assert.ok("toAddress" in transfer); + assert.ok("amount" in transfer); + } + }); + }); +}); diff --git a/packages/wasm-solana/test/transaction.ts b/packages/wasm-solana/test/transaction.ts new file mode 100644 index 0000000..528d3a2 --- /dev/null +++ b/packages/wasm-solana/test/transaction.ts @@ -0,0 +1,127 @@ +import * as assert from "assert"; +import { Transaction } from "../js/transaction.js"; + +// Helper to decode base64 in tests +function base64ToBytes(base64: string): Uint8Array { + const binary = Buffer.from(base64, "base64"); + return new Uint8Array(binary); +} + +describe("Transaction", () => { + // Test transaction from @solana/web3.js - a simple SOL transfer + // This is a real transaction serialized with Transaction.serialize() + const TEST_TX_BASE64 = + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDFVMqpim7tqEi2XL8R6KKkP0DYJvY3eiRXLlL1P9EjYgXKQC+k0FKnqyC4AZGJR7OhJXfpPP3NHOhS8t/6G7bLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1c7Oaj3RbyLIjU0/ZPpsmVfVUWAzc8g36fK5g6A0JoBAgIAAQwCAAAAoIYBAAAAAAA="; + + const TEST_TX_BYTES = base64ToBytes(TEST_TX_BASE64); + + it("should deserialize transaction from bytes", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + + assert.ok(tx.numSignatures > 0); + assert.ok(tx.instructions().length > 0); + }); + + it("should get fee payer", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const feePayer = tx.feePayer; + + assert.ok(feePayer); + // Fee payer should be a valid base58 Solana address + assert.ok(feePayer.length >= 32 && feePayer.length <= 44); + }); + + it("should get recent blockhash", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const blockhash = tx.recentBlockhash; + + // Blockhash should be a valid base58 string + assert.ok(blockhash.length >= 32 && blockhash.length <= 44); + }); + + it("should get account keys", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const keys = tx.accountKeys(); + + assert.ok(Array.isArray(keys)); + assert.ok(keys.length >= 1); + // First key should be the fee payer + assert.strictEqual(keys[0].toBase58(), tx.feePayer); + }); + + it("should roundtrip bytes", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const serialized = tx.toBytes(); + + const tx2 = Transaction.fromBytes(serialized); + assert.strictEqual(tx.numSignatures, tx2.numSignatures); + assert.strictEqual(tx.instructions().length, tx2.instructions().length); + assert.strictEqual(tx.recentBlockhash, tx2.recentBlockhash); + }); + + it("should get signable payload", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const payload = tx.signablePayload(); + + assert.ok(payload instanceof Uint8Array); + assert.ok(payload.length > 0); + }); + + it("should get signatures as bytes", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const sigs = tx.signatures(); + + assert.ok(Array.isArray(sigs)); + assert.strictEqual(sigs.length, tx.numSignatures); + + // Each signature should be 64 bytes + for (const sig of sigs) { + assert.ok(sig instanceof Uint8Array); + assert.strictEqual(sig.length, 64); + } + }); + + it("should reject invalid transaction bytes", () => { + const invalidBytes = new Uint8Array([0, 1, 2, 3]); + assert.throws(() => Transaction.fromBytes(invalidBytes), /deserialize/); + }); + + it("should get instructions", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const instructions = tx.instructions(); + + assert.ok(Array.isArray(instructions)); + assert.ok(instructions.length > 0); + + // Check first instruction structure + const instr = instructions[0]; + assert.ok(typeof instr.programId === "string"); + assert.ok(Array.isArray(instr.accounts)); + assert.ok(instr.data instanceof Uint8Array); + }); + + it("should get instruction accounts with signer/writable flags", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const instructions = tx.instructions(); + + assert.ok(instructions.length > 0); + const instr = instructions[0]; + assert.ok(instr.accounts.length > 0); + + // Check account structure + const account = instr.accounts[0]; + assert.ok(typeof account.pubkey === "string"); + assert.ok(typeof account.isSigner === "boolean"); + assert.ok(typeof account.isWritable === "boolean"); + }); + + it("should have System Program as program ID for SOL transfer", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const instructions = tx.instructions(); + + assert.ok(instructions.length > 0); + const instr = instructions[0]; + // System program ID is 11111111111111111111111111111111 + assert.strictEqual(instr.programId, "11111111111111111111111111111111"); + }); +}); From 3e828a20db692641021c1544f2d59469a22fab14 Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Tue, 20 Jan 2026 09:26:11 -0800 Subject: [PATCH 2/2] feat: add SPL Stake Pool (Jito) decoder support Add support for decoding SPL Stake Pool instructions used by Jito liquid staking: - DepositSol (discriminator 14): deposit SOL into stake pool, receive pool tokens - WithdrawStake (discriminator 10): withdraw stake by burning pool tokens Includes TypeScript types and tests using real Jito transactions from sdk-coin-sol. Ticket: BTC-2932 --- packages/wasm-solana/Cargo.lock | 1852 ++++++++++++++++- packages/wasm-solana/Cargo.toml | 2 + packages/wasm-solana/js/parser.ts | 32 + .../wasm-solana/src/instructions/decode.rs | 90 + .../wasm-solana/src/instructions/types.rs | 84 +- packages/wasm-solana/test/parser.ts | 146 +- 6 files changed, 2113 insertions(+), 93 deletions(-) diff --git a/packages/wasm-solana/Cargo.lock b/packages/wasm-solana/Cargo.lock index 56b9f9f..6805a7b 100644 --- a/packages/wasm-solana/Cargo.lock +++ b/packages/wasm-solana/Cargo.lock @@ -2,6 +2,72 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm-siv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-trait" version = "0.1.89" @@ -10,7 +76,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -19,6 +85,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "base64" version = "0.22.1" @@ -40,6 +112,21 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -58,16 +145,39 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115e54d64eb62cdebad391c19efc9dce4981c690c85a33a12199d99bb9546fee" +dependencies = [ + "borsh-derive 0.10.4", + "hashbrown 0.13.2", +] + [[package]] name = "borsh" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" dependencies = [ - "borsh-derive", + "borsh-derive 1.6.0", "cfg_aliases", ] +[[package]] +name = "borsh-derive" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831213f80d9423998dd696e2c5345aba6be7a0bd8cd19e31c5243e13df1cef89" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + [[package]] name = "borsh-derive" version = "1.6.0" @@ -75,10 +185,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" dependencies = [ "once_cell", - "proc-macro-crate", + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65d6ba50644c98714aa2a70d13d7df3cd75cd2b523a2b452bf010443800976b3" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276691d96f063427be83e6692b86148e488ebba9f48f77788724ca027ba3b6d4" +dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -111,6 +243,9 @@ name = "bytemuck" version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] [[package]] name = "bytemuck_derive" @@ -120,7 +255,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -157,6 +292,42 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -166,6 +337,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -173,9 +350,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -202,6 +389,7 @@ dependencies = [ "fiat-crypto", "rand_core 0.6.4", "rustc_version", + "serde", "subtle", "zeroize", ] @@ -214,9 +402,15 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + [[package]] name = "digest" version = "0.9.0" @@ -254,12 +448,18 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek 3.2.0", "ed25519", - "rand", + "rand 0.7.3", "serde", "sha2 0.9.9", "zeroize", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "equivalent" version = "1.0.2" @@ -326,6 +526,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "generic-array" version = "0.14.7" @@ -360,6 +572,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -388,7 +620,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", ] [[package]] @@ -407,6 +657,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -425,6 +684,52 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.9", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -446,6 +751,27 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "minicov" version = "0.3.8" @@ -465,6 +791,36 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -475,6 +831,28 @@ dependencies = [ "libm", ] +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -525,6 +903,24 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -536,9 +932,18 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] @@ -552,6 +957,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + [[package]] name = "quote" version = "1.0.43" @@ -569,11 +983,22 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha", + "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -584,6 +1009,16 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -598,6 +1033,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] [[package]] name = "rand_hc" @@ -683,6 +1121,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -700,7 +1148,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -740,6 +1188,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + [[package]] name = "shlex" version = "1.3.0" @@ -758,6 +1216,32 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "solana-account" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f949fe4edaeaea78c844023bfc1c898e0b1f5a100f8a8d2d0f85d0a7b090258" +dependencies = [ + "solana-account-info 2.3.0", + "solana-clock 2.2.2", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", +] + +[[package]] +name = "solana-account-info" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8f5152a288ef1912300fc6efa6c2d1f9bb55d9398eb6c72326360b8063987da" +dependencies = [ + "bincode", + "serde", + "solana-program-error 2.2.2", + "solana-program-memory 2.3.1", + "solana-pubkey 2.4.0", +] + [[package]] name = "solana-account-info" version = "3.1.0" @@ -765,8 +1249,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc3397241392f5756925029acaa8515dc70fcbe3d8059d4885d7d6533baf64fd" dependencies = [ "solana-address 2.0.0", - "solana-program-error", - "solana-program-memory", + "solana-program-error 3.0.0", + "solana-program-memory 3.1.0", ] [[package]] @@ -790,11 +1274,28 @@ dependencies = [ "serde_derive", "solana-atomic-u64 3.0.0", "solana-define-syscall 4.0.1", - "solana-program-error", + "solana-program-error 3.0.0", "solana-sanitize 3.0.1", "solana-sha256-hasher 3.1.0", ] +[[package]] +name = "solana-address-lookup-table-interface" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1673f67efe870b64a65cb39e6194be5b26527691ce5922909939961a6e6b395" +dependencies = [ + "bincode", + "bytemuck", + "serde", + "serde_derive", + "solana-clock 2.2.2", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-slot-hashes 2.2.1", +] + [[package]] name = "solana-atomic-u64" version = "2.2.1" @@ -813,6 +1314,63 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "solana-big-mod-exp" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75db7f2bbac3e62cfd139065d15bcda9e2428883ba61fc8d27ccb251081e7567" +dependencies = [ + "num-bigint", + "num-traits", + "solana-define-syscall 2.3.0", +] + +[[package]] +name = "solana-bincode" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a3787b8cf9c9fe3dd360800e8b70982b9e5a8af9e11c354b6665dd4a003adc" +dependencies = [ + "bincode", + "serde", + "solana-instruction 2.3.3", +] + +[[package]] +name = "solana-blake3-hasher" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0801e25a1b31a14494fc80882a036be0ffd290efc4c2d640bfcca120a4672" +dependencies = [ + "blake3", + "solana-define-syscall 2.3.0", + "solana-hash 2.3.0", + "solana-sanitize 2.2.1", +] + +[[package]] +name = "solana-borsh" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718333bcd0a1a7aed6655aa66bef8d7fb047944922b2d3a18f49cbc13e73d004" +dependencies = [ + "borsh 0.10.4", + "borsh 1.6.0", +] + +[[package]] +name = "solana-clock" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb482ab70fced82ad3d7d3d87be33d466a3498eb8aa856434ff3c0dfc2e2e31" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-sysvar-id 2.2.1", +] + [[package]] name = "solana-clock" version = "3.0.0" @@ -822,8 +1380,8 @@ dependencies = [ "serde", "serde_derive", "solana-sdk-ids 3.1.0", - "solana-sdk-macro", - "solana-sysvar-id", + "solana-sdk-macro 3.0.0", + "solana-sysvar-id 3.1.0", ] [[package]] @@ -832,23 +1390,51 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8432d2c4c22d0499aa06d62e4f7e333f81777b3d7c96050ae9e5cb71a8c3aee4" dependencies = [ - "borsh", + "borsh 1.6.0", "solana-instruction 2.3.3", "solana-sdk-ids 2.2.1", ] +[[package]] +name = "solana-cpi" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc71126edddc2ba014622fc32d0f5e2e78ec6c5a1e0eb511b85618c09e9ea11" +dependencies = [ + "solana-account-info 2.3.0", + "solana-define-syscall 2.3.0", + "solana-instruction 2.3.3", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-stable-layout 2.2.1", +] + [[package]] name = "solana-cpi" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dea26709d867aada85d0d3617db0944215c8bb28d3745b912de7db13a23280c" dependencies = [ - "solana-account-info", + "solana-account-info 3.1.0", "solana-define-syscall 4.0.1", "solana-instruction 3.1.0", - "solana-program-error", + "solana-program-error 3.0.0", "solana-pubkey 4.0.0", - "solana-stable-layout", + "solana-stable-layout 3.0.0", +] + +[[package]] +name = "solana-curve25519" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae4261b9a8613d10e77ac831a8fa60b6fa52b9b103df46d641deff9f9812a23" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "solana-define-syscall 2.3.0", + "subtle", + "thiserror 2.0.18", ] [[package]] @@ -878,6 +1464,31 @@ version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57e5b1c0bc1d4a4d10c88a4100499d954c09d3fecfae4912c1a074dff68b1738" +[[package]] +name = "solana-derivation-path" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "939756d798b25c5ec3cca10e06212bdca3b1443cb9bb740a38124f58b258737b" +dependencies = [ + "derivation-path", + "qstring", + "uriparse", +] + +[[package]] +name = "solana-epoch-rewards" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b575d3dd323b9ea10bb6fe89bf6bf93e249b215ba8ed7f68f1a3633f384db7" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 2.3.0", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-sysvar-id 2.2.1", +] + [[package]] name = "solana-epoch-rewards" version = "3.0.0" @@ -888,8 +1499,21 @@ dependencies = [ "serde_derive", "solana-hash 3.1.0", "solana-sdk-ids 3.1.0", - "solana-sdk-macro", - "solana-sysvar-id", + "solana-sdk-macro 3.0.0", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-epoch-schedule" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fce071fbddecc55d727b1d7ed16a629afe4f6e4c217bc8d00af3b785f6f67ed" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-sysvar-id 2.2.1", ] [[package]] @@ -901,8 +1525,59 @@ dependencies = [ "serde", "serde_derive", "solana-sdk-ids 3.1.0", - "solana-sdk-macro", - "solana-sysvar-id", + "solana-sdk-macro 3.0.0", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-example-mocks" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84461d56cbb8bb8d539347151e0525b53910102e4bced875d49d5139708e39d3" +dependencies = [ + "serde", + "serde_derive", + "solana-address-lookup-table-interface", + "solana-clock 2.2.2", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-keccak-hasher", + "solana-message 2.4.0", + "solana-nonce", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-system-interface 1.0.0", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-feature-gate-interface" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f5c5382b449e8e4e3016fb05e418c53d57782d8b5c30aa372fc265654b956d" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-account", + "solana-account-info 2.3.0", + "solana-instruction 2.3.3", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-system-interface 1.0.0", +] + +[[package]] +name = "solana-fee-calculator" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89bc408da0fb3812bc3008189d148b4d3e08252c79ad810b245482a3f70cd8d" +dependencies = [ + "log", + "serde", + "serde_derive", ] [[package]] @@ -922,8 +1597,13 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b96e9f0300fa287b545613f007dfe20043d7812bee255f418c1eb649c93b63" dependencies = [ + "borsh 1.6.0", + "bytemuck", + "bytemuck_derive", "five8 0.2.1", "js-sys", + "serde", + "serde_derive", "solana-atomic-u64 2.2.1", "solana-sanitize 2.2.1", "wasm-bindgen", @@ -959,9 +1639,14 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bab5682934bd1f65f8d2c16f21cb532526fcc1a09f796e2cacdb091eee5774ad" dependencies = [ + "bincode", + "borsh 1.6.0", "getrandom 0.2.17", "js-sys", "num-traits", + "serde", + "serde_derive", + "serde_json", "solana-define-syscall 2.3.0", "solana-pubkey 2.4.0", "wasm-bindgen", @@ -974,7 +1659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee1b699a2c1518028a9982e255e0eca10c44d90006542d9d7f9f40dbce3f7c78" dependencies = [ "bincode", - "borsh", + "borsh 1.6.0", "serde", "serde_derive", "solana-define-syscall 4.0.1", @@ -991,7 +1676,36 @@ dependencies = [ "num-traits", "serde", "serde_derive", - "solana-program-error", + "solana-program-error 3.0.0", +] + +[[package]] +name = "solana-instructions-sysvar" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0e85a6fad5c2d0c4f5b91d34b8ca47118fc593af706e523cdbedf846a954f57" +dependencies = [ + "bitflags", + "solana-account-info 2.3.0", + "solana-instruction 2.3.3", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-sanitize 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-serialize-utils", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-keccak-hasher" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7aeb957fbd42a451b99235df4942d96db7ef678e8d5061ef34c9b34cae12f79" +dependencies = [ + "sha3", + "solana-define-syscall 2.3.0", + "solana-hash 2.3.0", + "solana-sanitize 2.2.1", ] [[package]] @@ -1002,7 +1716,7 @@ checksum = "bd3f04aa1a05c535e93e121a95f66e7dcccf57e007282e8255535d24bf1e98bb" dependencies = [ "ed25519-dalek", "five8 0.2.1", - "rand", + "rand 0.7.3", "solana-pubkey 2.4.0", "solana-seed-phrase", "solana-signature 2.3.0", @@ -1010,6 +1724,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "solana-last-restart-slot" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6360ac2fdc72e7463565cd256eedcf10d7ef0c28a1249d261ec168c1b55cdd" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-sysvar-id 2.2.1", +] + [[package]] name = "solana-last-restart-slot" version = "3.0.0" @@ -1019,56 +1746,269 @@ dependencies = [ "serde", "serde_derive", "solana-sdk-ids 3.1.0", - "solana-sdk-macro", - "solana-sysvar-id", + "solana-sdk-macro 3.0.0", + "solana-sysvar-id 3.1.0", ] [[package]] -name = "solana-message" -version = "3.0.1" +name = "solana-loader-v2-interface" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85666605c9fd727f865ed381665db0a8fc29f984a030ecc1e40f43bfb2541623" +checksum = "d8ab08006dad78ae7cd30df8eea0539e207d08d91eaefb3e1d49a446e1c49654" dependencies = [ - "bincode", - "lazy_static", "serde", + "serde_bytes", "serde_derive", - "solana-address 1.1.0", - "solana-hash 3.1.0", - "solana-instruction 3.1.0", - "solana-sanitize 3.0.1", - "solana-sdk-ids 3.1.0", - "solana-short-vec", - "solana-transaction-error 3.0.0", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", ] [[package]] -name = "solana-msg" -version = "3.0.0" +name = "solana-loader-v3-interface" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f7162a05b8b0773156b443bccd674ea78bb9aa406325b467ea78c06c99a63a2" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-system-interface 1.0.0", +] + +[[package]] +name = "solana-loader-v4-interface" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706a777242f1f39a83e2a96a2a6cb034cb41169c6ecbee2cf09cb873d9659e7e" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-system-interface 1.0.0", +] + +[[package]] +name = "solana-message" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1796aabce376ff74bf89b78d268fa5e683d7d7a96a0a4e4813ec34de49d5314b" +dependencies = [ + "bincode", + "blake3", + "lazy_static", + "serde", + "serde_derive", + "solana-bincode", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sanitize 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-short-vec 2.2.1", + "solana-system-interface 1.0.0", + "solana-transaction-error 2.2.1", + "wasm-bindgen", +] + +[[package]] +name = "solana-message" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85666605c9fd727f865ed381665db0a8fc29f984a030ecc1e40f43bfb2541623" +dependencies = [ + "bincode", + "lazy_static", + "serde", + "serde_derive", + "solana-address 1.1.0", + "solana-hash 3.1.0", + "solana-instruction 3.1.0", + "solana-sanitize 3.0.1", + "solana-sdk-ids 3.1.0", + "solana-short-vec 3.1.0", + "solana-transaction-error 3.0.0", +] + +[[package]] +name = "solana-msg" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36a1a14399afaabc2781a1db09cb14ee4cc4ee5c7a5a3cfcc601811379a8092" +dependencies = [ + "solana-define-syscall 2.3.0", +] + +[[package]] +name = "solana-msg" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "264275c556ea7e22b9d3f87d56305546a38d4eee8ec884f3b126236cb7dcbbb4" dependencies = [ "solana-define-syscall 3.0.0", ] +[[package]] +name = "solana-native-token" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61515b880c36974053dd499c0510066783f0cc6ac17def0c7ef2a244874cf4a9" + +[[package]] +name = "solana-nonce" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703e22eb185537e06204a5bd9d509b948f0066f2d1d814a6f475dafb3ddf1325" +dependencies = [ + "serde", + "serde_derive", + "solana-fee-calculator 2.2.1", + "solana-hash 2.3.0", + "solana-pubkey 2.4.0", + "solana-sha256-hasher 2.3.0", +] + +[[package]] +name = "solana-program" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98eca145bd3545e2fbb07166e895370576e47a00a7d824e325390d33bf467210" +dependencies = [ + "bincode", + "blake3", + "borsh 0.10.4", + "borsh 1.6.0", + "bs58", + "bytemuck", + "console_error_panic_hook", + "console_log", + "getrandom 0.2.17", + "lazy_static", + "log", + "memoffset", + "num-bigint", + "num-derive", + "num-traits", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_derive", + "solana-account-info 2.3.0", + "solana-address-lookup-table-interface", + "solana-atomic-u64 2.2.1", + "solana-big-mod-exp", + "solana-bincode", + "solana-blake3-hasher", + "solana-borsh", + "solana-clock 2.2.2", + "solana-cpi 2.2.1", + "solana-decode-error", + "solana-define-syscall 2.3.0", + "solana-epoch-rewards 2.2.1", + "solana-epoch-schedule 2.2.1", + "solana-example-mocks", + "solana-feature-gate-interface", + "solana-fee-calculator 2.2.1", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-instructions-sysvar", + "solana-keccak-hasher", + "solana-last-restart-slot 2.2.1", + "solana-loader-v2-interface", + "solana-loader-v3-interface", + "solana-loader-v4-interface", + "solana-message 2.4.0", + "solana-msg 2.2.1", + "solana-native-token", + "solana-nonce", + "solana-program-entrypoint 2.3.0", + "solana-program-error 2.2.2", + "solana-program-memory 2.3.1", + "solana-program-option", + "solana-program-pack", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sanitize 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-secp256k1-recover", + "solana-serde-varint", + "solana-serialize-utils", + "solana-sha256-hasher 2.3.0", + "solana-short-vec 2.2.1", + "solana-slot-hashes 2.2.1", + "solana-slot-history 2.2.1", + "solana-stable-layout 2.2.1", + "solana-stake-interface 1.2.1", + "solana-system-interface 1.0.0", + "solana-sysvar 2.3.0", + "solana-sysvar-id 2.2.1", + "solana-vote-interface", + "thiserror 2.0.18", + "wasm-bindgen", +] + +[[package]] +name = "solana-program-entrypoint" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ce041b1a0ed275290a5008ee1a4a6c48f5054c8a3d78d313c08958a06aedbd" +dependencies = [ + "solana-account-info 2.3.0", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", +] + [[package]] name = "solana-program-entrypoint" version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c9b0a1ff494e05f503a08b3d51150b73aa639544631e510279d6375f290997" dependencies = [ - "solana-account-info", + "solana-account-info 3.1.0", "solana-define-syscall 4.0.1", - "solana-program-error", + "solana-program-error 3.0.0", "solana-pubkey 4.0.0", ] +[[package]] +name = "solana-program-error" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee2e0217d642e2ea4bee237f37bd61bb02aec60da3647c48ff88f6556ade775" +dependencies = [ + "borsh 1.6.0", + "num-traits", + "serde", + "serde_derive", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-pubkey 2.4.0", +] + [[package]] name = "solana-program-error" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1af32c995a7b692a915bb7414d5f8e838450cf7c70414e763d8abcae7b51f28" +[[package]] +name = "solana-program-memory" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a5426090c6f3fd6cfdc10685322fede9ca8e5af43cd6a59e98bfe4e91671712" +dependencies = [ + "solana-define-syscall 2.3.0", +] + [[package]] name = "solana-program-memory" version = "3.1.0" @@ -1078,18 +2018,39 @@ dependencies = [ "solana-define-syscall 4.0.1", ] +[[package]] +name = "solana-program-option" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc677a2e9bc616eda6dbdab834d463372b92848b2bfe4a1ed4e4b4adba3397d0" + +[[package]] +name = "solana-program-pack" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "319f0ef15e6e12dc37c597faccb7d62525a509fec5f6975ecb9419efddeb277b" +dependencies = [ + "solana-program-error 2.2.2", +] + [[package]] name = "solana-pubkey" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b62adb9c3261a052ca1f999398c388f1daf558a1b492f60a6d9e64857db4ff1" dependencies = [ + "borsh 0.10.4", + "borsh 1.6.0", + "bytemuck", + "bytemuck_derive", "curve25519-dalek 4.1.3", "five8 0.2.1", "five8_const 0.1.4", "getrandom 0.2.17", "js-sys", "num-traits", + "serde", + "serde_derive", "solana-atomic-u64 2.2.1", "solana-decode-error", "solana-define-syscall 2.3.0", @@ -1116,6 +2077,19 @@ dependencies = [ "solana-address 2.0.0", ] +[[package]] +name = "solana-rent" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1aea8fdea9de98ca6e8c2da5827707fb3842833521b528a713810ca685d2480" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-sysvar-id 2.2.1", +] + [[package]] name = "solana-rent" version = "3.1.0" @@ -1125,8 +2099,8 @@ dependencies = [ "serde", "serde_derive", "solana-sdk-ids 3.1.0", - "solana-sdk-macro", - "solana-sysvar-id", + "solana-sdk-macro 3.0.0", + "solana-sysvar-id 3.1.0", ] [[package]] @@ -1159,6 +2133,18 @@ dependencies = [ "solana-address 2.0.0", ] +[[package]] +name = "solana-sdk-macro" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86280da8b99d03560f6ab5aca9de2e38805681df34e0bb8f238e69b29433b9df" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "solana-sdk-macro" version = "3.0.0" @@ -1168,7 +2154,36 @@ dependencies = [ "bs58", "proc-macro2", "quote", - "syn", + "syn 2.0.114", +] + +[[package]] +name = "solana-secp256k1-recover" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa3120b6cdaa270f39444f5093a90a7b03d296d362878f7a6991d6de3bbe496" +dependencies = [ + "libsecp256k1", + "solana-define-syscall 2.3.0", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-security-txt" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "156bb61a96c605fa124e052d630dba2f6fb57e08c7d15b757e1e958b3ed7b3fe" +dependencies = [ + "hashbrown 0.15.2", +] + +[[package]] +name = "solana-seed-derivable" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beb82b5adb266c6ea90e5cf3967235644848eac476c5a1f2f9283a143b7c97f" +dependencies = [ + "solana-derivation-path", ] [[package]] @@ -1182,6 +2197,26 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "solana-serde-varint" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a7e155eba458ecfb0107b98236088c3764a09ddf0201ec29e52a0be40857113" +dependencies = [ + "serde", +] + +[[package]] +name = "solana-serialize-utils" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "817a284b63197d2b27afdba829c5ab34231da4a9b4e763466a003c40ca4f535e" +dependencies = [ + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sanitize 2.2.1", +] + [[package]] name = "solana-sha256-hasher" version = "2.3.0" @@ -1204,6 +2239,15 @@ dependencies = [ "solana-hash 4.0.1", ] +[[package]] +name = "solana-short-vec" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c54c66f19b9766a56fa0057d060de8378676cb64987533fa088861858fc5a69" +dependencies = [ + "serde", +] + [[package]] name = "solana-short-vec" version = "3.1.0" @@ -1259,6 +2303,19 @@ dependencies = [ "solana-transaction-error 3.0.0", ] +[[package]] +name = "solana-slot-hashes" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8691982114513763e88d04094c9caa0376b867a29577939011331134c301ce" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 2.3.0", + "solana-sdk-ids 2.2.1", + "solana-sysvar-id 2.2.1", +] + [[package]] name = "solana-slot-hashes" version = "3.0.0" @@ -1269,7 +2326,20 @@ dependencies = [ "serde_derive", "solana-hash 3.1.0", "solana-sdk-ids 3.1.0", - "solana-sysvar-id", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-slot-history" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ccc1b2067ca22754d5283afb2b0126d61eae734fc616d23871b0943b0d935e" +dependencies = [ + "bv", + "serde", + "serde_derive", + "solana-sdk-ids 2.2.1", + "solana-sysvar-id 2.2.1", ] [[package]] @@ -1282,7 +2352,17 @@ dependencies = [ "serde", "serde_derive", "solana-sdk-ids 3.1.0", - "solana-sysvar-id", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-stable-layout" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f14f7d02af8f2bc1b5efeeae71bc1c2b7f0f65cd75bcc7d8180f2c762a57f54" +dependencies = [ + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", ] [[package]] @@ -1297,67 +2377,151 @@ dependencies = [ [[package]] name = "solana-stake-interface" -version = "2.0.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9bc26191b533f9a6e5a14cca05174119819ced680a80febff2f5051a713f0db" +checksum = "5269e89fde216b4d7e1d1739cf5303f8398a1ff372a81232abbee80e554a838c" dependencies = [ + "borsh 0.10.4", + "borsh 1.6.0", "num-traits", "serde", "serde_derive", - "solana-clock", - "solana-cpi", - "solana-instruction 3.1.0", - "solana-program-error", - "solana-pubkey 3.0.0", - "solana-system-interface", - "solana-sysvar", + "solana-clock 2.2.2", + "solana-cpi 2.2.1", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-system-interface 1.0.0", + "solana-sysvar-id 2.2.1", ] [[package]] -name = "solana-system-interface" -version = "2.0.0" +name = "solana-stake-interface" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1790547bfc3061f1ee68ea9d8dc6c973c02a163697b24263a8e9f2e6d4afa2" +checksum = "b9bc26191b533f9a6e5a14cca05174119819ced680a80febff2f5051a713f0db" dependencies = [ "num-traits", "serde", "serde_derive", + "solana-clock 3.0.0", + "solana-cpi 3.1.0", "solana-instruction 3.1.0", - "solana-msg", - "solana-program-error", + "solana-program-error 3.0.0", "solana-pubkey 3.0.0", + "solana-system-interface 2.0.0", + "solana-sysvar 3.1.1", ] [[package]] -name = "solana-sysvar" +name = "solana-system-interface" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d7c18cb1a91c6be5f5a8ac9276a1d7c737e39a21beba9ea710ab4b9c63bc90" +dependencies = [ + "js-sys", + "num-traits", + "serde", + "serde_derive", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "wasm-bindgen", +] + +[[package]] +name = "solana-system-interface" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e1790547bfc3061f1ee68ea9d8dc6c973c02a163697b24263a8e9f2e6d4afa2" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-instruction 3.1.0", + "solana-msg 3.0.0", + "solana-program-error 3.0.0", + "solana-pubkey 3.0.0", +] + +[[package]] +name = "solana-sysvar" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c3595f95069f3d90f275bb9bd235a1973c4d059028b0a7f81baca2703815db" +dependencies = [ + "base64 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "lazy_static", + "serde", + "serde_derive", + "solana-account-info 2.3.0", + "solana-clock 2.2.2", + "solana-define-syscall 2.3.0", + "solana-epoch-rewards 2.2.1", + "solana-epoch-schedule 2.2.1", + "solana-fee-calculator 2.2.1", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-instructions-sysvar", + "solana-last-restart-slot 2.2.1", + "solana-program-entrypoint 2.3.0", + "solana-program-error 2.2.2", + "solana-program-memory 2.3.1", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sanitize 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-sdk-macro 2.2.1", + "solana-slot-hashes 2.2.1", + "solana-slot-history 2.2.1", + "solana-stake-interface 1.2.1", + "solana-sysvar-id 2.2.1", +] + +[[package]] +name = "solana-sysvar" version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6690d3dd88f15c21edff68eb391ef8800df7a1f5cec84ee3e8d1abf05affdf74" dependencies = [ - "base64", + "base64 0.22.1", "bincode", "lazy_static", "serde", "serde_derive", - "solana-account-info", - "solana-clock", + "solana-account-info 3.1.0", + "solana-clock 3.0.0", "solana-define-syscall 4.0.1", - "solana-epoch-rewards", - "solana-epoch-schedule", - "solana-fee-calculator", + "solana-epoch-rewards 3.0.0", + "solana-epoch-schedule 3.0.0", + "solana-fee-calculator 3.0.0", "solana-hash 4.0.1", "solana-instruction 3.1.0", - "solana-last-restart-slot", - "solana-program-entrypoint", - "solana-program-error", - "solana-program-memory", + "solana-last-restart-slot 3.0.0", + "solana-program-entrypoint 3.1.1", + "solana-program-error 3.0.0", + "solana-program-memory 3.1.0", "solana-pubkey 4.0.0", - "solana-rent", + "solana-rent 3.1.0", "solana-sdk-ids 3.1.0", - "solana-sdk-macro", - "solana-slot-hashes", - "solana-slot-history", - "solana-sysvar-id", + "solana-sdk-macro 3.0.0", + "solana-slot-hashes 3.0.0", + "solana-slot-history 3.0.0", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-sysvar-id" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5762b273d3325b047cfda250787f8d796d781746860d5d0a746ee29f3e8812c1" +dependencies = [ + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", ] [[package]] @@ -1383,10 +2547,10 @@ dependencies = [ "solana-hash 4.0.1", "solana-instruction 3.1.0", "solana-instruction-error", - "solana-message", + "solana-message 3.0.1", "solana-sanitize 3.0.1", "solana-sdk-ids 3.1.0", - "solana-short-vec", + "solana-short-vec 3.1.0", "solana-signature 3.1.0", "solana-signer 3.0.0", "solana-transaction-error 3.0.0", @@ -1414,12 +2578,448 @@ dependencies = [ "solana-sanitize 3.0.1", ] +[[package]] +name = "solana-vote-interface" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b80d57478d6599d30acc31cc5ae7f93ec2361a06aefe8ea79bc81739a08af4c3" +dependencies = [ + "bincode", + "num-derive", + "num-traits", + "serde", + "serde_derive", + "solana-clock 2.2.2", + "solana-decode-error", + "solana-hash 2.3.0", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-serde-varint", + "solana-serialize-utils", + "solana-short-vec 2.2.1", + "solana-system-interface 1.0.0", +] + +[[package]] +name = "solana-zk-sdk" +version = "2.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b9fc6ec37d16d0dccff708ed1dd6ea9ba61796700c3bb7c3b401973f10f63b" +dependencies = [ + "aes-gcm-siv", + "base64 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "itertools", + "js-sys", + "merlin", + "num-derive", + "num-traits", + "rand 0.8.5", + "serde", + "serde_derive", + "serde_json", + "sha3", + "solana-derivation-path", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-seed-derivable", + "solana-seed-phrase", + "solana-signature 2.3.0", + "solana-signer 2.2.1", + "subtle", + "thiserror 2.0.18", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "spl-discriminator" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7398da23554a31660f17718164e31d31900956054f54f52d5ec1be51cb4f4b3" +dependencies = [ + "bytemuck", + "solana-program-error 2.2.2", + "solana-sha256-hasher 2.3.0", + "spl-discriminator-derive", +] + +[[package]] +name = "spl-discriminator-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" +dependencies = [ + "quote", + "spl-discriminator-syn", + "syn 2.0.114", +] + +[[package]] +name = "spl-discriminator-syn" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1dbc82ab91422345b6df40a79e2b78c7bce1ebb366da323572dd60b7076b67" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.9", + "syn 2.0.114", + "thiserror 1.0.69", +] + +[[package]] +name = "spl-elgamal-registry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56cc66fe64651a48c8deb4793d8a5deec8f8faf19f355b9df294387bc5a36b5f" +dependencies = [ + "bytemuck", + "solana-account-info 2.3.0", + "solana-cpi 2.2.1", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-entrypoint 2.3.0", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-security-txt", + "solana-system-interface 1.0.0", + "solana-sysvar 2.3.0", + "solana-zk-sdk", + "spl-pod", + "spl-token-confidential-transfer-proof-extraction", +] + +[[package]] +name = "spl-memo" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f09647c0974e33366efeb83b8e2daebb329f0420149e74d3a4bd2c08cf9f7cb" +dependencies = [ + "solana-account-info 2.3.0", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-entrypoint 2.3.0", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", +] + +[[package]] +name = "spl-pod" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d994afaf86b779104b4a95ba9ca75b8ced3fdb17ee934e38cb69e72afbe17799" +dependencies = [ + "borsh 1.6.0", + "bytemuck", + "bytemuck_derive", + "num-derive", + "num-traits", + "solana-decode-error", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-program-option", + "solana-pubkey 2.4.0", + "solana-zk-sdk", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-program-error" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdebc8b42553070b75aa5106f071fef2eb798c64a7ec63375da4b1f058688c6" +dependencies = [ + "num-derive", + "num-traits", + "solana-decode-error", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "spl-program-error-derive", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-program-error-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2539e259c66910d78593475540e8072f0b10f0f61d7607bbf7593899ed52d0" +dependencies = [ + "proc-macro2", + "quote", + "sha2 0.10.9", + "syn 2.0.114", +] + +[[package]] +name = "spl-stake-pool" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0db03f091f43b5766296e80088718491b50949cd3eb4cce3e0cfed58fe2c18" +dependencies = [ + "arrayref", + "bincode", + "borsh 1.6.0", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "serde", + "serde_derive", + "solana-program", + "solana-security-txt", + "solana-stake-interface 1.2.1", + "solana-system-interface 1.0.0", + "spl-pod", + "spl-token-2022", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-tlv-account-resolution" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1408e961215688715d5a1063cbdcf982de225c45f99c82b4f7d7e1dd22b998d7" +dependencies = [ + "bytemuck", + "num-derive", + "num-traits", + "solana-account-info 2.3.0", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-type-length-value", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-token" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053067c6a82c705004f91dae058b11b4780407e9ccd6799dc9e7d0fab5f242da" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "solana-account-info 2.3.0", + "solana-cpi 2.2.1", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-entrypoint 2.3.0", + "solana-program-error 2.2.2", + "solana-program-memory 2.3.1", + "solana-program-option", + "solana-program-pack", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-sysvar 2.3.0", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-token-2022" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707d8237d17d857246b189d0fb278797dcd7cf6219374547791b231fd35a8cc8" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "solana-account-info 2.3.0", + "solana-clock 2.2.2", + "solana-cpi 2.2.1", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-native-token", + "solana-program-entrypoint 2.3.0", + "solana-program-error 2.2.2", + "solana-program-memory 2.3.1", + "solana-program-option", + "solana-program-pack", + "solana-pubkey 2.4.0", + "solana-rent 2.2.1", + "solana-sdk-ids 2.2.1", + "solana-security-txt", + "solana-system-interface 1.0.0", + "solana-sysvar 2.3.0", + "solana-zk-sdk", + "spl-elgamal-registry", + "spl-memo", + "spl-pod", + "spl-token", + "spl-token-confidential-transfer-ciphertext-arithmetic", + "spl-token-confidential-transfer-proof-extraction", + "spl-token-confidential-transfer-proof-generation", + "spl-token-group-interface", + "spl-token-metadata-interface", + "spl-transfer-hook-interface", + "spl-type-length-value", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-token-confidential-transfer-ciphertext-arithmetic" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cddd52bfc0f1c677b41493dafa3f2dbbb4b47cf0990f08905429e19dc8289b35" +dependencies = [ + "base64 0.22.1", + "bytemuck", + "solana-curve25519", + "solana-zk-sdk", +] + +[[package]] +name = "spl-token-confidential-transfer-proof-extraction" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512c85bdbbb4cbcc2038849a9e164c958b16541f252b53ea1a3933191c0a4a1a" +dependencies = [ + "bytemuck", + "solana-account-info 2.3.0", + "solana-curve25519", + "solana-instruction 2.3.3", + "solana-instructions-sysvar", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-zk-sdk", + "spl-pod", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-token-confidential-transfer-proof-generation" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa27b9174bea869a7ebf31e0be6890bce90b1a4288bc2bbf24bd413f80ae3fde" +dependencies = [ + "curve25519-dalek 4.1.3", + "solana-zk-sdk", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-token-group-interface" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5597b4cd76f85ce7cd206045b7dc22da8c25516573d42d267c8d1fd128db5129" +dependencies = [ + "bytemuck", + "num-derive", + "num-traits", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "spl-discriminator", + "spl-pod", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-token-metadata-interface" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "304d6e06f0de0c13a621464b1fd5d4b1bebf60d15ca71a44d3839958e0da16ee" +dependencies = [ + "borsh 1.6.0", + "num-derive", + "num-traits", + "solana-borsh", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "spl-discriminator", + "spl-pod", + "spl-type-length-value", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-transfer-hook-interface" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e905b849b6aba63bde8c4badac944ebb6c8e6e14817029cbe1bc16829133bd" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "solana-account-info 2.3.0", + "solana-cpi 2.2.1", + "solana-decode-error", + "solana-instruction 2.3.3", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "solana-pubkey 2.4.0", + "spl-discriminator", + "spl-pod", + "spl-program-error", + "spl-tlv-account-resolution", + "spl-type-length-value", + "thiserror 2.0.18", +] + +[[package]] +name = "spl-type-length-value" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d417eb548214fa822d93f84444024b4e57c13ed6719d4dcc68eec24fb481e9f5" +dependencies = [ + "bytemuck", + "num-derive", + "num-traits", + "solana-account-info 2.3.0", + "solana-decode-error", + "solana-msg 2.2.1", + "solana-program-error 2.2.2", + "spl-discriminator", + "spl-pod", + "thiserror 2.0.18", +] + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.114" @@ -1431,6 +3031,46 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "tinyvec" version = "1.10.0" @@ -1446,6 +3086,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -1488,6 +3137,26 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + [[package]] name = "version_check" version = "0.9.5" @@ -1561,7 +3230,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -1603,16 +3272,16 @@ checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] name = "wasm-solana" version = "0.1.0" dependencies = [ - "base64", + "base64 0.22.1", "bincode", - "borsh", + "borsh 1.6.0", "hex", "js-sys", "serde", @@ -1622,9 +3291,10 @@ dependencies = [ "solana-keypair", "solana-pubkey 2.4.0", "solana-signer 2.2.1", - "solana-stake-interface", - "solana-system-interface", + "solana-stake-interface 2.0.2", + "solana-system-interface 2.0.0", "solana-transaction", + "spl-stake-pool", "wasm-bindgen", "wasm-bindgen-test", ] @@ -1689,7 +3359,7 @@ checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1709,7 +3379,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] diff --git a/packages/wasm-solana/Cargo.toml b/packages/wasm-solana/Cargo.toml index 6e01d8c..814f407 100644 --- a/packages/wasm-solana/Cargo.toml +++ b/packages/wasm-solana/Cargo.toml @@ -23,6 +23,8 @@ solana-transaction = { version = "3.0", features = ["serde", "bincode"] } solana-system-interface = { version = "2.0", features = ["bincode"] } solana-stake-interface = { version = "2.0", features = ["bincode"] } solana-compute-budget-interface = { version = "2.0", features = ["borsh"] } +# SPL Stake Pool for Jito liquid staking support +spl-stake-pool = { version = "2.0", default-features = false } # Serialization bincode = "1.3" borsh = "1.5" diff --git a/packages/wasm-solana/js/parser.ts b/packages/wasm-solana/js/parser.ts index c92d49f..970d355 100644 --- a/packages/wasm-solana/js/parser.ts +++ b/packages/wasm-solana/js/parser.ts @@ -131,6 +131,36 @@ export interface MemoParams { memo: string; } +/** Stake pool deposit SOL parameters (Jito liquid staking) */ +export interface StakePoolDepositSolParams { + type: "StakePoolDepositSol"; + stakePool: string; + withdrawAuthority: string; + reserveStake: string; + fundingAccount: string; + destinationPoolAccount: string; + managerFeeAccount: string; + referralPoolAccount: string; + poolMint: string; + lamports: string; +} + +/** Stake pool withdraw stake parameters (Jito liquid staking) */ +export interface StakePoolWithdrawStakeParams { + type: "StakePoolWithdrawStake"; + stakePool: string; + validatorList: string; + withdrawAuthority: string; + validatorStake: string; + destinationStake: string; + destinationStakeAuthority: string; + sourceTransferAuthority: string; + sourcePoolAccount: string; + managerFeeAccount: string; + poolMint: string; + poolTokens: string; +} + /** Account metadata for unknown instructions */ export interface AccountMeta { pubkey: string; @@ -163,6 +193,8 @@ export type InstructionParams = | CreateAtaParams | CloseAtaParams | MemoParams + | StakePoolDepositSolParams + | StakePoolWithdrawStakeParams | UnknownInstructionParams; // ============================================================================= diff --git a/packages/wasm-solana/src/instructions/decode.rs b/packages/wasm-solana/src/instructions/decode.rs index 81bc21d..d8f9bf2 100644 --- a/packages/wasm-solana/src/instructions/decode.rs +++ b/packages/wasm-solana/src/instructions/decode.rs @@ -21,6 +21,7 @@ pub fn decode_instruction(ctx: InstructionContext) -> ParsedInstruction { MEMO_PROGRAM_ID => decode_memo_instruction(ctx), TOKEN_PROGRAM_ID | TOKEN_2022_PROGRAM_ID => decode_token_instruction(ctx), ATA_PROGRAM_ID => decode_ata_instruction(ctx), + STAKE_POOL_PROGRAM_ID => decode_stake_pool_instruction(ctx), _ => make_unknown(ctx), } } @@ -310,6 +311,95 @@ fn decode_ata_instruction(ctx: InstructionContext) -> ParsedInstruction { } } +// ============================================================================= +// Stake Pool (Jito) Decoding +// ============================================================================= + +/// Instruction discriminators for SPL Stake Pool program. +const STAKE_POOL_DEPOSIT_SOL: u8 = 14; +const STAKE_POOL_WITHDRAW_STAKE: u8 = 10; + +fn decode_stake_pool_instruction(ctx: InstructionContext) -> ParsedInstruction { + // Stake pool instruction format: first byte is discriminator, followed by u64 amount + if ctx.data.is_empty() { + return make_unknown(ctx); + } + + let discriminator = ctx.data[0]; + + match discriminator { + STAKE_POOL_DEPOSIT_SOL => { + // DepositSol: deposit SOL into stake pool, receive pool tokens + // Data: [0] discriminator (14), [1..9] lamports (u64 LE) + // Accounts: + // [0] stakePool + // [1] withdrawAuthority + // [2] reserveStake + // [3] fundingAccount (signer) + // [4] destinationPoolAccount + // [5] managerFeeAccount + // [6] referralPoolAccount + // [7] poolMint + // [8] systemProgram + // [9] tokenProgram + // [10] optional depositAuthority (signer) + if ctx.accounts.len() >= 8 && ctx.data.len() >= 9 { + let lamports = u64::from_le_bytes(ctx.data[1..9].try_into().unwrap_or([0; 8])); + ParsedInstruction::StakePoolDepositSol(StakePoolDepositSolParams { + stake_pool: ctx.accounts[0].clone(), + withdraw_authority: ctx.accounts[1].clone(), + reserve_stake: ctx.accounts[2].clone(), + funding_account: ctx.accounts[3].clone(), + destination_pool_account: ctx.accounts[4].clone(), + manager_fee_account: ctx.accounts[5].clone(), + referral_pool_account: ctx.accounts[6].clone(), + pool_mint: ctx.accounts[7].clone(), + lamports: lamports.to_string(), + }) + } else { + make_unknown(ctx) + } + } + STAKE_POOL_WITHDRAW_STAKE => { + // WithdrawStake: withdraw stake from pool by burning pool tokens + // Data: [0] discriminator (10), [1..9] poolTokens (u64 LE) + // Accounts: + // [0] stakePool + // [1] validatorList + // [2] withdrawAuthority + // [3] validatorStake + // [4] destinationStake + // [5] destinationStakeAuthority + // [6] sourceTransferAuthority (signer) + // [7] sourcePoolAccount + // [8] managerFeeAccount + // [9] poolMint + // [10] clockSysvar + // [11] tokenProgram + // [12] stakeProgram + if ctx.accounts.len() >= 10 && ctx.data.len() >= 9 { + let pool_tokens = u64::from_le_bytes(ctx.data[1..9].try_into().unwrap_or([0; 8])); + ParsedInstruction::StakePoolWithdrawStake(StakePoolWithdrawStakeParams { + stake_pool: ctx.accounts[0].clone(), + validator_list: ctx.accounts[1].clone(), + withdraw_authority: ctx.accounts[2].clone(), + validator_stake: ctx.accounts[3].clone(), + destination_stake: ctx.accounts[4].clone(), + destination_stake_authority: ctx.accounts[5].clone(), + source_transfer_authority: ctx.accounts[6].clone(), + source_pool_account: ctx.accounts[7].clone(), + manager_fee_account: ctx.accounts[8].clone(), + pool_mint: ctx.accounts[9].clone(), + pool_tokens: pool_tokens.to_string(), + }) + } else { + make_unknown(ctx) + } + } + _ => make_unknown(ctx), + } +} + // ============================================================================= // Fallback // ============================================================================= diff --git a/packages/wasm-solana/src/instructions/types.rs b/packages/wasm-solana/src/instructions/types.rs index 4f0e7f9..181d9f2 100644 --- a/packages/wasm-solana/src/instructions/types.rs +++ b/packages/wasm-solana/src/instructions/types.rs @@ -13,6 +13,7 @@ pub const MEMO_PROGRAM_ID: &str = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; pub const TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; pub const TOKEN_2022_PROGRAM_ID: &str = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"; pub const ATA_PROGRAM_ID: &str = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"; +pub const STAKE_POOL_PROGRAM_ID: &str = "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy"; /// A parsed instruction with type discriminant and params. #[derive(Debug, Clone, Serialize)] @@ -43,6 +44,10 @@ pub enum ParsedInstruction { // Memo Memo(MemoParams), + // Stake Pool (Jito liquid staking) instructions + StakePoolDepositSol(StakePoolDepositSolParams), + StakePoolWithdrawStake(StakePoolWithdrawStakeParams), + // Fallback for unknown/custom instructions Unknown(UnknownInstructionParams), } @@ -196,7 +201,7 @@ pub struct CloseAtaParams { } // ============================================================================= -// Memo & Unknown +// Memo // ============================================================================= #[derive(Debug, Clone, Serialize)] @@ -204,6 +209,83 @@ pub struct MemoParams { pub memo: String, } +// ============================================================================= +// Stake Pool (Jito) Params +// ============================================================================= + +/// Parameters for DepositSol instruction in stake pool (Jito liquid staking). +#[derive(Debug, Clone, Serialize)] +pub struct StakePoolDepositSolParams { + /// The stake pool address. + #[serde(rename = "stakePool")] + pub stake_pool: String, + /// The stake pool withdraw authority. + #[serde(rename = "withdrawAuthority")] + pub withdraw_authority: String, + /// The reserve stake account. + #[serde(rename = "reserveStake")] + pub reserve_stake: String, + /// The account providing lamports to deposit. + #[serde(rename = "fundingAccount")] + pub funding_account: String, + /// The user account to receive pool tokens. + #[serde(rename = "destinationPoolAccount")] + pub destination_pool_account: String, + /// The manager fee account. + #[serde(rename = "managerFeeAccount")] + pub manager_fee_account: String, + /// The referral pool account. + #[serde(rename = "referralPoolAccount")] + pub referral_pool_account: String, + /// The pool token mint. + #[serde(rename = "poolMint")] + pub pool_mint: String, + /// The amount of lamports to deposit. + pub lamports: String, +} + +/// Parameters for WithdrawStake instruction in stake pool (Jito liquid staking). +#[derive(Debug, Clone, Serialize)] +pub struct StakePoolWithdrawStakeParams { + /// The stake pool address. + #[serde(rename = "stakePool")] + pub stake_pool: String, + /// The validator stake list account. + #[serde(rename = "validatorList")] + pub validator_list: String, + /// The stake pool withdraw authority. + #[serde(rename = "withdrawAuthority")] + pub withdraw_authority: String, + /// The validator stake account to split from. + #[serde(rename = "validatorStake")] + pub validator_stake: String, + /// The uninitialized stake account to receive withdrawal. + #[serde(rename = "destinationStake")] + pub destination_stake: String, + /// The user account to set as stake authority. + #[serde(rename = "destinationStakeAuthority")] + pub destination_stake_authority: String, + /// The authority allowed to transfer from source pool account. + #[serde(rename = "sourceTransferAuthority")] + pub source_transfer_authority: String, + /// The user account with pool tokens to burn. + #[serde(rename = "sourcePoolAccount")] + pub source_pool_account: String, + /// The manager fee account. + #[serde(rename = "managerFeeAccount")] + pub manager_fee_account: String, + /// The pool token mint. + #[serde(rename = "poolMint")] + pub pool_mint: String, + /// The amount of pool tokens to withdraw. + #[serde(rename = "poolTokens")] + pub pool_tokens: String, +} + +// ============================================================================= +// Unknown (fallback) +// ============================================================================= + #[derive(Debug, Clone, Serialize)] pub struct UnknownInstructionParams { #[serde(rename = "programId")] diff --git a/packages/wasm-solana/test/parser.ts b/packages/wasm-solana/test/parser.ts index 42b5234..3e4cf1a 100644 --- a/packages/wasm-solana/test/parser.ts +++ b/packages/wasm-solana/test/parser.ts @@ -1,5 +1,10 @@ import * as assert from "assert"; -import { parseTransaction, type ParsedTransaction } from "../js/parser.js"; +import { + parseTransaction, + type ParsedTransaction, + type StakePoolDepositSolParams, + type StakePoolWithdrawStakeParams, +} from "../js/parser.js"; // Helper to decode base64 in tests function base64ToBytes(base64: string): Uint8Array { @@ -7,6 +12,19 @@ function base64ToBytes(base64: string): Uint8Array { return new Uint8Array(binary); } +// Jito stake pool test transactions from sdk-coin-sol +const JITO_STAKING_ACTIVATE_UNSIGNED_TX = + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAQKReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0Ecg6pe+BOG2OETfAVS9ftz6va1oE4onLBolJ2N+ZOOhJ6naP7fZEyKrpuOIYit0GvFUPv3Fsgiuc5jx3g9lS4fCeaj/uz5kDLhwd9rlyLcs2NOe440QJNrw0sMwcjrUh/80UHpgyyvEK2RdJXKDycbWyk81HAn6nNwB+1A6zmgvQSKPgjDtJW+F/RUJ9ib7FuAx+JpXBhk12dD2zm+00bWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABU5Z4kwFGooUp7HpeX8OEs36dJAhZlMZWmpRKm8WZgKwaBTtTK9ooXRnL9rIYDGmPoTqFe+h1EtyKT9tvbABZQBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnjMtr5L6vs6LY/96RABeX9/Zr6FYdWthxalfkEs7jQgQEICgUHAgABAwEEBgkJDuCTBAAAAAAA"; + +const JITO_STAKING_ACTIVATE_SIGNED_TX = + "AdOUrFCk9yyhi1iB1EfOOXHOeiaZGQnLRwnypt+be8r9lrYMx8w7/QTnithrqcuBApg1ctJAlJMxNZ925vMP2Q0BAAQKReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0Ecg6pe+BOG2OETfAVS9ftz6va1oE4onLBolJ2N+ZOOhJ6naP7fZEyKrpuOIYit0GvFUPv3Fsgiuc5jx3g9lS4fCeaj/uz5kDLhwd9rlyLcs2NOe440QJNrw0sMwcjrUh/80UHpgyyvEK2RdJXKDycbWyk81HAn6nNwB+1A6zmgvQSKPgjDtJW+F/RUJ9ib7FuAx+JpXBhk12dD2zm+00bWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABU5Z4kwFGooUp7HpeX8OEs36dJAhZlMZWmpRKm8WZgKwaBTtTK9ooXRnL9rIYDGmPoTqFe+h1EtyKT9tvbABZQBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnjMtr5L6vs6LY/96RABeX9/Zr6FYdWthxalfkEs7jQgQEICgUHAgABAwEEBgkJDuCTBAAAAAAA"; + +const JITO_STAKING_ACTIVATE_SIGNED_TX_WITH_MEMO = + "AVuU0ma/g7Ur8yGbZDVeoyHWdhh3fCilgfUoKq84lkq7wyhSySwqgR/WC3JAOiRGiW4/8S2244duw6GQNNb3vQYBAAULReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0Ecg6pe+BOG2OETfAVS9ftz6va1oE4onLBolJ2N+ZOOhJ6naP7fZEyKrpuOIYit0GvFUPv3Fsgiuc5jx3g9lS4fCeaj/uz5kDLhwd9rlyLcs2NOe440QJNrw0sMwcjrUh/80UHpgyyvEK2RdJXKDycbWyk81HAn6nNwB+1A6zmgvQSKPgjDtJW+F/RUJ9ib7FuAx+JpXBhk12dD2zm+00bWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABU5Z4kwFGooUp7HpeX8OEs36dJAhZlMZWmpRKm8WZgKwVKU1qZKSEGTSTocWDaOHx8NbXdvJK7geQfqEBBBUSNBoFO1Mr2ihdGcv2shgMaY+hOoV76HUS3IpP229sAFlAG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqeMy2vkvq+zotj/3pEAF5f39mvoVh1a2HFqV+QSzuNCBAgkKBQcCAAEDAQQGCgkO4JMEAAAAAAAIAAl0ZXN0IG1lbW8="; + +const JITO_STAKING_DEACTIVATE_SIGNED_TX = + "A7txZr55CtSJogfV1ihB1JOIuVbmhAh7BCl4hJeTBcbrq6KT+Jzbbjx4qEXDgRnMtY7cb9xnekOHUfKkW9D2RQpchl2oH4Np/+Oghy7QNjKrodZsFlqhiYoo+Zx0Bjf+Hwq35h/zVd1kHRTkaB1ebZwDeEejPrFgNCpkqxRh9ZgOMBethjkNPCrqzk50pOqx1ktJik5loScyp/81bggjQASE4jMdtET/a2jpFJeG34GZLIY6r+LNTXtGsK53qyR9CQMBBg9F5Xm8+SU89otH3/H7OjpcLCG6xbI8Ca4SpuBVa/CLQWJ7L7oBJ2H5uRm8+Uy7TJ3IBR2fCTKZMU05SSMyaL3p6Lfi7T4mzoclKbPedsv+JDs60KtRcBK6Y7CHyYejKikcg6pe+BOG2OETfAVS9ftz6va1oE4onLBolJ2N+ZOOhCPgdQm63e39tRapC5GXu1BHQyVdDjfF/13OiiQe7cQxl9rD5vZLBx1Aaz7SV4hmv2ZhGp4LQEU67b++EtDrIi8J5qP+7PmQMuHB32uXItyzY057jjRAk2vDSwzByOtSH/zRQemDLK8QrZF0lcoPJxtbKTzUcCfqc3AH7UDrOaC9BIo+CMO0lb4X9FQn2JvsW4DH4mlcGGTXZ0PbOb7TRtYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFTlniTAUaihSnsel5fw4Szfp0kCFmUxlaalEqbxZmArBoFO1Mr2ihdGcv2shgMaY+hOoV76HUS3IpP229sAFlAGodgXkTdUKpg0N73+KnqyVX9TXIp4citopJ3AAAAAAAan1RcYx3TJKFZjmGkdXraLXrijm0ttXHNVWyEAAAAABt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnjMtr5L6vs6LY/96RABeX9/Zr6FYdWthxalfkEs7jQgQQOAwMCAAkE6AMAAAAAAAAJAgABNAAAAACA1SIAAAAAAMgAAAAAAAAABqHYF5E3VCqYNDe9/ip6slV/U1yKeHIraKSdwAAAAAALDQgECgUBAAIDBgcNDgwJCugDAAAAAAAADAMBDQAEBQAAAA=="; + describe("parseTransaction", () => { // Test transaction from @solana/web3.js - a simple SOL transfer (100000 lamports) const TEST_TX_BASE64 = @@ -104,4 +122,130 @@ describe("parseTransaction", () => { } }); }); + + describe("Stake Pool (Jito) instruction decoding", () => { + it("should decode DepositSol instruction from Jito staking activate tx", () => { + const bytes = base64ToBytes(JITO_STAKING_ACTIVATE_UNSIGNED_TX); + const parsed = parseTransaction(bytes); + + // Find the DepositSol instruction (should be the last one with discriminator 14) + const depositSol = parsed.instructionsData.find( + (instr): instr is StakePoolDepositSolParams => instr.type === "StakePoolDepositSol", + ); + + assert.ok(depositSol, "Should have a StakePoolDepositSol instruction"); + assert.strictEqual(depositSol.type, "StakePoolDepositSol"); + + // Verify key fields are present and valid + assert.ok(depositSol.stakePool, "Should have stakePool"); + assert.ok(depositSol.withdrawAuthority, "Should have withdrawAuthority"); + assert.ok(depositSol.reserveStake, "Should have reserveStake"); + assert.ok(depositSol.fundingAccount, "Should have fundingAccount"); + assert.ok(depositSol.destinationPoolAccount, "Should have destinationPoolAccount"); + assert.ok(depositSol.managerFeeAccount, "Should have managerFeeAccount"); + assert.ok(depositSol.referralPoolAccount, "Should have referralPoolAccount"); + assert.ok(depositSol.poolMint, "Should have poolMint"); + assert.ok(depositSol.lamports, "Should have lamports amount"); + + // Verify amount is a valid number string (should be 80000000 = 0.08 SOL based on test tx) + const lamportsNum = BigInt(depositSol.lamports); + assert.ok(lamportsNum > 0n, "Lamports should be positive"); + }); + + it("should decode WithdrawStake instruction from Jito staking deactivate tx", () => { + const bytes = base64ToBytes(JITO_STAKING_DEACTIVATE_SIGNED_TX); + const parsed = parseTransaction(bytes); + + // Find the WithdrawStake instruction (should have discriminator 10) + const withdrawStake = parsed.instructionsData.find( + (instr): instr is StakePoolWithdrawStakeParams => instr.type === "StakePoolWithdrawStake", + ); + + assert.ok(withdrawStake, "Should have a StakePoolWithdrawStake instruction"); + assert.strictEqual(withdrawStake.type, "StakePoolWithdrawStake"); + + // Verify key fields are present and valid + assert.ok(withdrawStake.stakePool, "Should have stakePool"); + assert.ok(withdrawStake.validatorList, "Should have validatorList"); + assert.ok(withdrawStake.withdrawAuthority, "Should have withdrawAuthority"); + assert.ok(withdrawStake.validatorStake, "Should have validatorStake"); + assert.ok(withdrawStake.destinationStake, "Should have destinationStake"); + assert.ok(withdrawStake.destinationStakeAuthority, "Should have destinationStakeAuthority"); + assert.ok(withdrawStake.sourceTransferAuthority, "Should have sourceTransferAuthority"); + assert.ok(withdrawStake.sourcePoolAccount, "Should have sourcePoolAccount"); + assert.ok(withdrawStake.managerFeeAccount, "Should have managerFeeAccount"); + assert.ok(withdrawStake.poolMint, "Should have poolMint"); + assert.ok(withdrawStake.poolTokens, "Should have poolTokens amount"); + + // Verify pool tokens is a valid number string + const poolTokensNum = BigInt(withdrawStake.poolTokens); + assert.ok(poolTokensNum > 0n, "Pool tokens should be positive"); + }); + + it("should parse Jito staking tx and include correct instruction types", () => { + const bytes = base64ToBytes(JITO_STAKING_ACTIVATE_UNSIGNED_TX); + const parsed = parseTransaction(bytes); + + // Get all instruction types + const types = parsed.instructionsData.map((i) => i.type); + + // Should contain StakePoolDepositSol + assert.ok( + types.includes("StakePoolDepositSol"), + `Expected StakePoolDepositSol in ${types.join(", ")}`, + ); + }); + + it("should decode DepositSol from signed Jito staking tx", () => { + const bytes = base64ToBytes(JITO_STAKING_ACTIVATE_SIGNED_TX); + const parsed = parseTransaction(bytes); + + const depositSol = parsed.instructionsData.find( + (instr): instr is StakePoolDepositSolParams => instr.type === "StakePoolDepositSol", + ); + + assert.ok(depositSol, "Signed tx should have StakePoolDepositSol instruction"); + // Verify the lamports amount (300000 lamports in the test tx) + assert.strictEqual(depositSol.lamports, "300000"); + }); + + it("should decode DepositSol from Jito staking tx with memo", () => { + const bytes = base64ToBytes(JITO_STAKING_ACTIVATE_SIGNED_TX_WITH_MEMO); + const parsed = parseTransaction(bytes); + + // Should find both Memo and StakePoolDepositSol instructions + const types = parsed.instructionsData.map((i) => i.type); + assert.ok(types.includes("Memo"), `Expected Memo in ${types.join(", ")}`); + assert.ok( + types.includes("StakePoolDepositSol"), + `Expected StakePoolDepositSol in ${types.join(", ")}`, + ); + + // Verify memo content + const memo = parsed.instructionsData.find((i) => i.type === "Memo"); + if (memo && memo.type === "Memo") { + assert.strictEqual(memo.memo, "test memo"); + } + + // Verify DepositSol + const depositSol = parsed.instructionsData.find( + (instr): instr is StakePoolDepositSolParams => instr.type === "StakePoolDepositSol", + ); + assert.ok(depositSol); + assert.strictEqual(depositSol.lamports, "300000"); + }); + + it("should verify WithdrawStake pool tokens amount", () => { + const bytes = base64ToBytes(JITO_STAKING_DEACTIVATE_SIGNED_TX); + const parsed = parseTransaction(bytes); + + const withdrawStake = parsed.instructionsData.find( + (instr): instr is StakePoolWithdrawStakeParams => instr.type === "StakePoolWithdrawStake", + ); + + assert.ok(withdrawStake); + // Verify pool tokens amount (1000 in the test tx) + assert.strictEqual(withdrawStake.poolTokens, "1000"); + }); + }); });