From d659f9b27d7e141b1de631b6c6b642fd07ea7106 Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Fri, 23 Jan 2026 14:52:12 -0800 Subject: [PATCH 1/6] feat(wasm-solana): add SPL Stake Pool instruction decoding for Jito Add support for decoding SPL Stake Pool instructions used by Jito liquid staking. This enables WASM-based parsing of Jito staking transactions for parity with BitGoJS. Changes: - Add spl-stake-pool dependency for official Borsh deserialization - Add STAKE_POOL_PROGRAM_ID constant for SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy - Add StakePoolDepositSol and StakePoolWithdrawStake instruction types - Add decode_stake_pool_instruction using official StakePoolInstruction enum - Add TypeScript interfaces for stake pool instructions - Add Jito transaction parsing tests using BitGoJS test fixtures Ticket: BTC-2932 --- packages/wasm-solana/Cargo.lock | 1852 ++++++++++++++++- packages/wasm-solana/Cargo.toml | 1 + packages/wasm-solana/js/parser.ts | 32 + .../wasm-solana/src/instructions/decode.rs | 82 + .../wasm-solana/src/instructions/types.rs | 60 + packages/wasm-solana/test/bitgojs-compat.ts | 34 + 6 files changed, 1970 insertions(+), 91 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..fb4a64e 100644 --- a/packages/wasm-solana/Cargo.toml +++ b/packages/wasm-solana/Cargo.toml @@ -28,6 +28,7 @@ bincode = "1.3" borsh = "1.5" base64 = "0.22" serde-wasm-bindgen = "0.6" +spl-stake-pool = { version = "2.0.3", features = ["no-entrypoint"] } [dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/packages/wasm-solana/js/parser.ts b/packages/wasm-solana/js/parser.ts index 0894438..ce61cb5 100644 --- a/packages/wasm-solana/js/parser.ts +++ b/packages/wasm-solana/js/parser.ts @@ -132,6 +132,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; @@ -164,6 +194,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 5a705a2..34771ed 100644 --- a/packages/wasm-solana/src/instructions/decode.rs +++ b/packages/wasm-solana/src/instructions/decode.rs @@ -4,6 +4,7 @@ use super::types::*; use solana_compute_budget_interface::ComputeBudgetInstruction; use solana_stake_interface::instruction::StakeInstruction; use solana_system_interface::instruction::SystemInstruction; +use spl_stake_pool::instruction::StakePoolInstruction; /// Context for decoding an instruction - provides account addresses. pub struct InstructionContext<'a> { @@ -21,6 +22,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), } } @@ -320,6 +322,86 @@ fn decode_ata_instruction(ctx: InstructionContext) -> ParsedInstruction { } } +// ============================================================================= +// Stake Pool Program Decoding (Jito liquid staking) +// ============================================================================= + +fn decode_stake_pool_instruction(ctx: InstructionContext) -> ParsedInstruction { + use borsh::BorshDeserialize; + + let Ok(instr) = StakePoolInstruction::try_from_slice(ctx.data) else { + return make_unknown(ctx); + }; + + match instr { + StakePoolInstruction::DepositSol(lamports) => { + // DepositSol: deposit SOL into stake pool, receive pool tokens + // Accounts: + // [0] stakePool + // [1] withdrawAuthority + // [2] reserveStake + // [3] fundingAccount (signer) + // [4] destinationPoolAccount + // [5] managerFeeAccount + // [6] referralPoolAccount + // [7] poolMint + // [8] systemProgram + // [9] tokenProgram + // [10] depositAuthority (optional) + if ctx.accounts.len() >= 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) + } + } + StakePoolInstruction::WithdrawStake(pool_tokens) => { + // WithdrawStake: withdraw stake from pool by burning pool tokens + // 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 { + 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 26869c7..121e852 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)] @@ -50,6 +51,10 @@ pub enum ParsedInstruction { // Memo Memo(MemoParams), + // Stake Pool (Jito liquid staking) instructions + StakePoolDepositSol(StakePoolDepositSolParams), + StakePoolWithdrawStake(StakePoolWithdrawStakeParams), + // Fallback for unknown/custom instructions Unknown(UnknownInstructionParams), } @@ -231,6 +236,61 @@ pub struct CloseAtaParams { pub authority_address: String, } +// ============================================================================= +// Stake Pool (Jito) Params +// ============================================================================= + +/// Parameters for DepositSol instruction in stake pool (Jito liquid staking). +/// Discriminator: 14 +#[derive(Debug, Clone, Serialize)] +pub struct StakePoolDepositSolParams { + #[serde(rename = "stakePool")] + pub stake_pool: String, + #[serde(rename = "withdrawAuthority")] + pub withdraw_authority: String, + #[serde(rename = "reserveStake")] + pub reserve_stake: String, + #[serde(rename = "fundingAccount")] + pub funding_account: String, + #[serde(rename = "destinationPoolAccount")] + pub destination_pool_account: String, + #[serde(rename = "managerFeeAccount")] + pub manager_fee_account: String, + #[serde(rename = "referralPoolAccount")] + pub referral_pool_account: String, + #[serde(rename = "poolMint")] + pub pool_mint: String, + pub lamports: String, +} + +/// Parameters for WithdrawStake instruction in stake pool (Jito liquid staking). +/// Discriminator: 10 +#[derive(Debug, Clone, Serialize)] +pub struct StakePoolWithdrawStakeParams { + #[serde(rename = "stakePool")] + pub stake_pool: String, + #[serde(rename = "validatorList")] + pub validator_list: String, + #[serde(rename = "withdrawAuthority")] + pub withdraw_authority: String, + #[serde(rename = "validatorStake")] + pub validator_stake: String, + #[serde(rename = "destinationStake")] + pub destination_stake: String, + #[serde(rename = "destinationStakeAuthority")] + pub destination_stake_authority: String, + #[serde(rename = "sourceTransferAuthority")] + pub source_transfer_authority: String, + #[serde(rename = "sourcePoolAccount")] + pub source_pool_account: String, + #[serde(rename = "managerFeeAccount")] + pub manager_fee_account: String, + #[serde(rename = "poolMint")] + pub pool_mint: String, + #[serde(rename = "poolTokens")] + pub pool_tokens: String, +} + // ============================================================================= // Memo & Unknown // ============================================================================= diff --git a/packages/wasm-solana/test/bitgojs-compat.ts b/packages/wasm-solana/test/bitgojs-compat.ts index f5bce96..cd00a43 100644 --- a/packages/wasm-solana/test/bitgojs-compat.ts +++ b/packages/wasm-solana/test/bitgojs-compat.ts @@ -266,4 +266,38 @@ describe("BitGoJS Compatibility", () => { } }); }); + + describe("Jito liquid staking transaction", () => { + // From BitGoJS: test/resources/sol.ts - JITO_STAKING_ACTIVATE_SIGNED_TX + // This is a Jito DepositSol instruction (discriminator 14) - deposits SOL into the Jito stake pool + const TX_BASE64 = + "AdOUrFCk9yyhi1iB1EfOOXHOeiaZGQnLRwnypt+be8r9lrYMx8w7/QTnithrqcuBApg1ctJAlJMxNZ925vMP2Q0BAAQKReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0Ecg6pe+BOG2OETfAVS9ftz6va1oE4onLBolJ2N+ZOOhJ6naP7fZEyKrpuOIYit0GvFUPv3Fsgiuc5jx3g9lS4fCeaj/uz5kDLhwd9rlyLcs2NOe440QJNrw0sMwcjrUh/80UHpgyyvEK2RdJXKDycbWyk81HAn6nNwB+1A6zmgvQSKPgjDtJW+F/RUJ9ib7FuAx+JpXBhk12dD2zm+00bWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABU5Z4kwFGooUp7HpeX8OEs36dJAhZlMZWmpRKm8WZgKwaBTtTK9ooXRnL9rIYDGmPoTqFe+h1EtyKT9tvbABZQBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnjMtr5L6vs6LY/96RABeX9/Zr6FYdWthxalfkEs7jQgQEICgUHAgABAwEEBgkJDuCTBAAAAAAA"; + + it("should parse Jito DepositSol instruction", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + // Find the StakePoolDepositSol instruction + const depositSolInstr = parsed.instructionsData.find((i) => i.type === "StakePoolDepositSol"); + assert.ok(depositSolInstr, "Should have StakePoolDepositSol instruction"); + + if (depositSolInstr && depositSolInstr.type === "StakePoolDepositSol") { + // Amount should be 300000 lamports (0x493e0 in little endian: e0 93 04 00 00 00 00 00) + assert.strictEqual(depositSolInstr.lamports, "300000"); + // Stake pool should be the Jito stake pool program + assert.ok(depositSolInstr.stakePool, "Should have stakePool"); + assert.ok(depositSolInstr.fundingAccount, "Should have fundingAccount"); + assert.ok(depositSolInstr.destinationPoolAccount, "Should have destinationPoolAccount"); + assert.ok(depositSolInstr.poolMint, "Should have poolMint"); + } + }); + + it("should have correct fee payer for Jito transaction", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + // Fee payer from BitGoJS tests + assert.strictEqual(parsed.feePayer, "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe"); + }); + }); }); From 27e79ce9ae6fd91c3fa92d9dd2f01bc4be502647 Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Tue, 20 Jan 2026 22:27:57 -0800 Subject: [PATCH 2/6] feat: add transaction building core infrastructure Implements the core transaction building functionality using Solana SDK: - types.rs: TransactionIntent, Nonce (blockhash/durable), Instruction variants - build.rs: Transaction builder using solana-sdk 3.x types - Uses SDK instruction builders: system_ix::transfer(), advance_nonce_account(), etc. - Uses ComputeBudgetInstruction for priority fees Supported instructions: - Transfer, CreateAccount, NonceAdvance, NonceInitialize - Allocate, Assign, Memo, ComputeBudget Ticket: BTC-2955 --- packages/wasm-solana/Cargo.lock | 747 ++++++++++++++++++++-- packages/wasm-solana/Cargo.toml | 14 +- packages/wasm-solana/js/builder.ts | 207 ++++++ packages/wasm-solana/js/index.ts | 19 + packages/wasm-solana/src/builder/build.rs | 330 ++++++++++ packages/wasm-solana/src/builder/mod.rs | 10 + packages/wasm-solana/src/builder/types.rs | 116 ++++ packages/wasm-solana/src/lib.rs | 3 +- packages/wasm-solana/src/wasm/builder.rs | 65 ++ packages/wasm-solana/src/wasm/mod.rs | 2 + packages/wasm-solana/test/builder.ts | 277 ++++++++ 11 files changed, 1739 insertions(+), 51 deletions(-) create mode 100644 packages/wasm-solana/js/builder.ts create mode 100644 packages/wasm-solana/src/builder/build.rs create mode 100644 packages/wasm-solana/src/builder/mod.rs create mode 100644 packages/wasm-solana/src/builder/types.rs create mode 100644 packages/wasm-solana/src/wasm/builder.rs create mode 100644 packages/wasm-solana/test/builder.ts diff --git a/packages/wasm-solana/Cargo.lock b/packages/wasm-solana/Cargo.lock index 6805a7b..370b2c9 100644 --- a/packages/wasm-solana/Cargo.lock +++ b/packages/wasm-solana/Cargo.lock @@ -85,6 +85,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.12.3" @@ -97,6 +103,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bincode" version = "1.3.3" @@ -322,6 +334,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.4.2" @@ -343,6 +361,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -405,6 +435,16 @@ dependencies = [ "syn 2.0.114", ] +[[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 = "derivation-path" version = "0.2.0" @@ -427,17 +467,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature 2.2.0", + "spki", +] + [[package]] name = "ed25519" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature", + "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", ] [[package]] @@ -447,19 +512,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek 3.2.0", - "ed25519", + "ed25519 1.5.3", "rand 0.7.3", "serde", "sha2 0.9.9", "zeroize", ] +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek 4.1.3", + "ed25519 2.2.3", + "rand_core 0.6.4", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + +[[package]] +name = "ed25519-dalek-bip32" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b49a684b133c4980d7ee783936af771516011c8cd15f429dbda77245e282f03" +dependencies = [ + "derivation-path", + "ed25519-dalek 2.2.0", + "hmac", + "sha2 0.10.9", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -472,6 +583,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -546,6 +667,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -572,6 +694,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "hashbrown" version = "0.13.2" @@ -657,6 +790,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.9", + "signature 2.2.0", +] + [[package]] name = "keccak" version = "0.1.5" @@ -909,6 +1056,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "polyval" version = "0.6.2" @@ -1055,6 +1212,16 @@ dependencies = [ "bitflags", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -1085,6 +1252,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.27" @@ -1210,6 +1391,22 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "smallvec" version = "1.15.1" @@ -1229,6 +1426,24 @@ dependencies = [ "solana-sdk-ids 2.2.1", ] +[[package]] +name = "solana-account" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e0ac2a81ae17e1b3570deb50242ab4cfde50b848b898f57288b6271cc7b71f" +dependencies = [ + "bincode", + "serde", + "serde_bytes", + "serde_derive", + "solana-account-info 3.1.0", + "solana-clock 3.0.0", + "solana-instruction-error", + "solana-pubkey 4.0.0", + "solana-sdk-ids 3.1.0", + "solana-sysvar 3.1.1", +] + [[package]] name = "solana-account-info" version = "2.3.0" @@ -1248,6 +1463,8 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc3397241392f5756925029acaa8515dc70fcbe3d8059d4885d7d6533baf64fd" dependencies = [ + "bincode", + "serde_core", "solana-address 2.0.0", "solana-program-error 3.0.0", "solana-program-memory 3.1.0", @@ -1268,8 +1485,13 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e37320fd2945c5d654b2c6210624a52d66c3f1f73b653ed211ab91a703b35bdd" dependencies = [ + "borsh 1.6.0", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", "five8 1.0.0", "five8_const 1.0.0", + "rand 0.8.5", "serde", "serde_derive", "solana-atomic-u64 3.0.0", @@ -1296,6 +1518,18 @@ dependencies = [ "solana-slot-hashes 2.2.1", ] +[[package]] +name = "solana-address-lookup-table-interface" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e8df0b083c10ce32490410f3795016b1b5d9b4d094658c0a5e496753645b7cd" +dependencies = [ + "solana-clock 3.0.0", + "solana-pubkey 4.0.0", + "solana-sdk-ids 3.1.0", + "solana-slot-hashes 3.0.0", +] + [[package]] name = "solana-atomic-u64" version = "2.2.1" @@ -1325,6 +1559,17 @@ dependencies = [ "solana-define-syscall 2.3.0", ] +[[package]] +name = "solana-big-mod-exp" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c80fb6d791b3925d5ec4bf23a7c169ef5090c013059ec3ed7d0b2c04efa085" +dependencies = [ + "num-bigint", + "num-traits", + "solana-define-syscall 3.0.0", +] + [[package]] name = "solana-bincode" version = "2.2.1" @@ -1348,6 +1593,17 @@ dependencies = [ "solana-sanitize 2.2.1", ] +[[package]] +name = "solana-blake3-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7116e1d942a2432ca3f514625104757ab8a56233787e95144c93950029e31176" +dependencies = [ + "blake3", + "solana-define-syscall 4.0.1", + "solana-hash 4.0.1", +] + [[package]] name = "solana-borsh" version = "2.2.1" @@ -1386,13 +1642,13 @@ dependencies = [ [[package]] name = "solana-compute-budget-interface" -version = "2.2.2" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8432d2c4c22d0499aa06d62e4f7e333f81777b3d7c96050ae9e5cb71a8c3aee4" +checksum = "8292c436b269ad23cecc8b24f7da3ab07ca111661e25e00ce0e1d22771951ab9" dependencies = [ "borsh 1.6.0", - "solana-instruction 2.3.3", - "solana-sdk-ids 2.2.1", + "solana-instruction 3.1.0", + "solana-sdk-ids 3.1.0", ] [[package]] @@ -1475,6 +1731,27 @@ dependencies = [ "uriparse", ] +[[package]] +name = "solana-derivation-path" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff71743072690fdbdfcdc37700ae1cb77485aaad49019473a81aee099b1e0b8c" +dependencies = [ + "derivation-path", + "qstring", + "uriparse", +] + +[[package]] +name = "solana-epoch-info" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e093c84f6ece620a6b10cd036574b0cd51944231ab32d81f80f76d54aba833e6" +dependencies = [ + "serde", + "serde_derive", +] + [[package]] name = "solana-epoch-rewards" version = "2.2.1" @@ -1503,6 +1780,17 @@ dependencies = [ "solana-sysvar-id 3.1.0", ] +[[package]] +name = "solana-epoch-rewards-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee8beac9bff4db9225e57d532d169b0be5e447f1e6601a2f50f27a01bf5518f" +dependencies = [ + "siphasher", + "solana-address 2.0.0", + "solana-hash 4.0.1", +] + [[package]] name = "solana-epoch-schedule" version = "2.2.1" @@ -1529,6 +1817,16 @@ dependencies = [ "solana-sysvar-id 3.1.0", ] +[[package]] +name = "solana-epoch-stake" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc6693d0ea833b880514b9b88d95afb80b42762dca98b0712465d1fcbbcb89e" +dependencies = [ + "solana-define-syscall 3.0.0", + "solana-pubkey 3.0.0", +] + [[package]] name = "solana-example-mocks" version = "2.2.1" @@ -1537,19 +1835,40 @@ checksum = "84461d56cbb8bb8d539347151e0525b53910102e4bced875d49d5139708e39d3" dependencies = [ "serde", "serde_derive", - "solana-address-lookup-table-interface", + "solana-address-lookup-table-interface 2.2.2", "solana-clock 2.2.2", "solana-hash 2.3.0", "solana-instruction 2.3.3", - "solana-keccak-hasher", + "solana-keccak-hasher 2.2.1", "solana-message 2.4.0", - "solana-nonce", + "solana-nonce 2.2.1", "solana-pubkey 2.4.0", "solana-sdk-ids 2.2.1", "solana-system-interface 1.0.0", "thiserror 2.0.18", ] +[[package]] +name = "solana-example-mocks" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978855d164845c1b0235d4b4d101cadc55373fffaf0b5b6cfa2194d25b2ed658" +dependencies = [ + "serde", + "serde_derive", + "solana-address-lookup-table-interface 3.0.1", + "solana-clock 3.0.0", + "solana-hash 3.1.0", + "solana-instruction 3.1.0", + "solana-keccak-hasher 3.1.0", + "solana-message 3.0.1", + "solana-nonce 3.0.0", + "solana-pubkey 3.0.0", + "solana-sdk-ids 3.1.0", + "solana-system-interface 2.0.0", + "thiserror 2.0.18", +] + [[package]] name = "solana-feature-gate-interface" version = "2.2.2" @@ -1559,7 +1878,7 @@ dependencies = [ "bincode", "serde", "serde_derive", - "solana-account", + "solana-account 2.2.1", "solana-account-info 2.3.0", "solana-instruction 2.3.3", "solana-program-error 2.2.2", @@ -1591,6 +1910,22 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "solana-fee-structure" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2abdb1223eea8ec64136f39cb1ffcf257e00f915c957c35c0dd9e3f4e700b0" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-hard-forks" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abacc4b66ce471f135f48f22facf75cbbb0f8a252fbe2c1e0aa59d5b203f519" + [[package]] name = "solana-hash" version = "2.3.0" @@ -1624,6 +1959,7 @@ version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a5d48a6ee7b91fc7b998944ab026ed7b3e2fc8ee3bc58452644a86c2648152f" dependencies = [ + "borsh 1.6.0", "bytemuck", "bytemuck_derive", "five8 1.0.0", @@ -1633,6 +1969,16 @@ dependencies = [ "solana-sanitize 3.0.1", ] +[[package]] +name = "solana-inflation" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e92f37a14e7c660628752833250dd3dcd8e95309876aee751d7f8769a27947c6" +dependencies = [ + "serde", + "serde_derive", +] + [[package]] name = "solana-instruction" version = "2.3.3" @@ -1692,10 +2038,28 @@ dependencies = [ "solana-pubkey 2.4.0", "solana-sanitize 2.2.1", "solana-sdk-ids 2.2.1", - "solana-serialize-utils", + "solana-serialize-utils 2.2.1", "solana-sysvar-id 2.2.1", ] +[[package]] +name = "solana-instructions-sysvar" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddf67876c541aa1e21ee1acae35c95c6fbc61119814bfef70579317a5e26955" +dependencies = [ + "bitflags", + "solana-account-info 3.1.0", + "solana-instruction 3.1.0", + "solana-instruction-error", + "solana-program-error 3.0.0", + "solana-pubkey 3.0.0", + "solana-sanitize 3.0.1", + "solana-sdk-ids 3.1.0", + "solana-serialize-utils 3.1.0", + "solana-sysvar-id 3.1.0", +] + [[package]] name = "solana-keccak-hasher" version = "2.2.1" @@ -1708,22 +2072,51 @@ dependencies = [ "solana-sanitize 2.2.1", ] +[[package]] +name = "solana-keccak-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed1c0d16d6fdeba12291a1f068cdf0d479d9bff1141bf44afd7aa9d485f65ef8" +dependencies = [ + "sha3", + "solana-define-syscall 4.0.1", + "solana-hash 4.0.1", +] + [[package]] name = "solana-keypair" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd3f04aa1a05c535e93e121a95f66e7dcccf57e007282e8255535d24bf1e98bb" dependencies = [ - "ed25519-dalek", + "ed25519-dalek 1.0.1", "five8 0.2.1", "rand 0.7.3", "solana-pubkey 2.4.0", - "solana-seed-phrase", + "solana-seed-phrase 2.2.1", "solana-signature 2.3.0", "solana-signer 2.2.1", "wasm-bindgen", ] +[[package]] +name = "solana-keypair" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8be597c9e231b0cab2928ce3bc3e4ee77d9c0ad92977b9d901f3879f25a7a" +dependencies = [ + "ed25519-dalek 2.2.0", + "ed25519-dalek-bip32", + "five8 1.0.0", + "rand 0.8.5", + "solana-address 2.0.0", + "solana-derivation-path 3.0.0", + "solana-seed-derivable 3.0.0", + "solana-seed-phrase 3.0.0", + "solana-signature 3.1.0", + "solana-signer 3.0.0", +] + [[package]] name = "solana-last-restart-slot" version = "2.2.1" @@ -1824,6 +2217,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85666605c9fd727f865ed381665db0a8fc29f984a030ecc1e40f43bfb2541623" dependencies = [ "bincode", + "blake3", "lazy_static", "serde", "serde_derive", @@ -1860,6 +2254,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61515b880c36974053dd499c0510066783f0cc6ac17def0c7ef2a244874cf4a9" +[[package]] +name = "solana-native-token" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8dd4c280dca9d046139eb5b7a5ac9ad10403fbd64964c7d7571214950d758f" + [[package]] name = "solana-nonce" version = "2.2.1" @@ -1874,6 +2274,54 @@ dependencies = [ "solana-sha256-hasher 2.3.0", ] +[[package]] +name = "solana-nonce" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abbdc6c8caf1c08db9f36a50967539d0f72b9f1d4aea04fec5430f532e5afadc" +dependencies = [ + "solana-fee-calculator 3.0.0", + "solana-hash 3.1.0", + "solana-pubkey 3.0.0", + "solana-sha256-hasher 3.1.0", +] + +[[package]] +name = "solana-offchain-message" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e2a1141a673f72a05cf406b99e4b2b8a457792b7c01afa07b3f00d4e2de393" +dependencies = [ + "num_enum", + "solana-hash 3.1.0", + "solana-packet", + "solana-pubkey 3.0.0", + "solana-sanitize 3.0.1", + "solana-sha256-hasher 3.1.0", + "solana-signature 3.1.0", + "solana-signer 3.0.0", +] + +[[package]] +name = "solana-packet" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edf2f25743c95229ac0fdc32f8f5893ef738dbf332c669e9861d33ddb0f469d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "solana-presigner" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f704eaf825be3180832445b9e4983b875340696e8e7239bf2d535b0f86c14a2" +dependencies = [ + "solana-pubkey 3.0.0", + "solana-signature 3.1.0", + "solana-signer 3.0.0", +] + [[package]] name = "solana-program" version = "2.3.0" @@ -1900,11 +2348,11 @@ dependencies = [ "serde_bytes", "serde_derive", "solana-account-info 2.3.0", - "solana-address-lookup-table-interface", + "solana-address-lookup-table-interface 2.2.2", "solana-atomic-u64 2.2.1", - "solana-big-mod-exp", + "solana-big-mod-exp 2.2.1", "solana-bincode", - "solana-blake3-hasher", + "solana-blake3-hasher 2.2.1", "solana-borsh", "solana-clock 2.2.2", "solana-cpi 2.2.1", @@ -1912,34 +2360,34 @@ dependencies = [ "solana-define-syscall 2.3.0", "solana-epoch-rewards 2.2.1", "solana-epoch-schedule 2.2.1", - "solana-example-mocks", + "solana-example-mocks 2.2.1", "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-instructions-sysvar 2.2.2", + "solana-keccak-hasher 2.2.1", "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-native-token 2.3.0", + "solana-nonce 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-program-option 2.2.1", + "solana-program-pack 2.2.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-secp256k1-recover", - "solana-serde-varint", - "solana-serialize-utils", + "solana-secp256k1-recover 2.2.1", + "solana-serde-varint 2.2.2", + "solana-serialize-utils 2.2.1", "solana-sha256-hasher 2.3.0", "solana-short-vec 2.2.1", "solana-slot-hashes 2.2.1", @@ -1954,6 +2402,52 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "solana-program" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91b12305dd81045d705f427acd0435a2e46444b65367d7179d7bdcfc3bc5f5eb" +dependencies = [ + "memoffset", + "solana-account-info 3.1.0", + "solana-big-mod-exp 3.0.0", + "solana-blake3-hasher 3.1.0", + "solana-clock 3.0.0", + "solana-cpi 3.1.0", + "solana-define-syscall 3.0.0", + "solana-epoch-rewards 3.0.0", + "solana-epoch-schedule 3.0.0", + "solana-epoch-stake", + "solana-example-mocks 3.0.0", + "solana-fee-calculator 3.0.0", + "solana-hash 3.1.0", + "solana-instruction 3.1.0", + "solana-instruction-error", + "solana-instructions-sysvar 3.0.0", + "solana-keccak-hasher 3.1.0", + "solana-last-restart-slot 3.0.0", + "solana-msg 3.0.0", + "solana-native-token 3.0.0", + "solana-program-entrypoint 3.1.1", + "solana-program-error 3.0.0", + "solana-program-memory 3.1.0", + "solana-program-option 3.0.0", + "solana-program-pack 3.0.0", + "solana-pubkey 3.0.0", + "solana-rent 3.1.0", + "solana-sdk-ids 3.1.0", + "solana-secp256k1-recover 3.1.0", + "solana-serde-varint 3.0.0", + "solana-serialize-utils 3.1.0", + "solana-sha256-hasher 3.1.0", + "solana-short-vec 3.1.0", + "solana-slot-hashes 3.0.0", + "solana-slot-history 3.0.0", + "solana-stable-layout 3.0.0", + "solana-sysvar 3.1.1", + "solana-sysvar-id 3.1.0", +] + [[package]] name = "solana-program-entrypoint" version = "2.3.0" @@ -1999,6 +2493,10 @@ name = "solana-program-error" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1af32c995a7b692a915bb7414d5f8e838450cf7c70414e763d8abcae7b51f28" +dependencies = [ + "serde", + "serde_derive", +] [[package]] name = "solana-program-memory" @@ -2024,6 +2522,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc677a2e9bc616eda6dbdab834d463372b92848b2bfe4a1ed4e4b4adba3397d0" +[[package]] +name = "solana-program-option" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7b4ddb464f274deb4a497712664c3b612e3f5f82471d4e47710fc4ab1c3095" + [[package]] name = "solana-program-pack" version = "2.2.1" @@ -2033,6 +2537,15 @@ dependencies = [ "solana-program-error 2.2.2", ] +[[package]] +name = "solana-program-pack" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c169359de21f6034a63ebf96d6b380980307df17a8d371344ff04a883ec4e9d0" +dependencies = [ + "solana-program-error 3.0.0", +] + [[package]] name = "solana-pubkey" version = "2.4.0" @@ -2065,6 +2578,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8909d399deb0851aa524420beeb5646b115fd253ef446e35fe4504c904da3941" dependencies = [ + "rand 0.8.5", "solana-address 1.1.0", ] @@ -2115,6 +2629,44 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" +[[package]] +name = "solana-sdk" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f03df7969f5e723ad31b6c9eadccc209037ac4caa34d8dc259316b05c11e82b" +dependencies = [ + "bincode", + "bs58", + "serde", + "solana-account 3.3.0", + "solana-epoch-info", + "solana-epoch-rewards-hasher", + "solana-fee-structure", + "solana-inflation", + "solana-keypair 3.1.0", + "solana-message 3.0.1", + "solana-offchain-message", + "solana-presigner", + "solana-program 3.0.0", + "solana-program-memory 3.1.0", + "solana-pubkey 3.0.0", + "solana-sanitize 3.0.1", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.0", + "solana-seed-derivable 3.0.0", + "solana-seed-phrase 3.0.0", + "solana-serde", + "solana-serde-varint 3.0.0", + "solana-short-vec 3.1.0", + "solana-shred-version", + "solana-signature 3.1.0", + "solana-signer 3.0.0", + "solana-time-utils", + "solana-transaction", + "solana-transaction-error 3.0.0", + "thiserror 2.0.18", +] + [[package]] name = "solana-sdk-ids" version = "2.2.1" @@ -2168,6 +2720,17 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "solana-secp256k1-recover" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de18cfdab99eeb940fbedd8c981fa130c0d76252da75d05446f22fae8b51932" +dependencies = [ + "k256", + "solana-define-syscall 4.0.1", + "thiserror 2.0.18", +] + [[package]] name = "solana-security-txt" version = "1.1.2" @@ -2183,7 +2746,16 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beb82b5adb266c6ea90e5cf3967235644848eac476c5a1f2f9283a143b7c97f" dependencies = [ - "solana-derivation-path", + "solana-derivation-path 2.2.1", +] + +[[package]] +name = "solana-seed-derivable" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7bdb72758e3bec33ed0e2658a920f1f35dfb9ed576b951d20d63cb61ecd95c" +dependencies = [ + "solana-derivation-path 3.0.0", ] [[package]] @@ -2197,6 +2769,26 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "solana-seed-phrase" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc905b200a95f2ea9146e43f2a7181e3aeb55de6bc12afb36462d00a3c7310de" +dependencies = [ + "hmac", + "pbkdf2", + "sha2 0.10.9", +] + +[[package]] +name = "solana-serde" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709a93cab694c70f40b279d497639788fc2ccbcf9b4aa32273d4b361322c02dd" +dependencies = [ + "serde", +] + [[package]] name = "solana-serde-varint" version = "2.2.2" @@ -2206,6 +2798,15 @@ dependencies = [ "serde", ] +[[package]] +name = "solana-serde-varint" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5174c57d5ff3c1995f274d17156964664566e2cde18a07bba1586d35a70d3b" +dependencies = [ + "serde", +] + [[package]] name = "solana-serialize-utils" version = "2.2.1" @@ -2217,6 +2818,17 @@ dependencies = [ "solana-sanitize 2.2.1", ] +[[package]] +name = "solana-serialize-utils" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e41dd8feea239516c623a02f0a81c2367f4b604d7965237fed0751aeec33ed" +dependencies = [ + "solana-instruction-error", + "solana-pubkey 3.0.0", + "solana-sanitize 3.0.1", +] + [[package]] name = "solana-sha256-hasher" version = "2.3.0" @@ -2257,13 +2869,24 @@ dependencies = [ "serde_core", ] +[[package]] +name = "solana-shred-version" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94953e22ca28fe4541a3447d6baeaf519cc4ddc063253bfa673b721f34c136bb" +dependencies = [ + "solana-hard-forks", + "solana-hash 3.1.0", + "solana-sha256-hasher 3.1.0", +] + [[package]] name = "solana-signature" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64c8ec8e657aecfc187522fc67495142c12f35e55ddeca8698edbb738b8dbd8c" dependencies = [ - "ed25519-dalek", + "ed25519-dalek 1.0.1", "five8 0.2.1", "solana-sanitize 2.2.1", ] @@ -2274,7 +2897,9 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bb8057cc0e9f7b5e89883d49de6f407df655bb6f3a71d0b7baf9986a2218fd9" dependencies = [ + "ed25519-dalek 2.2.0", "five8 0.2.1", + "rand 0.8.5", "serde", "serde-big-array", "serde_derive", @@ -2445,6 +3070,21 @@ dependencies = [ "solana-pubkey 3.0.0", ] +[[package]] +name = "solana-system-interface" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14591d6508042ebefb110305d3ba761615927146a26917ade45dc332d8e1ecde" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-address 2.0.0", + "solana-instruction 3.1.0", + "solana-msg 3.0.0", + "solana-program-error 3.0.0", +] + [[package]] name = "solana-sysvar" version = "2.3.0" @@ -2466,7 +3106,7 @@ dependencies = [ "solana-fee-calculator 2.2.1", "solana-hash 2.3.0", "solana-instruction 2.3.3", - "solana-instructions-sysvar", + "solana-instructions-sysvar 2.2.2", "solana-last-restart-slot 2.2.1", "solana-program-entrypoint 2.3.0", "solana-program-error 2.2.2", @@ -2490,6 +3130,8 @@ checksum = "6690d3dd88f15c21edff68eb391ef8800df7a1f5cec84ee3e8d1abf05affdf74" dependencies = [ "base64 0.22.1", "bincode", + "bytemuck", + "bytemuck_derive", "lazy_static", "serde", "serde_derive", @@ -2534,6 +3176,12 @@ dependencies = [ "solana-sdk-ids 3.1.0", ] +[[package]] +name = "solana-time-utils" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced92c60aa76ec4780a9d93f3bd64dfa916e1b998eacc6f1c110f3f444f02c9" + [[package]] name = "solana-transaction" version = "3.0.2" @@ -2596,8 +3244,8 @@ dependencies = [ "solana-pubkey 2.4.0", "solana-rent 2.2.1", "solana-sdk-ids 2.2.1", - "solana-serde-varint", - "solana-serialize-utils", + "solana-serde-varint 2.2.2", + "solana-serialize-utils 2.2.1", "solana-short-vec 2.2.1", "solana-system-interface 1.0.0", ] @@ -2624,12 +3272,12 @@ dependencies = [ "serde_derive", "serde_json", "sha3", - "solana-derivation-path", + "solana-derivation-path 2.2.1", "solana-instruction 2.3.3", "solana-pubkey 2.4.0", "solana-sdk-ids 2.2.1", - "solana-seed-derivable", - "solana-seed-phrase", + "solana-seed-derivable 2.2.1", + "solana-seed-phrase 2.2.1", "solana-signature 2.3.0", "solana-signer 2.2.1", "subtle", @@ -2638,6 +3286,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "spl-discriminator" version = "0.4.1" @@ -2726,7 +3384,7 @@ dependencies = [ "solana-decode-error", "solana-msg 2.2.1", "solana-program-error 2.2.2", - "solana-program-option", + "solana-program-option 2.2.1", "solana-pubkey 2.4.0", "solana-zk-sdk", "thiserror 2.0.18", @@ -2774,7 +3432,7 @@ dependencies = [ "num_enum", "serde", "serde_derive", - "solana-program", + "solana-program 2.3.0", "solana-security-txt", "solana-stake-interface 1.2.1", "solana-system-interface 1.0.0", @@ -2824,8 +3482,8 @@ dependencies = [ "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-program-option 2.2.1", + "solana-program-pack 2.2.1", "solana-pubkey 2.4.0", "solana-rent 2.2.1", "solana-sdk-ids 2.2.1", @@ -2850,12 +3508,12 @@ dependencies = [ "solana-decode-error", "solana-instruction 2.3.3", "solana-msg 2.2.1", - "solana-native-token", + "solana-native-token 2.3.0", "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-program-option 2.2.1", + "solana-program-pack 2.2.1", "solana-pubkey 2.4.0", "solana-rent 2.2.1", "solana-sdk-ids 2.2.1", @@ -2899,7 +3557,7 @@ dependencies = [ "solana-account-info 2.3.0", "solana-curve25519", "solana-instruction 2.3.3", - "solana-instructions-sysvar", + "solana-instructions-sysvar 2.2.2", "solana-msg 2.2.1", "solana-program-error 2.2.2", "solana-pubkey 2.4.0", @@ -3288,11 +3946,12 @@ dependencies = [ "serde-wasm-bindgen", "serde_json", "solana-compute-budget-interface", - "solana-keypair", + "solana-keypair 2.2.3", "solana-pubkey 2.4.0", + "solana-sdk", "solana-signer 2.2.1", "solana-stake-interface 2.0.2", - "solana-system-interface 2.0.0", + "solana-system-interface 3.0.0", "solana-transaction", "spl-stake-pool", "wasm-bindgen", diff --git a/packages/wasm-solana/Cargo.toml b/packages/wasm-solana/Cargo.toml index fb4a64e..c8f84de 100644 --- a/packages/wasm-solana/Cargo.toml +++ b/packages/wasm-solana/Cargo.toml @@ -14,20 +14,22 @@ wasm-bindgen = "0.2" js-sys = "0.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -# Solana SDK crates +# Solana 3.x crates (for transaction building) +solana-sdk = { version = "3.0", default-features = false, features = ["full"] } +solana-transaction = { version = "3.0", features = ["serde", "bincode"] } +solana-system-interface = { version = "3.0", features = ["bincode"] } +solana-compute-budget-interface = { version = "3.0", features = ["borsh"] } +# Solana 2.x crates (no 3.x available yet for these) +solana-stake-interface = { version = "2.0", features = ["bincode"] } solana-pubkey = { version = "2.0", features = ["curve25519"] } solana-keypair = "2.0" solana-signer = "2.0" -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" +# SPL Stake Pool for Jito decoding spl-stake-pool = { version = "2.0.3", features = ["no-entrypoint"] } [dev-dependencies] diff --git a/packages/wasm-solana/js/builder.ts b/packages/wasm-solana/js/builder.ts new file mode 100644 index 0000000..6e6c14a --- /dev/null +++ b/packages/wasm-solana/js/builder.ts @@ -0,0 +1,207 @@ +/** + * Transaction building from high-level intents. + * + * Provides types and functions for building Solana transactions from a + * declarative intent structure, without requiring the full @solana/web3.js dependency. + */ + +import { BuilderNamespace } from "./wasm/wasm_solana.js"; + +// ============================================================================= +// Nonce Types +// ============================================================================= + +/** Use a recent blockhash for the transaction */ +export interface BlockhashNonceSource { + type: "blockhash"; + /** The recent blockhash value (base58) */ + value: string; +} + +/** Use a durable nonce account for the transaction */ +export interface DurableNonceSource { + type: "durable"; + /** The nonce account address (base58) */ + address: string; + /** The nonce authority address (base58) */ + authority: string; + /** The nonce value stored in the account (base58) - this becomes the blockhash */ + value: string; +} + +/** Nonce source for the transaction */ +export type NonceSource = BlockhashNonceSource | DurableNonceSource; + +// ============================================================================= +// Instruction Types +// ============================================================================= + +/** SOL transfer instruction */ +export interface TransferInstruction { + type: "transfer"; + /** Source account (base58) */ + from: string; + /** Destination account (base58) */ + to: string; + /** Amount in lamports (as string for BigInt compatibility) */ + lamports: string; +} + +/** Create new account instruction */ +export interface CreateAccountInstruction { + type: "createAccount"; + /** Funding account (base58) */ + from: string; + /** New account address (base58) */ + newAccount: string; + /** Lamports to transfer (as string) */ + lamports: string; + /** Space to allocate in bytes */ + space: number; + /** Owner program (base58) */ + owner: string; +} + +/** Advance durable nonce instruction */ +export interface NonceAdvanceInstruction { + type: "nonceAdvance"; + /** Nonce account address (base58) */ + nonce: string; + /** Nonce authority (base58) */ + authority: string; +} + +/** Initialize nonce account instruction */ +export interface NonceInitializeInstruction { + type: "nonceInitialize"; + /** Nonce account address (base58) */ + nonce: string; + /** Nonce authority (base58) */ + authority: string; +} + +/** Allocate space instruction */ +export interface AllocateInstruction { + type: "allocate"; + /** Account to allocate (base58) */ + account: string; + /** Space to allocate in bytes */ + space: number; +} + +/** Assign account to program instruction */ +export interface AssignInstruction { + type: "assign"; + /** Account to assign (base58) */ + account: string; + /** New owner program (base58) */ + owner: string; +} + +/** Memo instruction */ +export interface MemoInstruction { + type: "memo"; + /** The memo message */ + message: string; +} + +/** Compute budget instruction */ +export interface ComputeBudgetInstruction { + type: "computeBudget"; + /** Compute unit limit (optional) */ + unitLimit?: number; + /** Compute unit price in micro-lamports (optional) */ + unitPrice?: number; +} + +/** Union of all instruction types */ +export type Instruction = + | TransferInstruction + | CreateAccountInstruction + | NonceAdvanceInstruction + | NonceInitializeInstruction + | AllocateInstruction + | AssignInstruction + | MemoInstruction + | ComputeBudgetInstruction; + +// ============================================================================= +// TransactionIntent +// ============================================================================= + +/** + * A declarative intent to build a Solana transaction. + * + * @example + * ```typescript + * const intent: TransactionIntent = { + * feePayer: 'DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB', + * nonce: { + * type: 'blockhash', + * value: 'GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4' + * }, + * instructions: [ + * { type: 'transfer', from: '...', to: '...', lamports: '1000000' } + * ] + * }; + * ``` + */ +export interface TransactionIntent { + /** The fee payer's public key (base58) */ + feePayer: string; + /** The nonce source (blockhash or durable nonce) */ + nonce: NonceSource; + /** List of instructions to include */ + instructions: Instruction[]; +} + +// ============================================================================= +// buildTransaction function +// ============================================================================= + +/** + * Build a Solana transaction from a high-level intent. + * + * This function takes a declarative TransactionIntent and produces serialized + * transaction bytes that can be signed and submitted to the network. + * + * The returned transaction is unsigned - signatures should be added before + * broadcasting. + * + * @param intent - The transaction intent describing what to build + * @returns Serialized unsigned transaction bytes (Uint8Array) + * @throws Error if the intent cannot be built (e.g., invalid addresses) + * + * @example + * ```typescript + * import { buildTransaction } from '@bitgo/wasm-solana'; + * + * // Build a simple SOL transfer + * const txBytes = buildTransaction({ + * feePayer: sender, + * nonce: { type: 'blockhash', value: blockhash }, + * instructions: [ + * { type: 'transfer', from: sender, to: recipient, lamports: '1000000' } + * ] + * }); + * + * // The returned bytes can be signed and broadcast + * ``` + * + * @example + * ```typescript + * // Build with durable nonce and priority fee + * const txBytes = buildTransaction({ + * feePayer: sender, + * nonce: { type: 'durable', address: nonceAccount, authority: sender, value: nonceValue }, + * instructions: [ + * { type: 'computeBudget', unitLimit: 200000, unitPrice: 5000 }, + * { type: 'transfer', from: sender, to: recipient, lamports: '1000000' }, + * { type: 'memo', message: 'BitGo transfer' } + * ] + * }); + * ``` + */ +export function buildTransaction(intent: TransactionIntent): Uint8Array { + return BuilderNamespace.build_transaction(intent); +} diff --git a/packages/wasm-solana/js/index.ts b/packages/wasm-solana/js/index.ts index 66d8bc1..d85a4e2 100644 --- a/packages/wasm-solana/js/index.ts +++ b/packages/wasm-solana/js/index.ts @@ -8,6 +8,7 @@ export * as keypair from "./keypair.js"; export * as pubkey from "./pubkey.js"; export * as transaction from "./transaction.js"; export * as parser from "./parser.js"; +export * as builder from "./builder.js"; // Top-level class exports for convenience export { Keypair } from "./keypair.js"; @@ -16,6 +17,7 @@ export { Transaction } from "./transaction.js"; // Top-level function exports export { parseTransaction } from "./parser.js"; +export { buildTransaction } from "./builder.js"; // Type exports export type { AccountMeta, Instruction } from "./transaction.js"; @@ -40,3 +42,20 @@ export type { MemoParams, UnknownInstructionParams, } from "./parser.js"; + +// Builder type exports (prefixed to avoid conflict with parser/transaction types) +export type { + TransactionIntent, + NonceSource, + BlockhashNonceSource, + DurableNonceSource, + Instruction as BuilderInstruction, + TransferInstruction, + CreateAccountInstruction, + NonceAdvanceInstruction, + NonceInitializeInstruction, + AllocateInstruction, + AssignInstruction, + MemoInstruction, + ComputeBudgetInstruction, +} from "./builder.js"; diff --git a/packages/wasm-solana/src/builder/build.rs b/packages/wasm-solana/src/builder/build.rs new file mode 100644 index 0000000..5015d22 --- /dev/null +++ b/packages/wasm-solana/src/builder/build.rs @@ -0,0 +1,330 @@ +//! Transaction building implementation. +//! +//! Uses the Solana SDK for transaction construction and serialization. + +use crate::error::WasmSolanaError; + +use super::types::{Instruction as IntentInstruction, Nonce, TransactionIntent}; + +// Use SDK types for building (3.x ecosystem) +use solana_sdk::hash::Hash; +use solana_sdk::instruction::{AccountMeta, Instruction}; +use solana_sdk::message::Message; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::transaction::Transaction; +use solana_compute_budget_interface::ComputeBudgetInstruction; +use solana_system_interface::instruction::{self as system_ix, SystemInstruction}; + +/// Well-known program IDs +mod program_ids { + use super::Pubkey; + + pub fn memo_program() -> Pubkey { + "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" + .parse() + .unwrap() + } +} + +/// Build a transaction from an intent structure. +/// +/// Returns the serialized unsigned transaction (wire format). +pub fn build_transaction(intent: TransactionIntent) -> Result, WasmSolanaError> { + // Parse fee payer + let fee_payer: Pubkey = intent + .fee_payer + .parse() + .map_err(|_| WasmSolanaError::new(&format!("Invalid fee_payer: {}", intent.fee_payer)))?; + + // Build all instructions + let mut instructions: Vec = Vec::new(); + + // Handle nonce - either blockhash or durable nonce + let blockhash_str = match &intent.nonce { + Nonce::Blockhash { value } => value.clone(), + Nonce::Durable { + address, + authority, + value, + } => { + // For durable nonce, prepend the nonce advance instruction + let nonce_pubkey: Pubkey = address.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid nonce.address: {}", address)) + })?; + let authority_pubkey: Pubkey = authority.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid nonce.authority: {}", authority)) + })?; + instructions.push(system_ix::advance_nonce_account(&nonce_pubkey, &authority_pubkey)); + + // The blockhash is the nonce value stored in the nonce account + value.clone() + } + }; + + // Parse blockhash + let blockhash: Hash = blockhash_str + .parse() + .map_err(|_| WasmSolanaError::new(&format!("Invalid blockhash: {}", blockhash_str)))?; + + // Build each instruction + for ix in intent.instructions { + instructions.push(build_instruction(ix)?); + } + + // Create message using SDK (handles account ordering correctly) + let message = Message::new_with_blockhash(&instructions, Some(&fee_payer), &blockhash); + + // Create unsigned transaction + let mut tx = Transaction::new_unsigned(message); + tx.message.recent_blockhash = blockhash; + + // Serialize using bincode (standard Solana serialization) + let tx_bytes = + bincode::serialize(&tx).map_err(|e| WasmSolanaError::new(&format!("Serialize: {}", e)))?; + + Ok(tx_bytes) +} + +/// Build a single instruction from the IntentInstruction enum. +fn build_instruction(ix: IntentInstruction) -> Result { + match ix { + // ===== System Program ===== + IntentInstruction::Transfer { from, to, lamports } => { + let from_pubkey: Pubkey = from + .parse() + .map_err(|_| WasmSolanaError::new(&format!("Invalid transfer.from: {}", from)))?; + let to_pubkey: Pubkey = to + .parse() + .map_err(|_| WasmSolanaError::new(&format!("Invalid transfer.to: {}", to)))?; + let amount: u64 = lamports.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid transfer.lamports: {}", lamports)) + })?; + Ok(system_ix::transfer(&from_pubkey, &to_pubkey, amount)) + } + + IntentInstruction::CreateAccount { + from, + new_account, + lamports, + space, + owner, + } => { + let from_pubkey: Pubkey = from.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid createAccount.from: {}", from)) + })?; + let new_pubkey: Pubkey = new_account.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid createAccount.newAccount: {}", new_account)) + })?; + let owner_pubkey: Pubkey = owner.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid createAccount.owner: {}", owner)) + })?; + let amount: u64 = lamports.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid createAccount.lamports: {}", lamports)) + })?; + Ok(system_ix::create_account( + &from_pubkey, + &new_pubkey, + amount, + space, + &owner_pubkey, + )) + } + + IntentInstruction::NonceAdvance { nonce, authority } => { + let nonce_pubkey: Pubkey = nonce.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid nonceAdvance.nonce: {}", nonce)) + })?; + let authority_pubkey: Pubkey = authority.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid nonceAdvance.authority: {}", authority)) + })?; + Ok(system_ix::advance_nonce_account( + &nonce_pubkey, + &authority_pubkey, + )) + } + + IntentInstruction::NonceInitialize { nonce, authority } => { + // Note: In SDK 3.x, nonce initialization is combined with creation. + // This creates an InitializeNonceAccount instruction manually. + let nonce_pubkey: Pubkey = nonce.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid nonceInitialize.nonce: {}", nonce)) + })?; + let authority_pubkey: Pubkey = authority.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid nonceInitialize.authority: {}", authority)) + })?; + Ok(build_nonce_initialize(&nonce_pubkey, &authority_pubkey)) + } + + IntentInstruction::Allocate { account, space } => { + let account_pubkey: Pubkey = account.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid allocate.account: {}", account)) + })?; + Ok(system_ix::allocate(&account_pubkey, space)) + } + + IntentInstruction::Assign { account, owner } => { + let account_pubkey: Pubkey = account.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid assign.account: {}", account)) + })?; + let owner_pubkey: Pubkey = owner.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid assign.owner: {}", owner)) + })?; + Ok(system_ix::assign(&account_pubkey, &owner_pubkey)) + } + + // ===== Memo Program ===== + IntentInstruction::Memo { message } => Ok(build_memo(&message)), + + // ===== Compute Budget Program ===== + IntentInstruction::ComputeBudget { + unit_limit, + unit_price, + } => { + // Return a single instruction - prefer unit_price if both specified + // Use SDK's ComputeBudgetInstruction 3.x methods (compatible with solana-sdk 3.x) + if let Some(price) = unit_price { + Ok(ComputeBudgetInstruction::set_compute_unit_price(price)) + } else if let Some(limit) = unit_limit { + Ok(ComputeBudgetInstruction::set_compute_unit_limit(limit)) + } else { + Err(WasmSolanaError::new( + "ComputeBudget instruction requires either unitLimit or unitPrice", + )) + } + } + } +} + +// ===== Nonce Instruction Builders ===== + +/// Build an InitializeNonceAccount instruction using the SDK's SystemInstruction enum. +/// SDK 3.x `create_nonce_account` combines create + initialize; we extract just initialize. +fn build_nonce_initialize(nonce: &Pubkey, authority: &Pubkey) -> Instruction { + // System program ID + let system_program_id: Pubkey = "11111111111111111111111111111111".parse().unwrap(); + + // Sysvars (same addresses as used by SDK) + let recent_blockhashes_sysvar: Pubkey = "SysvarRecentB1ockHashes11111111111111111111" + .parse() + .unwrap(); + let rent_sysvar: Pubkey = "SysvarRent111111111111111111111111111111111" + .parse() + .unwrap(); + + // Use SDK's SystemInstruction enum with bincode serialization (same as SDK does) + Instruction::new_with_bincode( + system_program_id, + &SystemInstruction::InitializeNonceAccount(*authority), + vec![ + AccountMeta::new(*nonce, false), // nonce account: writable + AccountMeta::new_readonly(recent_blockhashes_sysvar, false), // RecentBlockhashes sysvar + AccountMeta::new_readonly(rent_sysvar, false), // Rent sysvar + ], + ) +} + +// ===== Other Instruction Builders ===== + +/// Build a memo instruction. +fn build_memo(message: &str) -> Instruction { + Instruction::new_with_bytes(program_ids::memo_program(), message.as_bytes(), vec![]) +} + +#[cfg(test)] +mod tests { + use super::*; + + // Use our 2.x parsing Transaction for verification (different type than SDK Transaction) + fn verify_tx_structure(tx_bytes: &[u8], expected_instructions: usize) { + use crate::transaction::TransactionExt; + let tx = crate::Transaction::from_bytes(tx_bytes).unwrap(); + assert_eq!(tx.num_instructions(), expected_instructions); + } + + #[test] + fn test_build_simple_transfer() { + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![IntentInstruction::Transfer { + from: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + to: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + lamports: "1000000".to_string(), + }], + }; + + let result = build_transaction(intent); + assert!(result.is_ok(), "Failed to build transaction: {:?}", result); + + let tx_bytes = result.unwrap(); + assert!(!tx_bytes.is_empty()); + verify_tx_structure(&tx_bytes, 1); + } + + #[test] + fn test_build_with_memo() { + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![ + IntentInstruction::Transfer { + from: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + to: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + lamports: "1000000".to_string(), + }, + IntentInstruction::Memo { + message: "BitGo transfer".to_string(), + }, + ], + }; + + let result = build_transaction(intent); + assert!(result.is_ok()); + + let tx_bytes = result.unwrap(); + verify_tx_structure(&tx_bytes, 2); + } + + #[test] + fn test_build_with_compute_budget() { + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![ + IntentInstruction::ComputeBudget { + unit_limit: Some(200000), + unit_price: None, + }, + IntentInstruction::Transfer { + from: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + to: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + lamports: "1000000".to_string(), + }, + ], + }; + + let result = build_transaction(intent); + assert!(result.is_ok()); + } + + #[test] + fn test_invalid_pubkey() { + let intent = TransactionIntent { + fee_payer: "invalid".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![], + }; + + let result = build_transaction(intent); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Invalid")); + } +} diff --git a/packages/wasm-solana/src/builder/mod.rs b/packages/wasm-solana/src/builder/mod.rs new file mode 100644 index 0000000..0325cab --- /dev/null +++ b/packages/wasm-solana/src/builder/mod.rs @@ -0,0 +1,10 @@ +//! Transaction building module. +//! +//! This module provides the `buildTransaction()` function which creates Solana +//! transactions from a high-level `TransactionIntent` structure. + +mod build; +mod types; + +pub use build::build_transaction; +pub use types::{Instruction, Nonce, TransactionIntent}; diff --git a/packages/wasm-solana/src/builder/types.rs b/packages/wasm-solana/src/builder/types.rs new file mode 100644 index 0000000..2a92a88 --- /dev/null +++ b/packages/wasm-solana/src/builder/types.rs @@ -0,0 +1,116 @@ +//! Types for transaction building. +//! +//! These types are designed to be serialized from JavaScript via serde. +//! They use string representations for public keys and amounts to ensure +//! compatibility with JavaScript's number limitations. + +use serde::Deserialize; + +/// Nonce source for transaction - either a recent blockhash or durable nonce account. +#[derive(Debug, Clone, Deserialize)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum Nonce { + /// Use a recent blockhash (standard transactions) + Blockhash { value: String }, + /// Use a durable nonce account (offline signing) + Durable { + address: String, + authority: String, + /// Nonce value stored in the account (this becomes the blockhash) + value: String, + }, +} + +/// Intent to build a transaction. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionIntent { + /// The fee payer's public key (base58) + pub fee_payer: String, + /// Nonce source + pub nonce: Nonce, + /// List of instructions to include + pub instructions: Vec, +} + +/// An instruction to include in the transaction. +/// +/// This is a discriminated union (tagged enum) that supports all instruction types. +/// Use the `type` field to determine which variant is being used. +#[derive(Debug, Clone, Deserialize)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum Instruction { + // ===== System Program Instructions ===== + /// Transfer SOL from one account to another + Transfer { + from: String, + to: String, + /// Amount in lamports (as string for BigInt compatibility) + lamports: String, + }, + + /// Create a new account + CreateAccount { + from: String, + #[serde(rename = "newAccount")] + new_account: String, + /// Lamports to transfer to new account (as string) + lamports: String, + /// Space to allocate in bytes + space: u64, + /// Program owner of the new account + owner: String, + }, + + /// Advance a nonce account + NonceAdvance { + /// Nonce account address + nonce: String, + /// Nonce authority + authority: String, + }, + + /// Initialize a nonce account + NonceInitialize { + /// Nonce account address + nonce: String, + /// Nonce authority + authority: String, + }, + + /// Allocate space in an account + Allocate { account: String, space: u64 }, + + /// Assign account to a program + Assign { account: String, owner: String }, + + // ===== Memo Program ===== + /// Add a memo to the transaction + Memo { message: String }, + + // ===== Compute Budget Program ===== + /// Set compute budget (priority fees) + ComputeBudget { + /// Compute unit limit (optional) + #[serde(rename = "unitLimit")] + unit_limit: Option, + /// Compute unit price in micro-lamports (optional) + #[serde(rename = "unitPrice")] + unit_price: Option, + }, + // ===== Stake Program Instructions (Phase 10) ===== + // TODO: Add in Phase 10 + // StakeInitialize { ... } + // StakeDelegate { ... } + // StakeDeactivate { ... } + // StakeWithdraw { ... } + // StakeAuthorize { ... } + // StakeSplit { ... } + // StakeMerge { ... } + + // ===== SPL Token Instructions (Phase 11) ===== + // TODO: Add in Phase 11 + // TokenTransfer { ... } + // CreateAta { ... } + // CloseAta { ... } +} diff --git a/packages/wasm-solana/src/lib.rs b/packages/wasm-solana/src/lib.rs index d88c037..b63ec05 100644 --- a/packages/wasm-solana/src/lib.rs +++ b/packages/wasm-solana/src/lib.rs @@ -23,6 +23,7 @@ //! let pubkey = Pubkey::from_base58("FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH").unwrap(); //! ``` +pub mod builder; mod error; mod instructions; pub mod keypair; @@ -38,4 +39,4 @@ pub use pubkey::{Pubkey, PubkeyExt}; pub use transaction::{Transaction, TransactionExt}; // Re-export WASM types -pub use wasm::{ParserNamespace, WasmKeypair, WasmPubkey, WasmTransaction}; +pub use wasm::{BuilderNamespace, ParserNamespace, WasmKeypair, WasmPubkey, WasmTransaction}; diff --git a/packages/wasm-solana/src/wasm/builder.rs b/packages/wasm-solana/src/wasm/builder.rs new file mode 100644 index 0000000..c129bcf --- /dev/null +++ b/packages/wasm-solana/src/wasm/builder.rs @@ -0,0 +1,65 @@ +//! WASM binding for transaction building. +//! +//! Exposes a `buildTransaction` function that creates transactions from +//! a high-level intent structure. + +use crate::builder; +use wasm_bindgen::prelude::*; + +/// Namespace for transaction building operations. +#[wasm_bindgen] +pub struct BuilderNamespace; + +#[wasm_bindgen] +impl BuilderNamespace { + /// Build a Solana transaction from an intent structure. + /// + /// Takes a TransactionIntent JSON object and returns serialized transaction bytes. + /// + /// # Intent Structure + /// + /// ```json + /// { + /// "feePayer": "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB", + /// "nonce": { + /// "type": "blockhash", + /// "value": "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4" + /// }, + /// "instructions": [ + /// { "type": "transfer", "from": "...", "to": "...", "lamports": "1000000" }, + /// { "type": "memo", "message": "BitGo tx" } + /// ] + /// } + /// ``` + /// + /// # Instruction Types + /// + /// - `transfer`: SOL transfer (from, to, lamports) + /// - `createAccount`: Create new account (from, newAccount, lamports, space, owner) + /// - `nonceAdvance`: Advance durable nonce (nonce, authority) + /// - `nonceInitialize`: Initialize nonce account (nonce, authority) + /// - `allocate`: Allocate space (account, space) + /// - `assign`: Assign to program (account, owner) + /// - `memo`: Add memo (message) + /// - `computeBudget`: Set compute units (unitLimit, unitPrice) + /// + /// # Returns + /// + /// Serialized unsigned transaction bytes (Uint8Array). + /// The transaction will have empty signature placeholders that can be + /// filled in later by signing. + /// + /// @param intent - The transaction intent as a JSON object + /// @returns Serialized transaction bytes + #[wasm_bindgen] + pub fn build_transaction(intent: JsValue) -> Result, JsValue> { + // Deserialize the intent from JavaScript + let intent: builder::TransactionIntent = + serde_wasm_bindgen::from_value(intent).map_err(|e| { + JsValue::from_str(&format!("Failed to parse transaction intent: {}", e)) + })?; + + // Build the transaction + builder::build_transaction(intent).map_err(|e| JsValue::from_str(&e.to_string())) + } +} diff --git a/packages/wasm-solana/src/wasm/mod.rs b/packages/wasm-solana/src/wasm/mod.rs index a149e9c..17c26fc 100644 --- a/packages/wasm-solana/src/wasm/mod.rs +++ b/packages/wasm-solana/src/wasm/mod.rs @@ -1,8 +1,10 @@ +mod builder; mod keypair; mod parser; mod pubkey; mod transaction; +pub use builder::BuilderNamespace; pub use keypair::WasmKeypair; pub use parser::ParserNamespace; pub use pubkey::WasmPubkey; diff --git a/packages/wasm-solana/test/builder.ts b/packages/wasm-solana/test/builder.ts new file mode 100644 index 0000000..1f7648d --- /dev/null +++ b/packages/wasm-solana/test/builder.ts @@ -0,0 +1,277 @@ +import * as assert from "assert"; +import { + buildTransaction, + parseTransaction, + type TransactionIntent, + type BuilderInstruction, +} from "../js/index.js"; + +describe("buildTransaction", () => { + // Test addresses from BitGoJS sdk-coin-sol/test/resources/sol.ts + const AUTH_ACCOUNT = "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe"; // authAccount.pub + const RECIPIENT = "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH"; // accountWithSeed.publicKey + const NONCE_ACCOUNT = "8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh"; // nonceAccount.pub + const BLOCKHASH = "5ne7phA48Jrvpn39AtupB8ZkCCAy8gLTfpGihZPuDqen"; // blockHashes.validBlockHashes[0] + const STAKE_ACCOUNT = "3c5emUWjViFqT72LxQYec8gkU8ZtmfKKXHvGgJNUBdYx"; // stakeAccount.pub + + // Aliases for clarity + const SENDER = AUTH_ACCOUNT; + + describe("simple transfer", () => { + it("should build a SOL transfer transaction", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { type: "transfer", from: SENDER, to: RECIPIENT, lamports: "1000000" }, + ], + }; + + const txBytes = buildTransaction(intent); + assert.ok(txBytes instanceof Uint8Array); + assert.ok(txBytes.length > 0); + + // Parse it back to verify structure + const parsed = parseTransaction(txBytes); + assert.strictEqual(parsed.feePayer, SENDER); + assert.strictEqual(parsed.nonce, BLOCKHASH); + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "Transfer"); + }); + + it("should parse the transfer instruction correctly", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "transfer", + from: SENDER, + to: RECIPIENT, + lamports: "1000000", + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + const transfer = parsed.instructionsData[0]; + assert.strictEqual(transfer.type, "Transfer"); + if (transfer.type === "Transfer") { + // Parser uses fromAddress/toAddress/amount + assert.strictEqual(transfer.fromAddress, SENDER); + assert.strictEqual(transfer.toAddress, RECIPIENT); + assert.strictEqual(transfer.amount, "1000000"); + } + }); + }); + + describe("transfer with memo", () => { + it("should build a transfer with memo", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { type: "transfer", from: SENDER, to: RECIPIENT, lamports: "1000000" }, + { type: "memo", message: "BitGo transfer" }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 2); + assert.strictEqual(parsed.instructionsData[0].type, "Transfer"); + assert.strictEqual(parsed.instructionsData[1].type, "Memo"); + + const memo = parsed.instructionsData[1]; + if (memo.type === "Memo") { + // Parser uses 'memo' field + assert.strictEqual(memo.memo, "BitGo transfer"); + } + }); + }); + + describe("compute budget", () => { + it("should build with compute unit limit", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { type: "computeBudget", unitLimit: 200000 }, + { type: "transfer", from: SENDER, to: RECIPIENT, lamports: "1000000" }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 2); + assert.strictEqual(parsed.instructionsData[0].type, "SetComputeUnitLimit"); + assert.strictEqual(parsed.instructionsData[1].type, "Transfer"); + + const computeBudget = parsed.instructionsData[0]; + if (computeBudget.type === "SetComputeUnitLimit") { + assert.strictEqual(computeBudget.units, 200000); + } + }); + + it("should build with compute unit price (priority fee)", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { type: "computeBudget", unitPrice: 5000 }, + { type: "transfer", from: SENDER, to: RECIPIENT, lamports: "1000000" }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 2); + assert.strictEqual(parsed.instructionsData[0].type, "SetPriorityFee"); + assert.strictEqual(parsed.instructionsData[1].type, "Transfer"); + + const priorityFee = parsed.instructionsData[0]; + if (priorityFee.type === "SetPriorityFee") { + // Parser uses 'fee' as a number + assert.strictEqual(priorityFee.fee, 5000); + } + }); + }); + + describe("durable nonce", () => { + it("should prepend nonce advance instruction for durable nonce", () => { + // Use BitGoJS nonceAccount.pub and a sample nonce value + const NONCE_AUTHORITY = SENDER; + // This is the nonce value stored in the nonce account (becomes the blockhash) + const NONCE_VALUE = "GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi"; + + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { + type: "durable", + address: NONCE_ACCOUNT, + authority: NONCE_AUTHORITY, + value: NONCE_VALUE, + }, + instructions: [ + { type: "transfer", from: SENDER, to: RECIPIENT, lamports: "1000000" }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + // Should have 2 instructions: NonceAdvance + Transfer + assert.strictEqual(parsed.instructionsData.length, 2); + assert.strictEqual(parsed.instructionsData[0].type, "NonceAdvance"); + assert.strictEqual(parsed.instructionsData[1].type, "Transfer"); + + // Verify nonce advance params + const nonceAdvance = parsed.instructionsData[0]; + if (nonceAdvance.type === "NonceAdvance") { + // Parser uses walletNonceAddress/authWalletAddress + assert.strictEqual(nonceAdvance.walletNonceAddress, NONCE_ACCOUNT); + assert.strictEqual(nonceAdvance.authWalletAddress, NONCE_AUTHORITY); + } + }); + }); + + describe("create account", () => { + it("should build create account instruction", () => { + // Use BitGoJS stakeAccount.pub as the new account + const NEW_ACCOUNT = STAKE_ACCOUNT; + const SYSTEM_PROGRAM = "11111111111111111111111111111111"; + + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "createAccount", + from: SENDER, + newAccount: NEW_ACCOUNT, + lamports: "1000000", + space: 165, + owner: SYSTEM_PROGRAM, + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "CreateAccount"); + + const createAccount = parsed.instructionsData[0]; + if (createAccount.type === "CreateAccount") { + // Parser uses fromAddress/newAddress/amount/space/owner + assert.strictEqual(createAccount.fromAddress, SENDER); + assert.strictEqual(createAccount.newAddress, NEW_ACCOUNT); + assert.strictEqual(createAccount.amount, "1000000"); + assert.strictEqual(createAccount.space, 165); + assert.strictEqual(createAccount.owner, SYSTEM_PROGRAM); + } + }); + }); + + describe("error handling", () => { + it("should reject invalid public key", () => { + const intent: TransactionIntent = { + feePayer: "invalid", + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [], + }; + + assert.throws( + () => buildTransaction(intent), + /Invalid fee_payer/ + ); + }); + + it("should reject invalid blockhash", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: "invalid" }, + instructions: [], + }; + + assert.throws(() => buildTransaction(intent), /Invalid blockhash/); + }); + + it("should reject computeBudget without unitLimit or unitPrice", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [{ type: "computeBudget" } as BuilderInstruction], + }; + + assert.throws( + () => buildTransaction(intent), + /ComputeBudget.*unitLimit.*unitPrice/ + ); + }); + }); + + describe("roundtrip", () => { + it("should produce consistent bytes on rebuild", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { type: "transfer", from: SENDER, to: RECIPIENT, lamports: "1000000" }, + { type: "memo", message: "Test" }, + ], + }; + + const txBytes1 = buildTransaction(intent); + const txBytes2 = buildTransaction(intent); + + assert.deepStrictEqual(txBytes1, txBytes2); + }); + }); +}); From 5041b869fcbd2387d8571451818feb6735abd9ec Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Fri, 23 Jan 2026 15:34:10 -0800 Subject: [PATCH 3/6] feat(wasm-solana): add Stake Program transaction building Add support for building native staking instructions: - StakeInitialize: Initialize stake account with authorized staker/withdrawer - StakeDelegate: Delegate stake to a validator - StakeDeactivate: Deactivate a stake account - StakeWithdraw: Withdraw from a stake account - StakeAuthorize: Change stake account authorization Ticket: BTC-2955 --- packages/wasm-solana/src/builder/build.rs | 292 +++++++++++++++++++++- packages/wasm-solana/src/builder/types.rs | 69 ++++- 2 files changed, 348 insertions(+), 13 deletions(-) diff --git a/packages/wasm-solana/src/builder/build.rs b/packages/wasm-solana/src/builder/build.rs index 5015d22..2a7491b 100644 --- a/packages/wasm-solana/src/builder/build.rs +++ b/packages/wasm-solana/src/builder/build.rs @@ -13,9 +13,11 @@ use solana_sdk::message::Message; use solana_sdk::pubkey::Pubkey; use solana_sdk::transaction::Transaction; use solana_compute_budget_interface::ComputeBudgetInstruction; +use solana_stake_interface::instruction::StakeInstruction; +use solana_stake_interface::state::{Authorized, Lockup, StakeAuthorize}; use solana_system_interface::instruction::{self as system_ix, SystemInstruction}; -/// Well-known program IDs +/// Well-known program IDs and sysvars mod program_ids { use super::Pubkey; @@ -24,6 +26,36 @@ mod program_ids { .parse() .unwrap() } + + pub fn stake_program() -> Pubkey { + "Stake11111111111111111111111111111111111111" + .parse() + .unwrap() + } + + pub fn clock_sysvar() -> Pubkey { + "SysvarC1ock11111111111111111111111111111111" + .parse() + .unwrap() + } + + pub fn rent_sysvar() -> Pubkey { + "SysvarRent111111111111111111111111111111111" + .parse() + .unwrap() + } + + pub fn stake_history_sysvar() -> Pubkey { + "SysvarStakeHistory1111111111111111111111111" + .parse() + .unwrap() + } + + pub fn stake_config() -> Pubkey { + "StakeConfig11111111111111111111111111111111" + .parse() + .unwrap() + } } /// Build a transaction from an intent structure. @@ -192,6 +224,126 @@ fn build_instruction(ix: IntentInstruction) -> Result { + let stake_pubkey: Pubkey = stake.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeInitialize.stake: {}", stake)) + })?; + let staker_pubkey: Pubkey = staker.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeInitialize.staker: {}", staker)) + })?; + let withdrawer_pubkey: Pubkey = withdrawer.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakeInitialize.withdrawer: {}", + withdrawer + )) + })?; + Ok(build_stake_initialize( + &stake_pubkey, + &Authorized { + staker: staker_pubkey, + withdrawer: withdrawer_pubkey, + }, + )) + } + + IntentInstruction::StakeDelegate { + stake, + vote, + authority, + } => { + let stake_pubkey: Pubkey = stake.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeDelegate.stake: {}", stake)) + })?; + let vote_pubkey: Pubkey = vote.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeDelegate.vote: {}", vote)) + })?; + let authority_pubkey: Pubkey = authority.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeDelegate.authority: {}", authority)) + })?; + Ok(build_stake_delegate( + &stake_pubkey, + &vote_pubkey, + &authority_pubkey, + )) + } + + IntentInstruction::StakeDeactivate { stake, authority } => { + let stake_pubkey: Pubkey = stake.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeDeactivate.stake: {}", stake)) + })?; + let authority_pubkey: Pubkey = authority.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeDeactivate.authority: {}", authority)) + })?; + Ok(build_stake_deactivate(&stake_pubkey, &authority_pubkey)) + } + + IntentInstruction::StakeWithdraw { + stake, + recipient, + lamports, + authority, + } => { + let stake_pubkey: Pubkey = stake.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeWithdraw.stake: {}", stake)) + })?; + let recipient_pubkey: Pubkey = recipient.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeWithdraw.recipient: {}", recipient)) + })?; + let amount: u64 = lamports.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeWithdraw.lamports: {}", lamports)) + })?; + let authority_pubkey: Pubkey = authority.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeWithdraw.authority: {}", authority)) + })?; + Ok(build_stake_withdraw( + &stake_pubkey, + &recipient_pubkey, + amount, + &authority_pubkey, + )) + } + + IntentInstruction::StakeAuthorize { + stake, + new_authority, + authorize_type, + authority, + } => { + let stake_pubkey: Pubkey = stake.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeAuthorize.stake: {}", stake)) + })?; + let new_authority_pubkey: Pubkey = new_authority.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakeAuthorize.newAuthority: {}", + new_authority + )) + })?; + let authority_pubkey: Pubkey = authority.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakeAuthorize.authority: {}", authority)) + })?; + let stake_authorize = match authorize_type.to_lowercase().as_str() { + "staker" => StakeAuthorize::Staker, + "withdrawer" => StakeAuthorize::Withdrawer, + _ => { + return Err(WasmSolanaError::new(&format!( + "Invalid stakeAuthorize.authorizeType: {} (expected 'staker' or 'withdrawer')", + authorize_type + ))) + } + }; + Ok(build_stake_authorize( + &stake_pubkey, + &authority_pubkey, + &new_authority_pubkey, + stake_authorize, + )) + } } } @@ -230,6 +382,87 @@ fn build_memo(message: &str) -> Instruction { Instruction::new_with_bytes(program_ids::memo_program(), message.as_bytes(), vec![]) } +// ===== Stake Instruction Builders ===== + +/// Build a stake initialize instruction. +fn build_stake_initialize(stake: &Pubkey, authorized: &Authorized) -> Instruction { + Instruction::new_with_bincode( + program_ids::stake_program(), + &StakeInstruction::Initialize(*authorized, Lockup::default()), + vec![ + AccountMeta::new(*stake, false), + AccountMeta::new_readonly(program_ids::rent_sysvar(), false), + ], + ) +} + +/// Build a stake delegate instruction. +fn build_stake_delegate(stake: &Pubkey, vote: &Pubkey, authority: &Pubkey) -> Instruction { + Instruction::new_with_bincode( + program_ids::stake_program(), + &StakeInstruction::DelegateStake, + vec![ + AccountMeta::new(*stake, false), + AccountMeta::new_readonly(*vote, false), + AccountMeta::new_readonly(program_ids::clock_sysvar(), false), + AccountMeta::new_readonly(program_ids::stake_history_sysvar(), false), + AccountMeta::new_readonly(program_ids::stake_config(), false), + AccountMeta::new_readonly(*authority, true), + ], + ) +} + +/// Build a stake deactivate instruction. +fn build_stake_deactivate(stake: &Pubkey, authority: &Pubkey) -> Instruction { + Instruction::new_with_bincode( + program_ids::stake_program(), + &StakeInstruction::Deactivate, + vec![ + AccountMeta::new(*stake, false), + AccountMeta::new_readonly(program_ids::clock_sysvar(), false), + AccountMeta::new_readonly(*authority, true), + ], + ) +} + +/// Build a stake withdraw instruction. +fn build_stake_withdraw( + stake: &Pubkey, + recipient: &Pubkey, + lamports: u64, + authority: &Pubkey, +) -> Instruction { + Instruction::new_with_bincode( + program_ids::stake_program(), + &StakeInstruction::Withdraw(lamports), + vec![ + AccountMeta::new(*stake, false), + AccountMeta::new(*recipient, false), + AccountMeta::new_readonly(program_ids::clock_sysvar(), false), + AccountMeta::new_readonly(program_ids::stake_history_sysvar(), false), + AccountMeta::new_readonly(*authority, true), + ], + ) +} + +/// Build a stake authorize instruction. +fn build_stake_authorize( + stake: &Pubkey, + authority: &Pubkey, + new_authority: &Pubkey, + stake_authorize: StakeAuthorize, +) -> Instruction { + Instruction::new_with_bincode( + program_ids::stake_program(), + &StakeInstruction::Authorize(*new_authority, stake_authorize), + vec![ + AccountMeta::new(*stake, false), + AccountMeta::new_readonly(program_ids::clock_sysvar(), false), + AccountMeta::new_readonly(*authority, true), + ], + ) +} + #[cfg(test)] mod tests { use super::*; @@ -327,4 +560,61 @@ mod tests { assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("Invalid")); } + + #[test] + fn test_build_stake_delegate() { + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![IntentInstruction::StakeDelegate { + stake: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + vote: "5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN".to_string(), + authority: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + }], + }; + + let result = build_transaction(intent); + assert!(result.is_ok(), "Failed to build stake delegate: {:?}", result); + verify_tx_structure(&result.unwrap(), 1); + } + + #[test] + fn test_build_stake_deactivate() { + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![IntentInstruction::StakeDeactivate { + stake: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + authority: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + }], + }; + + let result = build_transaction(intent); + assert!(result.is_ok(), "Failed to build stake deactivate: {:?}", result); + verify_tx_structure(&result.unwrap(), 1); + } + + #[test] + fn test_build_stake_withdraw() { + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![IntentInstruction::StakeWithdraw { + stake: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + recipient: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + lamports: "1000000".to_string(), + authority: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + }], + }; + + let result = build_transaction(intent); + assert!(result.is_ok(), "Failed to build stake withdraw: {:?}", result); + verify_tx_structure(&result.unwrap(), 1); + } } diff --git a/packages/wasm-solana/src/builder/types.rs b/packages/wasm-solana/src/builder/types.rs index 2a92a88..563b87b 100644 --- a/packages/wasm-solana/src/builder/types.rs +++ b/packages/wasm-solana/src/builder/types.rs @@ -98,18 +98,63 @@ pub enum Instruction { #[serde(rename = "unitPrice")] unit_price: Option, }, - // ===== Stake Program Instructions (Phase 10) ===== - // TODO: Add in Phase 10 - // StakeInitialize { ... } - // StakeDelegate { ... } - // StakeDeactivate { ... } - // StakeWithdraw { ... } - // StakeAuthorize { ... } - // StakeSplit { ... } - // StakeMerge { ... } - - // ===== SPL Token Instructions (Phase 11) ===== - // TODO: Add in Phase 11 + // ===== Stake Program Instructions ===== + /// Initialize a stake account with authorized staker and withdrawer + StakeInitialize { + /// Stake account address + stake: String, + /// Authorized staker pubkey + staker: String, + /// Authorized withdrawer pubkey + withdrawer: String, + }, + + /// Delegate stake to a validator + StakeDelegate { + /// Stake account address + stake: String, + /// Vote account (validator) to delegate to + vote: String, + /// Stake authority + authority: String, + }, + + /// Deactivate a stake account + StakeDeactivate { + /// Stake account address + stake: String, + /// Stake authority + authority: String, + }, + + /// Withdraw from a stake account + StakeWithdraw { + /// Stake account address + stake: String, + /// Recipient address for withdrawn lamports + recipient: String, + /// Amount in lamports to withdraw (as string) + lamports: String, + /// Withdraw authority + authority: String, + }, + + /// Change stake account authorization + StakeAuthorize { + /// Stake account address + stake: String, + /// New authority pubkey + #[serde(rename = "newAuthority")] + new_authority: String, + /// Authorization type: "staker" or "withdrawer" + #[serde(rename = "authorizeType")] + authorize_type: String, + /// Current authority + authority: String, + }, + + // ===== SPL Token Instructions ===== + // TODO: Add SPL Token support // TokenTransfer { ... } // CreateAta { ... } // CloseAta { ... } From 1f52a74cc37d190c8e5ca4bda5d63947a3e8badf Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Fri, 23 Jan 2026 15:36:14 -0800 Subject: [PATCH 4/6] feat(wasm-solana): add SPL Token transaction building Add support for building SPL Token instructions: - TokenTransfer: Transfer tokens using TransferChecked for safety - CreateAssociatedTokenAccount: Create ATAs with automatic address derivation - CloseAssociatedTokenAccount: Close token accounts and reclaim rent Supports both Token Program and Token-2022 via configurable program ID. Ticket: BTC-2955 --- packages/wasm-solana/src/builder/build.rs | 253 ++++++++++++++++++++++ packages/wasm-solana/src/builder/types.rs | 52 ++++- 2 files changed, 301 insertions(+), 4 deletions(-) diff --git a/packages/wasm-solana/src/builder/build.rs b/packages/wasm-solana/src/builder/build.rs index 2a7491b..1c97532 100644 --- a/packages/wasm-solana/src/builder/build.rs +++ b/packages/wasm-solana/src/builder/build.rs @@ -56,6 +56,16 @@ mod program_ids { .parse() .unwrap() } + + pub fn ata_program() -> Pubkey { + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + .parse() + .unwrap() + } + + pub fn system_program() -> Pubkey { + "11111111111111111111111111111111".parse().unwrap() + } } /// Build a transaction from an intent structure. @@ -344,6 +354,100 @@ fn build_instruction(ix: IntentInstruction) -> Result { + let source_pubkey: Pubkey = source.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid tokenTransfer.source: {}", source)) + })?; + let destination_pubkey: Pubkey = destination.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid tokenTransfer.destination: {}", destination)) + })?; + let mint_pubkey: Pubkey = mint.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid tokenTransfer.mint: {}", mint)) + })?; + let authority_pubkey: Pubkey = authority.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid tokenTransfer.authority: {}", authority)) + })?; + let token_program: Pubkey = program_id.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid tokenTransfer.programId: {}", program_id)) + })?; + let transfer_amount: u64 = amount.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid tokenTransfer.amount: {}", amount)) + })?; + Ok(build_token_transfer_checked( + &source_pubkey, + &mint_pubkey, + &destination_pubkey, + &authority_pubkey, + transfer_amount, + decimals, + &token_program, + )) + } + + IntentInstruction::CreateAssociatedTokenAccount { + payer, + owner, + mint, + token_program_id, + } => { + let payer_pubkey: Pubkey = payer.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid createAta.payer: {}", payer)) + })?; + let owner_pubkey: Pubkey = owner.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid createAta.owner: {}", owner)) + })?; + let mint_pubkey: Pubkey = mint.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid createAta.mint: {}", mint)) + })?; + let token_program: Pubkey = token_program_id.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid createAta.tokenProgramId: {}", + token_program_id + )) + })?; + Ok(build_create_ata( + &payer_pubkey, + &owner_pubkey, + &mint_pubkey, + &token_program, + )) + } + + IntentInstruction::CloseAssociatedTokenAccount { + account, + destination, + authority, + program_id, + } => { + let account_pubkey: Pubkey = account.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid closeAta.account: {}", account)) + })?; + let destination_pubkey: Pubkey = destination.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid closeAta.destination: {}", destination)) + })?; + let authority_pubkey: Pubkey = authority.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid closeAta.authority: {}", authority)) + })?; + let token_program: Pubkey = program_id.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid closeAta.programId: {}", program_id)) + })?; + Ok(build_close_account( + &account_pubkey, + &destination_pubkey, + &authority_pubkey, + &token_program, + )) + } } } @@ -463,6 +567,92 @@ fn build_stake_authorize( ) } +// ===== SPL Token Instruction Builders ===== + +/// Build a TransferChecked instruction for SPL Token. +/// TransferChecked is safer than Transfer as it verifies decimals. +fn build_token_transfer_checked( + source: &Pubkey, + mint: &Pubkey, + destination: &Pubkey, + authority: &Pubkey, + amount: u64, + decimals: u8, + token_program: &Pubkey, +) -> Instruction { + // TransferChecked instruction data: [12, amount (8 bytes LE), decimals (1 byte)] + let mut data = vec![12u8]; // TransferChecked discriminator + data.extend_from_slice(&amount.to_le_bytes()); + data.push(decimals); + + Instruction::new_with_bytes( + *token_program, + &data, + vec![ + AccountMeta::new(*source, false), // source token account + AccountMeta::new_readonly(*mint, false), // mint + AccountMeta::new(*destination, false), // destination token account + AccountMeta::new_readonly(*authority, true), // owner/authority (signer) + ], + ) +} + +/// Build a CreateAssociatedTokenAccount instruction. +fn build_create_ata( + payer: &Pubkey, + owner: &Pubkey, + mint: &Pubkey, + token_program: &Pubkey, +) -> Instruction { + // Derive the ATA address + let ata = get_associated_token_address(owner, mint, token_program); + + // ATA program create instruction has no data (or discriminator 0) + Instruction::new_with_bytes( + program_ids::ata_program(), + &[], + vec![ + AccountMeta::new(*payer, true), // payer (signer) + AccountMeta::new(ata, false), // associated token account + AccountMeta::new_readonly(*owner, false), // wallet owner + AccountMeta::new_readonly(*mint, false), // token mint + AccountMeta::new_readonly(program_ids::system_program(), false), // system program + AccountMeta::new_readonly(*token_program, false), // token program + ], + ) +} + +/// Build a CloseAccount instruction for SPL Token. +fn build_close_account( + account: &Pubkey, + destination: &Pubkey, + authority: &Pubkey, + token_program: &Pubkey, +) -> Instruction { + // CloseAccount instruction data: [9] (discriminator only) + Instruction::new_with_bytes( + *token_program, + &[9u8], // CloseAccount discriminator + vec![ + AccountMeta::new(*account, false), // account to close + AccountMeta::new(*destination, false), // destination for lamports + AccountMeta::new_readonly(*authority, true), // owner/authority (signer) + ], + ) +} + +/// Derive the Associated Token Account address. +fn get_associated_token_address(owner: &Pubkey, mint: &Pubkey, token_program: &Pubkey) -> Pubkey { + // ATA is a PDA with seeds: [owner, token_program, mint] + let seeds = &[ + owner.as_ref(), + token_program.as_ref(), + mint.as_ref(), + ]; + let (ata, _bump) = Pubkey::find_program_address(seeds, &program_ids::ata_program()); + ata +} + #[cfg(test)] mod tests { use super::*; @@ -617,4 +807,67 @@ mod tests { assert!(result.is_ok(), "Failed to build stake withdraw: {:?}", result); verify_tx_structure(&result.unwrap(), 1); } + + #[test] + fn test_build_token_transfer() { + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![IntentInstruction::TokenTransfer { + source: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + destination: "5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN".to_string(), + mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(), // USDC mint + amount: "1000000".to_string(), + decimals: 6, + authority: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string(), + }], + }; + + let result = build_transaction(intent); + assert!(result.is_ok(), "Failed to build token transfer: {:?}", result); + verify_tx_structure(&result.unwrap(), 1); + } + + #[test] + fn test_build_create_ata() { + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![IntentInstruction::CreateAssociatedTokenAccount { + payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + owner: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(), // USDC mint + token_program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string(), + }], + }; + + let result = build_transaction(intent); + assert!(result.is_ok(), "Failed to build create ATA: {:?}", result); + verify_tx_structure(&result.unwrap(), 1); + } + + #[test] + fn test_build_close_ata() { + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![IntentInstruction::CloseAssociatedTokenAccount { + account: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + destination: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + authority: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string(), + }], + }; + + let result = build_transaction(intent); + assert!(result.is_ok(), "Failed to build close ATA: {:?}", result); + verify_tx_structure(&result.unwrap(), 1); + } } diff --git a/packages/wasm-solana/src/builder/types.rs b/packages/wasm-solana/src/builder/types.rs index 563b87b..6ed82fc 100644 --- a/packages/wasm-solana/src/builder/types.rs +++ b/packages/wasm-solana/src/builder/types.rs @@ -154,8 +154,52 @@ pub enum Instruction { }, // ===== SPL Token Instructions ===== - // TODO: Add SPL Token support - // TokenTransfer { ... } - // CreateAta { ... } - // CloseAta { ... } + /// Transfer tokens (uses TransferChecked for safety) + TokenTransfer { + /// Source token account + source: String, + /// Destination token account + destination: String, + /// Token mint address + mint: String, + /// Amount of tokens to transfer (as string, in smallest units) + amount: String, + /// Number of decimals for the token + decimals: u8, + /// Owner/authority of the source account + authority: String, + /// Token program ID (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA or Token-2022) + #[serde(rename = "programId", default = "default_token_program")] + program_id: String, + }, + + /// Create an Associated Token Account + CreateAssociatedTokenAccount { + /// Payer for account creation + payer: String, + /// Owner of the new ATA + owner: String, + /// Token mint address + mint: String, + /// Token program ID (optional, defaults to Token Program) + #[serde(rename = "tokenProgramId", default = "default_token_program")] + token_program_id: String, + }, + + /// Close an Associated Token Account + CloseAssociatedTokenAccount { + /// Token account to close + account: String, + /// Destination for remaining lamports + destination: String, + /// Authority of the account + authority: String, + /// Token program ID (optional, defaults to Token Program) + #[serde(rename = "programId", default = "default_token_program")] + program_id: String, + }, +} + +fn default_token_program() -> String { + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string() } From 2012dc95b7d0cae0513660e280294311a6efd33c Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Fri, 23 Jan 2026 15:39:30 -0800 Subject: [PATCH 5/6] feat(wasm-solana): add Jito Stake Pool transaction building Add transaction building support for SPL Stake Pool (Jito liquid staking): - StakePoolDepositSol: deposit SOL into stake pool, receive pool tokens - StakePoolWithdrawStake: withdraw stake by burning pool tokens Uses spl-stake-pool crate with BorshSerialize for proper instruction data encoding. Includes all required account metas for both operations. BTC-2955 --- packages/wasm-solana/src/builder/build.rs | 328 ++++++++++++++++++++++ packages/wasm-solana/src/builder/types.rs | 67 +++++ 2 files changed, 395 insertions(+) diff --git a/packages/wasm-solana/src/builder/build.rs b/packages/wasm-solana/src/builder/build.rs index 1c97532..36637b4 100644 --- a/packages/wasm-solana/src/builder/build.rs +++ b/packages/wasm-solana/src/builder/build.rs @@ -16,6 +16,7 @@ use solana_compute_budget_interface::ComputeBudgetInstruction; use solana_stake_interface::instruction::StakeInstruction; use solana_stake_interface::state::{Authorized, Lockup, StakeAuthorize}; use solana_system_interface::instruction::{self as system_ix, SystemInstruction}; +use spl_stake_pool::instruction::StakePoolInstruction; /// Well-known program IDs and sysvars mod program_ids { @@ -66,6 +67,18 @@ mod program_ids { pub fn system_program() -> Pubkey { "11111111111111111111111111111111".parse().unwrap() } + + pub fn stake_pool_program() -> Pubkey { + "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy" + .parse() + .unwrap() + } + + pub fn token_program() -> Pubkey { + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + .parse() + .unwrap() + } } /// Build a transaction from an intent structure. @@ -448,6 +461,176 @@ fn build_instruction(ix: IntentInstruction) -> Result { + let stake_pool_pubkey: Pubkey = stake_pool.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakePoolDepositSol.stakePool: {}", stake_pool)) + })?; + let withdraw_authority_pubkey: Pubkey = withdraw_authority.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolDepositSol.withdrawAuthority: {}", + withdraw_authority + )) + })?; + let reserve_stake_pubkey: Pubkey = reserve_stake.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolDepositSol.reserveStake: {}", + reserve_stake + )) + })?; + let funding_account_pubkey: Pubkey = funding_account.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolDepositSol.fundingAccount: {}", + funding_account + )) + })?; + let destination_pool_account_pubkey: Pubkey = + destination_pool_account.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolDepositSol.destinationPoolAccount: {}", + destination_pool_account + )) + })?; + let manager_fee_account_pubkey: Pubkey = manager_fee_account.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolDepositSol.managerFeeAccount: {}", + manager_fee_account + )) + })?; + let referral_pool_account_pubkey: Pubkey = + referral_pool_account.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolDepositSol.referralPoolAccount: {}", + referral_pool_account + )) + })?; + let pool_mint_pubkey: Pubkey = pool_mint.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakePoolDepositSol.poolMint: {}", pool_mint)) + })?; + let deposit_lamports: u64 = lamports.parse().map_err(|_| { + WasmSolanaError::new(&format!("Invalid stakePoolDepositSol.lamports: {}", lamports)) + })?; + + Ok(build_stake_pool_deposit_sol( + &stake_pool_pubkey, + &withdraw_authority_pubkey, + &reserve_stake_pubkey, + &funding_account_pubkey, + &destination_pool_account_pubkey, + &manager_fee_account_pubkey, + &referral_pool_account_pubkey, + &pool_mint_pubkey, + deposit_lamports, + )) + } + + IntentInstruction::StakePoolWithdrawStake { + stake_pool, + validator_list, + withdraw_authority, + validator_stake, + destination_stake, + destination_stake_authority, + source_transfer_authority, + source_pool_account, + manager_fee_account, + pool_mint, + pool_tokens, + } => { + let stake_pool_pubkey: Pubkey = stake_pool.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.stakePool: {}", + stake_pool + )) + })?; + let validator_list_pubkey: Pubkey = validator_list.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.validatorList: {}", + validator_list + )) + })?; + let withdraw_authority_pubkey: Pubkey = withdraw_authority.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.withdrawAuthority: {}", + withdraw_authority + )) + })?; + let validator_stake_pubkey: Pubkey = validator_stake.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.validatorStake: {}", + validator_stake + )) + })?; + let destination_stake_pubkey: Pubkey = destination_stake.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.destinationStake: {}", + destination_stake + )) + })?; + let destination_stake_authority_pubkey: Pubkey = + destination_stake_authority.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.destinationStakeAuthority: {}", + destination_stake_authority + )) + })?; + let source_transfer_authority_pubkey: Pubkey = + source_transfer_authority.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.sourceTransferAuthority: {}", + source_transfer_authority + )) + })?; + let source_pool_account_pubkey: Pubkey = source_pool_account.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.sourcePoolAccount: {}", + source_pool_account + )) + })?; + let manager_fee_account_pubkey: Pubkey = manager_fee_account.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.managerFeeAccount: {}", + manager_fee_account + )) + })?; + let pool_mint_pubkey: Pubkey = pool_mint.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.poolMint: {}", + pool_mint + )) + })?; + let withdraw_pool_tokens: u64 = pool_tokens.parse().map_err(|_| { + WasmSolanaError::new(&format!( + "Invalid stakePoolWithdrawStake.poolTokens: {}", + pool_tokens + )) + })?; + + Ok(build_stake_pool_withdraw_stake( + &stake_pool_pubkey, + &validator_list_pubkey, + &withdraw_authority_pubkey, + &validator_stake_pubkey, + &destination_stake_pubkey, + &destination_stake_authority_pubkey, + &source_transfer_authority_pubkey, + &source_pool_account_pubkey, + &manager_fee_account_pubkey, + &pool_mint_pubkey, + withdraw_pool_tokens, + )) + } } } @@ -653,6 +836,89 @@ fn get_associated_token_address(owner: &Pubkey, mint: &Pubkey, token_program: &P ata } +// ===== Jito Stake Pool Instruction Builders ===== + +/// Build a DepositSol instruction for SPL Stake Pool (Jito). +#[allow(clippy::too_many_arguments)] +fn build_stake_pool_deposit_sol( + stake_pool: &Pubkey, + withdraw_authority: &Pubkey, + reserve_stake: &Pubkey, + funding_account: &Pubkey, + destination_pool_account: &Pubkey, + manager_fee_account: &Pubkey, + referral_pool_account: &Pubkey, + pool_mint: &Pubkey, + lamports: u64, +) -> Instruction { + use borsh::BorshSerialize; + + // DepositSol instruction data using spl-stake-pool + let instruction_data = StakePoolInstruction::DepositSol(lamports); + let mut data = Vec::new(); + instruction_data.serialize(&mut data).unwrap(); + + Instruction::new_with_bytes( + program_ids::stake_pool_program(), + &data, + vec![ + AccountMeta::new(*stake_pool, false), + AccountMeta::new_readonly(*withdraw_authority, false), + AccountMeta::new(*reserve_stake, false), + AccountMeta::new(*funding_account, true), // signer + AccountMeta::new(*destination_pool_account, false), + AccountMeta::new(*manager_fee_account, false), + AccountMeta::new(*referral_pool_account, false), + AccountMeta::new(*pool_mint, false), + AccountMeta::new_readonly(program_ids::system_program(), false), + AccountMeta::new_readonly(program_ids::token_program(), false), + ], + ) +} + +/// Build a WithdrawStake instruction for SPL Stake Pool (Jito). +#[allow(clippy::too_many_arguments)] +fn build_stake_pool_withdraw_stake( + stake_pool: &Pubkey, + validator_list: &Pubkey, + withdraw_authority: &Pubkey, + validator_stake: &Pubkey, + destination_stake: &Pubkey, + destination_stake_authority: &Pubkey, + source_transfer_authority: &Pubkey, + source_pool_account: &Pubkey, + manager_fee_account: &Pubkey, + pool_mint: &Pubkey, + pool_tokens: u64, +) -> Instruction { + use borsh::BorshSerialize; + + // WithdrawStake instruction data using spl-stake-pool + let instruction_data = StakePoolInstruction::WithdrawStake(pool_tokens); + let mut data = Vec::new(); + instruction_data.serialize(&mut data).unwrap(); + + Instruction::new_with_bytes( + program_ids::stake_pool_program(), + &data, + vec![ + AccountMeta::new(*stake_pool, false), + AccountMeta::new(*validator_list, false), + AccountMeta::new_readonly(*withdraw_authority, false), + AccountMeta::new(*validator_stake, false), + AccountMeta::new(*destination_stake, false), + AccountMeta::new_readonly(*destination_stake_authority, false), + AccountMeta::new_readonly(*source_transfer_authority, true), // signer + AccountMeta::new(*source_pool_account, false), + AccountMeta::new(*manager_fee_account, false), + AccountMeta::new(*pool_mint, false), + AccountMeta::new_readonly(program_ids::clock_sysvar(), false), + AccountMeta::new_readonly(program_ids::token_program(), false), + AccountMeta::new_readonly(program_ids::stake_program(), false), + ], + ) +} + #[cfg(test)] mod tests { use super::*; @@ -870,4 +1136,66 @@ mod tests { assert!(result.is_ok(), "Failed to build close ATA: {:?}", result); verify_tx_structure(&result.unwrap(), 1); } + + #[test] + fn test_build_stake_pool_deposit_sol() { + // Jito stake pool addresses (testnet-like) + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![IntentInstruction::StakePoolDepositSol { + stake_pool: "Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb".to_string(), + withdraw_authority: "6iQKfEyhr3bZMotVkW6beNZz5CPAkiwvgV2CTje9pVSS".to_string(), + reserve_stake: "BgKUXdS4Wy6Vdgp1jwT2dz5ZgxPG94aPL77dQscSPGmc".to_string(), + funding_account: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + destination_pool_account: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + manager_fee_account: "5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN".to_string(), + referral_pool_account: "5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN".to_string(), + pool_mint: "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn".to_string(), + lamports: "1000000000".to_string(), // 1 SOL + }], + }; + + let result = build_transaction(intent); + assert!( + result.is_ok(), + "Failed to build stake pool deposit sol: {:?}", + result + ); + verify_tx_structure(&result.unwrap(), 1); + } + + #[test] + fn test_build_stake_pool_withdraw_stake() { + // Jito stake pool addresses (testnet-like) + let intent = TransactionIntent { + fee_payer: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + nonce: Nonce::Blockhash { + value: "GWaQEymC3Z9SHM2gkh8u12xL1zJPMHPCSVR3pSDpEXE4".to_string(), + }, + instructions: vec![IntentInstruction::StakePoolWithdrawStake { + stake_pool: "Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb".to_string(), + validator_list: "3R3nGZpQs2aZo5FDQvd2MUQ5R5E9g7NvHQaxpLPYA8r2".to_string(), + withdraw_authority: "6iQKfEyhr3bZMotVkW6beNZz5CPAkiwvgV2CTje9pVSS".to_string(), + validator_stake: "BgKUXdS4Wy6Vdgp1jwT2dz5ZgxPG94aPL77dQscSPGmc".to_string(), + destination_stake: "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH".to_string(), + destination_stake_authority: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + source_transfer_authority: "DgT9qyYwYKBRDyDw3EfR12LHQCQjtNrKu2qMsXHuosmB".to_string(), + source_pool_account: "5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN".to_string(), + manager_fee_account: "5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN".to_string(), + pool_mint: "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn".to_string(), + pool_tokens: "1000000000".to_string(), // 1 JitoSOL + }], + }; + + let result = build_transaction(intent); + assert!( + result.is_ok(), + "Failed to build stake pool withdraw stake: {:?}", + result + ); + verify_tx_structure(&result.unwrap(), 1); + } } diff --git a/packages/wasm-solana/src/builder/types.rs b/packages/wasm-solana/src/builder/types.rs index 6ed82fc..e2fd1e4 100644 --- a/packages/wasm-solana/src/builder/types.rs +++ b/packages/wasm-solana/src/builder/types.rs @@ -198,6 +198,73 @@ pub enum Instruction { #[serde(rename = "programId", default = "default_token_program")] program_id: String, }, + // ===== Jito Stake Pool Instructions ===== + /// Deposit SOL into a stake pool (Jito liquid staking) + StakePoolDepositSol { + /// Stake pool address + #[serde(rename = "stakePool")] + stake_pool: String, + /// Withdraw authority PDA + #[serde(rename = "withdrawAuthority")] + withdraw_authority: String, + /// Reserve stake account + #[serde(rename = "reserveStake")] + reserve_stake: String, + /// Funding account (SOL source, signer) + #[serde(rename = "fundingAccount")] + funding_account: String, + /// Destination for pool tokens + #[serde(rename = "destinationPoolAccount")] + destination_pool_account: String, + /// Manager fee account + #[serde(rename = "managerFeeAccount")] + manager_fee_account: String, + /// Referral pool account + #[serde(rename = "referralPoolAccount")] + referral_pool_account: String, + /// Pool mint address + #[serde(rename = "poolMint")] + pool_mint: String, + /// Amount in lamports to deposit (as string) + lamports: String, + }, + + /// Withdraw stake from a stake pool (Jito liquid staking) + StakePoolWithdrawStake { + /// Stake pool address + #[serde(rename = "stakePool")] + stake_pool: String, + /// Validator list account + #[serde(rename = "validatorList")] + validator_list: String, + /// Withdraw authority PDA + #[serde(rename = "withdrawAuthority")] + withdraw_authority: String, + /// Validator stake account to split from + #[serde(rename = "validatorStake")] + validator_stake: String, + /// Destination stake account (uninitialized) + #[serde(rename = "destinationStake")] + destination_stake: String, + /// Authority for the destination stake account + #[serde(rename = "destinationStakeAuthority")] + destination_stake_authority: String, + /// Source pool token account authority (signer) + #[serde(rename = "sourceTransferAuthority")] + source_transfer_authority: String, + /// Source pool token account + #[serde(rename = "sourcePoolAccount")] + source_pool_account: String, + /// Manager fee account + #[serde(rename = "managerFeeAccount")] + manager_fee_account: String, + /// Pool mint address + #[serde(rename = "poolMint")] + pool_mint: String, + /// Amount of pool tokens to burn (as string) + #[serde(rename = "poolTokens")] + pool_tokens: String, + }, } fn default_token_program() -> String { From 201cb2545f992a037b7a8c06b3900cf0963b8eb2 Mon Sep 17 00:00:00 2001 From: Luis Covarrubias Date: Fri, 23 Jan 2026 15:59:28 -0800 Subject: [PATCH 6/6] feat(wasm-solana): add TypeScript tests and types for Stake, Token, and Jito - Add TypeScript interfaces for all builder instruction types: - Stake Program: StakeInitialize, StakeDelegate, StakeDeactivate, StakeWithdraw, StakeAuthorize - SPL Token: TokenTransfer, CreateAssociatedTokenAccount, CloseAssociatedTokenAccount - Jito Stake Pool: StakePoolDepositSol, StakePoolWithdrawStake - Update parser types to include decimalPlaces and programId for TokenTransferParams, and add StakeInitializeParams - Add comprehensive TypeScript tests for all new instruction types: - Stake Program tests (initialize, delegate, deactivate, withdraw, full staking flow) - SPL Token tests (transfer, create ATA, close ATA, transfer with ATA) - Jito Stake Pool tests (deposit sol, withdraw stake, deposit with ATA) All 73 tests passing. Co-Authored-By: Claude Opus 4.5 --- package-lock.json | 28 +- packages/wasm-solana/js/builder.ts | 176 ++++++++++- packages/wasm-solana/js/index.ts | 16 + packages/wasm-solana/js/parser.ts | 12 + packages/wasm-solana/test/builder.ts | 421 ++++++++++++++++++++++++++- 5 files changed, 624 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6bbcfe9..c562fcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2960,7 +2960,6 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -3339,7 +3338,6 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -3570,7 +3568,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4671,7 +4668,6 @@ "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", @@ -5147,7 +5143,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5210,7 +5205,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5862,7 +5856,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001646", "electron-to-chromium": "^1.5.4", @@ -8015,7 +8008,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8858,8 +8850,7 @@ "node_modules/fp-ts": { "version": "2.16.9", "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.9.tgz", - "integrity": "sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==", - "peer": true + "integrity": "sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==" }, "node_modules/fresh": { "version": "0.5.2", @@ -10301,7 +10292,6 @@ "version": "2.2.21", "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.21.tgz", "integrity": "sha512-zz2Z69v9ZIC3mMLYWIeoUcwWD6f+O7yP92FMVVaXEOSZH1jnVBmET/urd/uoarD1WGBY4rCj8TAyMPzsGNzMFQ==", - "peer": true, "peerDependencies": { "fp-ts": "^2.5.0" } @@ -11800,7 +11790,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -12567,7 +12556,6 @@ "version": "2.3.13", "resolved": "https://registry.npmjs.org/monocle-ts/-/monocle-ts-2.3.13.tgz", "integrity": "sha512-D5Ygd3oulEoAm3KuGO0eeJIrhFf1jlQIoEVV2DYsZUMz42j4tGxgct97Aq68+F8w4w4geEnwFa8HayTS/7lpKQ==", - "peer": true, "peerDependencies": { "fp-ts": "^2.5.0" } @@ -12871,7 +12859,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/newtype-ts/-/newtype-ts-0.3.5.tgz", "integrity": "sha512-v83UEQMlVR75yf1OUdoSFssjitxzjZlqBAjiGQ4WJaML8Jdc68LJ+BaSAXUmKY4bNzp7hygkKLYTsDi14PxI2g==", - "peer": true, "peerDependencies": { "fp-ts": "^2.0.0", "monocle-ts": "^2.0.0" @@ -15296,7 +15283,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -15448,7 +15434,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -16755,7 +16740,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.1", @@ -17882,7 +17866,6 @@ "integrity": "sha512-6qGjWccl5yoyugHt3jTgztJ9Y0JVzyH8/Voc/D8PlLat9pwxQYXz7W1Dpnq5h0/G5GCYGUaDSlYcyk3AMh5A6g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -19995,7 +19978,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -20178,8 +20160,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/tsx": { "version": "4.20.6", @@ -20332,7 +20313,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20701,7 +20681,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, - "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -20748,7 +20727,6 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -20832,7 +20810,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -20945,7 +20922,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", diff --git a/packages/wasm-solana/js/builder.ts b/packages/wasm-solana/js/builder.ts index 6e6c14a..00c4003 100644 --- a/packages/wasm-solana/js/builder.ts +++ b/packages/wasm-solana/js/builder.ts @@ -114,6 +114,170 @@ export interface ComputeBudgetInstruction { unitPrice?: number; } +// ============================================================================= +// Stake Program Instructions +// ============================================================================= + +/** Initialize a stake account instruction */ +export interface StakeInitializeInstruction { + type: "stakeInitialize"; + /** Stake account address (base58) */ + stake: string; + /** Authorized staker pubkey (base58) */ + staker: string; + /** Authorized withdrawer pubkey (base58) */ + withdrawer: string; +} + +/** Delegate stake to a validator instruction */ +export interface StakeDelegateInstruction { + type: "stakeDelegate"; + /** Stake account address (base58) */ + stake: string; + /** Vote account (validator) to delegate to (base58) */ + vote: string; + /** Stake authority (base58) */ + authority: string; +} + +/** Deactivate a stake account instruction */ +export interface StakeDeactivateInstruction { + type: "stakeDeactivate"; + /** Stake account address (base58) */ + stake: string; + /** Stake authority (base58) */ + authority: string; +} + +/** Withdraw from a stake account instruction */ +export interface StakeWithdrawInstruction { + type: "stakeWithdraw"; + /** Stake account address (base58) */ + stake: string; + /** Recipient address (base58) */ + recipient: string; + /** Amount in lamports to withdraw (as string) */ + lamports: string; + /** Withdraw authority (base58) */ + authority: string; +} + +/** Change stake account authorization instruction */ +export interface StakeAuthorizeInstruction { + type: "stakeAuthorize"; + /** Stake account address (base58) */ + stake: string; + /** New authority pubkey (base58) */ + newAuthority: string; + /** Authorization type: "staker" or "withdrawer" */ + authorizeType: "staker" | "withdrawer"; + /** Current authority (base58) */ + authority: string; +} + +// ============================================================================= +// SPL Token Instructions +// ============================================================================= + +/** Transfer tokens instruction (uses TransferChecked) */ +export interface TokenTransferInstruction { + type: "tokenTransfer"; + /** Source token account (base58) */ + source: string; + /** Destination token account (base58) */ + destination: string; + /** Token mint address (base58) */ + mint: string; + /** Amount of tokens (as string, in smallest units) */ + amount: string; + /** Number of decimals for the token */ + decimals: number; + /** Owner/authority of the source account (base58) */ + authority: string; + /** Token program ID (optional, defaults to SPL Token) */ + programId?: string; +} + +/** Create an Associated Token Account instruction */ +export interface CreateAssociatedTokenAccountInstruction { + type: "createAssociatedTokenAccount"; + /** Payer for account creation (base58) */ + payer: string; + /** Owner of the new ATA (base58) */ + owner: string; + /** Token mint address (base58) */ + mint: string; + /** Token program ID (optional, defaults to SPL Token) */ + tokenProgramId?: string; +} + +/** Close an Associated Token Account instruction */ +export interface CloseAssociatedTokenAccountInstruction { + type: "closeAssociatedTokenAccount"; + /** Token account to close (base58) */ + account: string; + /** Destination for remaining lamports (base58) */ + destination: string; + /** Authority of the account (base58) */ + authority: string; + /** Token program ID (optional, defaults to SPL Token) */ + programId?: string; +} + +// ============================================================================= +// Jito Stake Pool Instructions +// ============================================================================= + +/** Deposit SOL into a stake pool (Jito liquid staking) */ +export interface StakePoolDepositSolInstruction { + type: "stakePoolDepositSol"; + /** Stake pool address (base58) */ + stakePool: string; + /** Withdraw authority PDA (base58) */ + withdrawAuthority: string; + /** Reserve stake account (base58) */ + reserveStake: string; + /** Funding account (SOL source, signer) (base58) */ + fundingAccount: string; + /** Destination for pool tokens (base58) */ + destinationPoolAccount: string; + /** Manager fee account (base58) */ + managerFeeAccount: string; + /** Referral pool account (base58) */ + referralPoolAccount: string; + /** Pool mint address (base58) */ + poolMint: string; + /** Amount in lamports to deposit (as string) */ + lamports: string; +} + +/** Withdraw stake from a stake pool (Jito liquid staking) */ +export interface StakePoolWithdrawStakeInstruction { + type: "stakePoolWithdrawStake"; + /** Stake pool address (base58) */ + stakePool: string; + /** Validator list account (base58) */ + validatorList: string; + /** Withdraw authority PDA (base58) */ + withdrawAuthority: string; + /** Validator stake account to split from (base58) */ + validatorStake: string; + /** Destination stake account (uninitialized) (base58) */ + destinationStake: string; + /** Authority for the destination stake account (base58) */ + destinationStakeAuthority: string; + /** Source pool token account authority (signer) (base58) */ + sourceTransferAuthority: string; + /** Source pool token account (base58) */ + sourcePoolAccount: string; + /** Manager fee account (base58) */ + managerFeeAccount: string; + /** Pool mint address (base58) */ + poolMint: string; + /** Amount of pool tokens to burn (as string) */ + poolTokens: string; +} + /** Union of all instruction types */ export type Instruction = | TransferInstruction @@ -123,7 +287,17 @@ export type Instruction = | AllocateInstruction | AssignInstruction | MemoInstruction - | ComputeBudgetInstruction; + | ComputeBudgetInstruction + | StakeInitializeInstruction + | StakeDelegateInstruction + | StakeDeactivateInstruction + | StakeWithdrawInstruction + | StakeAuthorizeInstruction + | TokenTransferInstruction + | CreateAssociatedTokenAccountInstruction + | CloseAssociatedTokenAccountInstruction + | StakePoolDepositSolInstruction + | StakePoolWithdrawStakeInstruction; // ============================================================================= // TransactionIntent diff --git a/packages/wasm-solana/js/index.ts b/packages/wasm-solana/js/index.ts index d85a4e2..bb116b6 100644 --- a/packages/wasm-solana/js/index.ts +++ b/packages/wasm-solana/js/index.ts @@ -34,12 +34,15 @@ export type { StakingWithdrawParams, StakingDelegateParams, StakingAuthorizeParams, + StakeInitializeParams, SetComputeUnitLimitParams, SetPriorityFeeParams, TokenTransferParams, CreateAtaParams, CloseAtaParams, MemoParams, + StakePoolDepositSolParams, + StakePoolWithdrawStakeParams, UnknownInstructionParams, } from "./parser.js"; @@ -58,4 +61,17 @@ export type { AssignInstruction, MemoInstruction, ComputeBudgetInstruction, + // Stake Program + StakeInitializeInstruction, + StakeDelegateInstruction, + StakeDeactivateInstruction, + StakeWithdrawInstruction, + StakeAuthorizeInstruction, + // SPL Token + TokenTransferInstruction, + CreateAssociatedTokenAccountInstruction, + CloseAssociatedTokenAccountInstruction, + // Jito Stake Pool + StakePoolDepositSolInstruction, + StakePoolWithdrawStakeInstruction, } from "./builder.js"; diff --git a/packages/wasm-solana/js/parser.ts b/packages/wasm-solana/js/parser.ts index ce61cb5..0df5e72 100644 --- a/packages/wasm-solana/js/parser.ts +++ b/packages/wasm-solana/js/parser.ts @@ -87,6 +87,14 @@ export interface StakingAuthorizeParams { authorizeType: "Staker" | "Withdrawer"; } +/** Stake initialize parameters (intermediate type) */ +export interface StakeInitializeParams { + type: "StakeInitialize"; + stakingAddress: string; + staker: string; + withdrawer: string; +} + /** Set compute unit limit parameters */ export interface SetComputeUnitLimitParams { type: "SetComputeUnitLimit"; @@ -107,6 +115,8 @@ export interface TokenTransferParams { amount: string; sourceAddress: string; tokenAddress?: string; + programId: string; + decimalPlaces?: number; } /** Create associated token account parameters */ @@ -116,6 +126,7 @@ export interface CreateAtaParams { ataAddress: string; ownerAddress: string; payerAddress: string; + programId: string; } /** Close associated token account parameters */ @@ -188,6 +199,7 @@ export type InstructionParams = | StakingWithdrawParams | StakingDelegateParams | StakingAuthorizeParams + | StakeInitializeParams | SetComputeUnitLimitParams | SetPriorityFeeParams | TokenTransferParams diff --git a/packages/wasm-solana/test/builder.ts b/packages/wasm-solana/test/builder.ts index 1f7648d..ffc784a 100644 --- a/packages/wasm-solana/test/builder.ts +++ b/packages/wasm-solana/test/builder.ts @@ -136,8 +136,8 @@ describe("buildTransaction", () => { const priorityFee = parsed.instructionsData[0]; if (priorityFee.type === "SetPriorityFee") { - // Parser uses 'fee' as a number - assert.strictEqual(priorityFee.fee, 5000); + // Parser uses 'fee' as BigInt + assert.strictEqual(priorityFee.fee, BigInt(5000)); } }); }); @@ -274,4 +274,421 @@ describe("buildTransaction", () => { assert.deepStrictEqual(txBytes1, txBytes2); }); }); + + // ===== Stake Program Tests ===== + describe("stake program", () => { + // From BitGoJS test/resources/sol.ts + const VALIDATOR = "CyjoLt3kjqB57K7ewCBHmnHq3UgEj3ak6A7m6EsBsuhA"; // validator.pub + + it("should build stake initialize instruction", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "stakeInitialize", + stake: STAKE_ACCOUNT, + staker: SENDER, + withdrawer: SENDER, + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "StakeInitialize"); + + const stakeInit = parsed.instructionsData[0]; + if (stakeInit.type === "StakeInitialize") { + assert.strictEqual(stakeInit.stakingAddress, STAKE_ACCOUNT); + assert.strictEqual(stakeInit.staker, SENDER); + assert.strictEqual(stakeInit.withdrawer, SENDER); + } + }); + + it("should build stake delegate instruction", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "stakeDelegate", + stake: STAKE_ACCOUNT, + vote: VALIDATOR, + authority: SENDER, + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "StakingDelegate"); + + const stakeDelegate = parsed.instructionsData[0]; + if (stakeDelegate.type === "StakingDelegate") { + assert.strictEqual(stakeDelegate.stakingAddress, STAKE_ACCOUNT); + assert.strictEqual(stakeDelegate.validator, VALIDATOR); + assert.strictEqual(stakeDelegate.fromAddress, SENDER); + } + }); + + it("should build stake deactivate instruction", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "stakeDeactivate", + stake: STAKE_ACCOUNT, + authority: SENDER, + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "StakingDeactivate"); + + const stakeDeactivate = parsed.instructionsData[0]; + if (stakeDeactivate.type === "StakingDeactivate") { + assert.strictEqual(stakeDeactivate.stakingAddress, STAKE_ACCOUNT); + assert.strictEqual(stakeDeactivate.fromAddress, SENDER); + } + }); + + it("should build stake withdraw instruction", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "stakeWithdraw", + stake: STAKE_ACCOUNT, + recipient: RECIPIENT, + lamports: "300000", + authority: SENDER, + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "StakingWithdraw"); + + const stakeWithdraw = parsed.instructionsData[0]; + if (stakeWithdraw.type === "StakingWithdraw") { + assert.strictEqual(stakeWithdraw.stakingAddress, STAKE_ACCOUNT); + assert.strictEqual(stakeWithdraw.fromAddress, SENDER); + assert.strictEqual(stakeWithdraw.amount, "300000"); + } + }); + + it("should build full staking activate flow", () => { + // Typical staking activate: CreateAccount + StakeInitialize + StakeDelegate + // The parser combines these into a single StakingActivate instruction + const STAKE_PROGRAM = "Stake11111111111111111111111111111111111111"; + + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "createAccount", + from: SENDER, + newAccount: STAKE_ACCOUNT, + lamports: "300000", + space: 200, // Stake account size + owner: STAKE_PROGRAM, + }, + { + type: "stakeInitialize", + stake: STAKE_ACCOUNT, + staker: SENDER, + withdrawer: SENDER, + }, + { + type: "stakeDelegate", + stake: STAKE_ACCOUNT, + vote: VALIDATOR, + authority: SENDER, + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + // Parser combines CreateAccount + StakeInitialize + StakeDelegate into StakingActivate + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "StakingActivate"); + + const stakingActivate = parsed.instructionsData[0]; + if (stakingActivate.type === "StakingActivate") { + assert.strictEqual(stakingActivate.stakingAddress, STAKE_ACCOUNT); + assert.strictEqual(stakingActivate.validator, VALIDATOR); + assert.strictEqual(stakingActivate.amount, "300000"); + assert.strictEqual(stakingActivate.stakingType, "NATIVE"); + } + }); + }); + + // ===== SPL Token Tests ===== + describe("spl token", () => { + // From BitGoJS test/resources/sol.ts + const MINT_USDC = "F4uLeXJoFz3hw13MposuwaQbMcZbCjqvEGPPeRRB1Byf"; // tokenTransfers.mintUSDC + const SOURCE_ATA = "2fyhC1YbqaYszkUQw2YGNRVkr2abr69UwFXVCjz4Q5f5"; // tokenTransfers.sourceUSDC + const DEST_ATA = "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH"; + + it("should build token transfer instruction", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "tokenTransfer", + source: SOURCE_ATA, + destination: DEST_ATA, + mint: MINT_USDC, + amount: "300000", + decimals: 9, + authority: SENDER, + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "TokenTransfer"); + + const tokenTransfer = parsed.instructionsData[0]; + if (tokenTransfer.type === "TokenTransfer") { + assert.strictEqual(tokenTransfer.sourceAddress, SOURCE_ATA); + assert.strictEqual(tokenTransfer.toAddress, DEST_ATA); + assert.strictEqual(tokenTransfer.amount, "300000"); + assert.strictEqual(tokenTransfer.tokenAddress, MINT_USDC); + assert.strictEqual(tokenTransfer.decimalPlaces, 9); + } + }); + + it("should build create associated token account instruction", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "createAssociatedTokenAccount", + payer: SENDER, + owner: RECIPIENT, + mint: MINT_USDC, + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "CreateAssociatedTokenAccount"); + + const createAta = parsed.instructionsData[0]; + if (createAta.type === "CreateAssociatedTokenAccount") { + assert.strictEqual(createAta.payerAddress, SENDER); + assert.strictEqual(createAta.ownerAddress, RECIPIENT); + assert.strictEqual(createAta.mintAddress, MINT_USDC); + } + }); + + it("should build close associated token account instruction", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "closeAssociatedTokenAccount", + account: SOURCE_ATA, + destination: SENDER, + authority: SENDER, + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "CloseAssociatedTokenAccount"); + + const closeAta = parsed.instructionsData[0]; + if (closeAta.type === "CloseAssociatedTokenAccount") { + assert.strictEqual(closeAta.accountAddress, SOURCE_ATA); + assert.strictEqual(closeAta.destinationAddress, SENDER); + assert.strictEqual(closeAta.authorityAddress, SENDER); + } + }); + + it("should build token transfer with create ATA", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "createAssociatedTokenAccount", + payer: SENDER, + owner: RECIPIENT, + mint: MINT_USDC, + }, + { + type: "tokenTransfer", + source: SOURCE_ATA, + destination: DEST_ATA, + mint: MINT_USDC, + amount: "300000", + decimals: 9, + authority: SENDER, + }, + { type: "memo", message: "test memo" }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 3); + assert.strictEqual(parsed.instructionsData[0].type, "CreateAssociatedTokenAccount"); + assert.strictEqual(parsed.instructionsData[1].type, "TokenTransfer"); + assert.strictEqual(parsed.instructionsData[2].type, "Memo"); + }); + }); + + // ===== Jito Stake Pool Tests ===== + describe("jito stake pool", () => { + // From BitGoJS Jito constants + const JITO_STAKE_POOL = "Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb"; + const JITO_WITHDRAW_AUTHORITY = "6iQKfEyhr3bZMotVkW6beNZz5CPAkiwvgV2CTje9pVSS"; + const JITO_RESERVE_STAKE = "BgKUXdS4Wy6Vdgp1jwT2dz5ZgxPG94aPL77dQscSPGmc"; + const JITO_POOL_MINT = "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn"; // JitoSOL + const MANAGER_FEE_ACCOUNT = "5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN"; + const VALIDATOR_LIST = "3R3nGZpQs2aZo5FDQvd2MUQ5R5E9g7NvHQaxpLPYA8r2"; + const VALIDATOR_STAKE = "BgKUXdS4Wy6Vdgp1jwT2dz5ZgxPG94aPL77dQscSPGmc"; + const DEST_STAKE = "FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH"; + const SOURCE_POOL_ACCOUNT = "5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN"; + + it("should build stake pool deposit sol instruction", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "stakePoolDepositSol", + stakePool: JITO_STAKE_POOL, + withdrawAuthority: JITO_WITHDRAW_AUTHORITY, + reserveStake: JITO_RESERVE_STAKE, + fundingAccount: SENDER, + destinationPoolAccount: SOURCE_POOL_ACCOUNT, + managerFeeAccount: MANAGER_FEE_ACCOUNT, + referralPoolAccount: MANAGER_FEE_ACCOUNT, + poolMint: JITO_POOL_MINT, + lamports: "300000", + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "StakePoolDepositSol"); + + const depositSol = parsed.instructionsData[0]; + if (depositSol.type === "StakePoolDepositSol") { + assert.strictEqual(depositSol.stakePool, JITO_STAKE_POOL); + assert.strictEqual(depositSol.fundingAccount, SENDER); + assert.strictEqual(depositSol.poolMint, JITO_POOL_MINT); + assert.strictEqual(depositSol.lamports, "300000"); + } + }); + + it("should build stake pool withdraw stake instruction", () => { + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "stakePoolWithdrawStake", + stakePool: JITO_STAKE_POOL, + validatorList: VALIDATOR_LIST, + withdrawAuthority: JITO_WITHDRAW_AUTHORITY, + validatorStake: VALIDATOR_STAKE, + destinationStake: DEST_STAKE, + destinationStakeAuthority: SENDER, + sourceTransferAuthority: SENDER, + sourcePoolAccount: SOURCE_POOL_ACCOUNT, + managerFeeAccount: MANAGER_FEE_ACCOUNT, + poolMint: JITO_POOL_MINT, + poolTokens: "300000", + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 1); + assert.strictEqual(parsed.instructionsData[0].type, "StakePoolWithdrawStake"); + + const withdrawStake = parsed.instructionsData[0]; + if (withdrawStake.type === "StakePoolWithdrawStake") { + assert.strictEqual(withdrawStake.stakePool, JITO_STAKE_POOL); + assert.strictEqual(withdrawStake.destinationStake, DEST_STAKE); + assert.strictEqual(withdrawStake.sourceTransferAuthority, SENDER); + assert.strictEqual(withdrawStake.poolMint, JITO_POOL_MINT); + assert.strictEqual(withdrawStake.poolTokens, "300000"); + } + }); + + it("should build jito deposit with create ATA", () => { + // Typical Jito deposit flow: Create ATA for JitoSOL + DepositSol + const intent: TransactionIntent = { + feePayer: SENDER, + nonce: { type: "blockhash", value: BLOCKHASH }, + instructions: [ + { + type: "createAssociatedTokenAccount", + payer: SENDER, + owner: SENDER, + mint: JITO_POOL_MINT, + }, + { + type: "stakePoolDepositSol", + stakePool: JITO_STAKE_POOL, + withdrawAuthority: JITO_WITHDRAW_AUTHORITY, + reserveStake: JITO_RESERVE_STAKE, + fundingAccount: SENDER, + destinationPoolAccount: SOURCE_POOL_ACCOUNT, + managerFeeAccount: MANAGER_FEE_ACCOUNT, + referralPoolAccount: MANAGER_FEE_ACCOUNT, + poolMint: JITO_POOL_MINT, + lamports: "1000000000", // 1 SOL + }, + ], + }; + + const txBytes = buildTransaction(intent); + const parsed = parseTransaction(txBytes); + + assert.strictEqual(parsed.instructionsData.length, 2); + assert.strictEqual(parsed.instructionsData[0].type, "CreateAssociatedTokenAccount"); + assert.strictEqual(parsed.instructionsData[1].type, "StakePoolDepositSol"); + }); + }); });