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/Cargo.lock b/packages/wasm-solana/Cargo.lock index 27ddb0a..370b2c9 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,18 +85,60 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" 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" @@ -49,12 +157,119 @@ 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 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "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 1.0.109", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -83,12 +298,54 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -98,6 +355,24 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +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" @@ -105,9 +380,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" @@ -134,6 +419,7 @@ dependencies = [ "fiat-crypto", "rand_core 0.6.4", "rustc_version", + "serde", "subtle", "zeroize", ] @@ -146,7 +432,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -159,6 +445,12 @@ dependencies = [ "zeroize", ] +[[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" @@ -175,10 +467,25 @@ 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" @@ -206,7 +513,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek 3.2.0", "ed25519 1.5.3", - "rand", + "rand 0.7.3", "serde", "sha2 0.9.9", "zeroize", @@ -220,12 +527,72 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "feature-probe" +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" @@ -247,6 +614,15 @@ dependencies = [ "five8_core", ] +[[package]] +name = "five8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f76610e969fa1784327ded240f1e28a3fd9520c9cec93b636fcf62dd37f772" +dependencies = [ + "five8_core", +] + [[package]] name = "five8_const" version = "0.1.4" @@ -256,12 +632,33 @@ dependencies = [ "five8_core", ] +[[package]] +name = "five8_const" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0f1728185f277989ca573a402716ae0beaaea3f76a8ff87ef9dd8fb19436c5" +dependencies = [ + "five8_core", +] + [[package]] name = "five8_core" version = "0.1.2" 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" @@ -270,6 +667,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -297,11 +695,48 @@ dependencies = [ ] [[package]] -name = "hex" -version = "0.4.3" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.12.1" @@ -311,6 +746,34 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 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]] name = "itoa" version = "1.0.17" @@ -327,6 +790,35 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.180" @@ -339,6 +831,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" @@ -348,12 +886,39 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memchr" version = "2.7.6" 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" @@ -373,6 +938,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" @@ -383,6 +978,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" @@ -433,6 +1050,12 @@ 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 = "pkcs8" version = "0.10.2" @@ -443,6 +1066,18 @@ dependencies = [ "spki", ] +[[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" @@ -452,6 +1087,24 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +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", +] + [[package]] name = "proc-macro2" version = "1.0.105" @@ -461,6 +1114,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" @@ -478,11 +1140,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" @@ -494,343 +1167,2620 @@ dependencies = [ ] [[package]] -name = "rand_core" -version = "0.5.1" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +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" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +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" +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" +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" +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" +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", +] + +[[package]] +name = "solana-address" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ecac8e1b7f74c2baa9e774c42817e3e75b20787134b76cc4d45e8a604488f5" +dependencies = [ + "solana-address 2.0.0", +] + +[[package]] +name = "solana-address" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37320fd2945c5d654b2c6210624a52d66c3f1f73b653ed211ab91a703b35bdd" +dependencies = [ + "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", + "solana-define-syscall 4.0.1", + "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-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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52e52720efe60465b052b9e7445a01c17550666beec855cce66f44766697bc2" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "solana-atomic-u64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a933ff1e50aff72d02173cfcd7511bd8540b027ee720b75f353f594f834216d0" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "solana-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-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" +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-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" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb62e9381182459a4520b5fe7fb22d423cae736239a6427fc398a88743d0ed59" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.0", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-compute-budget-interface" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8292c436b269ad23cecc8b24f7da3ab07ca111661e25e00ce0e1d22771951ab9" +dependencies = [ + "borsh 1.6.0", + "solana-instruction 3.1.0", + "solana-sdk-ids 3.1.0", +] + +[[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 3.1.0", + "solana-define-syscall 4.0.1", + "solana-instruction 3.1.0", + "solana-program-error 3.0.0", + "solana-pubkey 4.0.0", + "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]] +name = "solana-decode-error" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c781686a18db2f942e70913f7ca15dc120ec38dcab42ff7557db2c70c625a35" +dependencies = [ + "num-traits", +] + +[[package]] +name = "solana-define-syscall" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae3e2abcf541c8122eafe9a625d4d194b4023c20adde1e251f94e056bb1aee2" + +[[package]] +name = "solana-define-syscall" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9697086a4e102d28a156b8d6b521730335d6951bd39a5e766512bbe09007cee" + +[[package]] +name = "solana-define-syscall" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e5b1c0bc1d4a4d10c88a4100499d954c09d3fecfae4912c1a074dff68b1738" + +[[package]] +name = "solana-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-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" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b319a4ed70390af911090c020571f0ff1f4ec432522d05ab89f5c08080381995" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 3.1.0", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.0", + "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" +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]] +name = "solana-epoch-schedule" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5481e72cc4d52c169db73e4c0cd16de8bc943078aac587ec4817a75cc6388f" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.0", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84461d56cbb8bb8d539347151e0525b53910102e4bced875d49d5139708e39d3" +dependencies = [ + "serde", + "serde_derive", + "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 2.2.1", + "solana-message 2.4.0", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f5c5382b449e8e4e3016fb05e418c53d57782d8b5c30aa372fc265654b956d" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-account 2.2.1", + "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]] +name = "solana-fee-calculator" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a73cc03ca4bed871ca174558108835f8323e85917bb38b9c81c7af2ab853efe" +dependencies = [ + "log", + "serde", + "serde_derive", +] + +[[package]] +name = "solana-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" +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", +] + +[[package]] +name = "solana-hash" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "337c246447142f660f778cf6cb582beba8e28deb05b3b24bfb9ffd7c562e5f41" +dependencies = [ + "solana-hash 4.0.1", +] + +[[package]] +name = "solana-hash" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5d48a6ee7b91fc7b998944ab026ed7b3e2fc8ee3bc58452644a86c2648152f" +dependencies = [ + "borsh 1.6.0", + "bytemuck", + "bytemuck_derive", + "five8 1.0.0", + "serde", + "serde_derive", + "solana-atomic-u64 3.0.0", + "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" +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", +] + +[[package]] +name = "solana-instruction" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1b699a2c1518028a9982e255e0eca10c44d90006542d9d7f9f40dbce3f7c78" +dependencies = [ + "bincode", + "borsh 1.6.0", + "serde", + "serde_derive", + "solana-define-syscall 4.0.1", + "solana-instruction-error", + "solana-pubkey 4.0.0", +] + +[[package]] +name = "solana-instruction-error" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b04259e03c05faf38a8c24217b5cfe4c90572ae6184ab49cddb1584fdd756d3f" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-program-error 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 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" +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]] +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 1.0.1", + "five8 0.2.1", + "rand 0.7.3", + "solana-pubkey 2.4.0", + "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" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcda154ec827f5fc1e4da0af3417951b7e9b8157540f81f936c4a8b1156134d0" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.0", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-loader-v2-interface" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8ab08006dad78ae7cd30df8eea0539e207d08d91eaefb3e1d49a446e1c49654" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", +] + +[[package]] +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", + "blake3", + "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-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" +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-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" +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 2.2.2", + "solana-atomic-u64 2.2.1", + "solana-big-mod-exp 2.2.1", + "solana-bincode", + "solana-blake3-hasher 2.2.1", + "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 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 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 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 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 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", + "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" +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" +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 3.1.0", + "solana-define-syscall 4.0.1", + "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" +dependencies = [ + "serde", + "serde_derive", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4068648649653c2c50546e9a7fb761791b5ab0cda054c771bb5808d3a4b9eb52" +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-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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "319f0ef15e6e12dc37c597faccb7d62525a509fec5f6975ecb9419efddeb277b" +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" +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", + "solana-sanitize 2.2.1", + "solana-sha256-hasher 2.3.0", + "wasm-bindgen", +] + +[[package]] +name = "solana-pubkey" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8909d399deb0851aa524420beeb5646b115fd253ef446e35fe4504c904da3941" +dependencies = [ + "rand 0.8.5", + "solana-address 1.1.0", +] + +[[package]] +name = "solana-pubkey" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f7104d456b58e1418c21a8581e89810278d1190f70f27ece7fc0b2c9282a57" +dependencies = [ + "solana-address 2.0.0", +] + +[[package]] +name = "solana-rent" +version = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e860d5499a705369778647e97d760f7670adfb6fc8419dd3d568deccd46d5487" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sdk-macro 3.0.0", + "solana-sysvar-id 3.1.0", +] + +[[package]] +name = "solana-sanitize" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" + +[[package]] +name = "solana-sanitize" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" + +[[package]] +name = "solana-sdk" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5d8b9cc68d5c88b062a33e23a6466722467dde0035152d8fb1afbcdf350a5f" +dependencies = [ + "solana-pubkey 2.4.0", +] + +[[package]] +name = "solana-sdk-ids" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def234c1956ff616d46c9dd953f251fa7096ddbaa6d52b165218de97882b7280" +dependencies = [ + "solana-address 2.0.0", +] + +[[package]] +name = "solana-sdk-macro" +version = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6430000e97083460b71d9fbadc52a2ab2f88f53b3a4c5e58c5ae3640a0e8c00" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "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-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" +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 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]] +name = "solana-seed-phrase" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36187af2324f079f65a675ec22b31c24919cb4ac22c79472e85d819db9bbbc15" +dependencies = [ + "hmac", + "pbkdf2", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a7e155eba458ecfb0107b98236088c3764a09ddf0201ec29e52a0be40857113" +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" +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-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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa3feb32c28765f6aa1ce8f3feac30936f16c5c3f7eb73d63a5b8f6f8ecdc44" +dependencies = [ + "sha2 0.10.9", + "solana-define-syscall 2.3.0", + "solana-hash 2.3.0", +] + +[[package]] +name = "solana-sha256-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7dc3011ea4c0334aaaa7e7128cb390ecf546b28d412e9bf2064680f57f588f" +dependencies = [ + "sha2 0.10.9", + "solana-define-syscall 4.0.1", + "solana-hash 4.0.1", +] + +[[package]] +name = "solana-short-vec" +version = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79fb1809a32cfcf7d9c47b7070a92fa17cdb620ab5829e9a8a9ff9d138a7a175" +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 1.0.1", + "five8 0.2.1", + "solana-sanitize 2.2.1", +] + +[[package]] +name = "solana-signature" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb8057cc0e9f7b5e89883d49de6f407df655bb6f3a71d0b7baf9986a2218fd9" +dependencies = [ + "ed25519-dalek 2.2.0", + "five8 0.2.1", + "rand 0.8.5", + "serde", + "serde-big-array", + "serde_derive", + "solana-sanitize 3.0.1", +] + +[[package]] +name = "solana-signer" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" +dependencies = [ + "solana-pubkey 2.4.0", + "solana-signature 2.3.0", + "solana-transaction-error 2.2.1", +] + +[[package]] +name = "solana-signer" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bfea97951fee8bae0d6038f39a5efcb6230ecdfe33425ac75196d1a1e3e3235" +dependencies = [ + "solana-pubkey 3.0.0", + "solana-signature 3.1.0", + "solana-transaction-error 3.0.0", +] + +[[package]] +name = "solana-slot-hashes" +version = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80a293f952293281443c04f4d96afd9d547721923d596e92b4377ed2360f1746" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 3.1.0", + "solana-sdk-ids 3.1.0", + "solana-sysvar-id 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]] +name = "solana-slot-history" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f914f6b108f5bba14a280b458d023e3621c9973f27f015a4d755b50e88d89e97" +dependencies = [ + "bv", + "serde", + "serde_derive", + "solana-sdk-ids 3.1.0", + "solana-sysvar-id 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]] +name = "solana-stable-layout" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1da74507795b6e8fb60b7c7306c0c36e2c315805d16eaaf479452661234685ac" +dependencies = [ + "solana-instruction 3.1.0", + "solana-pubkey 3.0.0", +] + +[[package]] +name = "solana-stake-interface" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5269e89fde216b4d7e1d1739cf5303f8398a1ff372a81232abbee80e554a838c" +dependencies = [ + "borsh 0.10.4", + "borsh 1.6.0", + "num-traits", + "serde", + "serde_derive", + "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-stake-interface" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9bc26191b533f9a6e5a14cca05174119819ced680a80febff2f5051a713f0db" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-clock 3.0.0", + "solana-cpi 3.1.0", + "solana-instruction 3.1.0", + "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-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-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" +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 2.2.2", + "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 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "lazy_static", + "serde", + "serde_derive", + "solana-account-info 3.1.0", + "solana-clock 3.0.0", + "solana-define-syscall 4.0.1", + "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 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 3.1.0", + "solana-sdk-ids 3.1.0", + "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]] +name = "solana-sysvar-id" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17358d1e9a13e5b9c2264d301102126cf11a47fd394cdf3dec174fe7bc96e1de" +dependencies = [ + "solana-address 2.0.0", + "solana-sdk-ids 3.1.0", +] + +[[package]] +name = "solana-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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ceb2efbf427a91b884709ffac4dac29117752ce1e37e9ae04977e450aa0bb76" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-address 2.0.0", + "solana-hash 4.0.1", + "solana-instruction 3.1.0", + "solana-instruction-error", + "solana-message 3.0.1", + "solana-sanitize 3.0.1", + "solana-sdk-ids 3.1.0", + "solana-short-vec 3.1.0", + "solana-signature 3.1.0", + "solana-signer 3.0.0", + "solana-transaction-error 3.0.0", +] + +[[package]] +name = "solana-transaction-error" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" +dependencies = [ + "solana-instruction 2.3.3", + "solana-sanitize 2.2.1", +] + +[[package]] +name = "solana-transaction-error" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4222065402340d7e6aec9dc3e54d22992ddcf923d91edcd815443c2bfca3144a" +dependencies = [ + "serde", + "serde_derive", + "solana-instruction-error", + "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 2.2.2", + "solana-serialize-utils 2.2.1", + "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 2.2.1", + "solana-instruction 2.3.3", + "solana-pubkey 2.4.0", + "solana-sdk-ids 2.2.1", + "solana-seed-derivable 2.2.1", + "solana-seed-phrase 2.2.1", + "solana-signature 2.3.0", + "solana-signer 2.2.1", + "subtle", + "thiserror 2.0.18", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "spki" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ - "getrandom 0.1.16", + "base64ct", + "der", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "spl-discriminator" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "a7398da23554a31660f17718164e31d31900956054f54f52d5ec1be51cb4f4b3" dependencies = [ - "getrandom 0.2.17", + "bytemuck", + "solana-program-error 2.2.2", + "solana-sha256-hasher 2.3.0", + "spl-discriminator-derive", ] [[package]] -name = "rand_hc" +name = "spl-discriminator-derive" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "d9e8418ea6269dcfb01c712f0444d2c75542c04448b480e87de59d2865edc750" dependencies = [ - "rand_core 0.5.1", + "quote", + "spl-discriminator-syn", + "syn 2.0.114", ] [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "spl-discriminator-syn" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "5d1dbc82ab91422345b6df40a79e2b78c7bce1ebb366da323572dd60b7076b67" dependencies = [ - "bitflags", + "proc-macro2", + "quote", + "sha2 0.10.9", + "syn 2.0.114", + "thiserror 1.0.69", ] [[package]] -name = "rustc_version" -version = "0.4.1" +name = "spl-elgamal-registry" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +checksum = "56cc66fe64651a48c8deb4793d8a5deec8f8faf19f355b9df294387bc5a36b5f" dependencies = [ - "semver", + "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 = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "same-file" -version = "1.0.6" +name = "spl-memo" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +checksum = "9f09647c0974e33366efeb83b8e2daebb329f0420149e74d3a4bd2c08cf9f7cb" dependencies = [ - "winapi-util", + "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 = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "serde" -version = "1.0.228" +name = "spl-pod" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "d994afaf86b779104b4a95ba9ca75b8ced3fdb17ee934e38cb69e72afbe17799" dependencies = [ - "serde_core", - "serde_derive", + "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 2.2.1", + "solana-pubkey 2.4.0", + "solana-zk-sdk", + "thiserror 2.0.18", ] [[package]] -name = "serde_core" -version = "1.0.228" +name = "spl-program-error" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "9cdebc8b42553070b75aa5106f071fef2eb798c64a7ec63375da4b1f058688c6" dependencies = [ - "serde_derive", + "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 = "serde_derive" -version = "1.0.228" +name = "spl-program-error-derive" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "2a2539e259c66910d78593475540e8072f0b10f0f61d7607bbf7593899ed52d0" dependencies = [ "proc-macro2", "quote", - "syn", + "sha2 0.10.9", + "syn 2.0.114", ] [[package]] -name = "serde_json" -version = "1.0.149" +name = "spl-stake-pool" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "6f0db03f091f43b5766296e80088718491b50949cd3eb4cce3e0cfed58fe2c18" dependencies = [ - "itoa", - "memchr", + "arrayref", + "bincode", + "borsh 1.6.0", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", "serde", - "serde_core", - "zmij", + "serde_derive", + "solana-program 2.3.0", + "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 = "sha2" -version = "0.9.9" +name = "spl-tlv-account-resolution" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +checksum = "1408e961215688715d5a1063cbdcf982de225c45f99c82b4f7d7e1dd22b998d7" dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "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 = "sha2" -version = "0.10.9" +name = "spl-token" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "053067c6a82c705004f91dae058b11b4780407e9ccd6799dc9e7d0fab5f242da" dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", + "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 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", + "solana-sysvar 2.3.0", + "thiserror 2.0.18", ] [[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signature" -version = "1.6.4" +name = "spl-token-2022" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" - -[[package]] -name = "signature" -version = "2.2.0" +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 2.3.0", + "solana-program-entrypoint 2.3.0", + "solana-program-error 2.2.2", + "solana-program-memory 2.3.1", + "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", + "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 = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "cddd52bfc0f1c677b41493dafa3f2dbbb4b47cf0990f08905429e19dc8289b35" dependencies = [ - "rand_core 0.6.4", + "base64 0.22.1", + "bytemuck", + "solana-curve25519", + "solana-zk-sdk", ] [[package]] -name = "smallvec" -version = "1.15.1" +name = "spl-token-confidential-transfer-proof-extraction" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "512c85bdbbb4cbcc2038849a9e164c958b16541f252b53ea1a3933191c0a4a1a" +dependencies = [ + "bytemuck", + "solana-account-info 2.3.0", + "solana-curve25519", + "solana-instruction 2.3.3", + "solana-instructions-sysvar 2.2.2", + "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 = "solana-atomic-u64" -version = "2.2.1" +name = "spl-token-confidential-transfer-proof-generation" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52e52720efe60465b052b9e7445a01c17550666beec855cce66f44766697bc2" +checksum = "fa27b9174bea869a7ebf31e0be6890bce90b1a4288bc2bbf24bd413f80ae3fde" dependencies = [ - "parking_lot", + "curve25519-dalek 4.1.3", + "solana-zk-sdk", + "thiserror 2.0.18", ] [[package]] -name = "solana-decode-error" -version = "2.3.0" +name = "spl-token-group-interface" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c781686a18db2f942e70913f7ca15dc120ec38dcab42ff7557db2c70c625a35" +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 = "solana-define-syscall" -version = "2.3.0" +name = "spl-token-metadata-interface" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae3e2abcf541c8122eafe9a625d4d194b4023c20adde1e251f94e056bb1aee2" +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 = "solana-hash" -version = "2.3.0" +name = "spl-transfer-hook-interface" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b96e9f0300fa287b545613f007dfe20043d7812bee255f418c1eb649c93b63" +checksum = "a7e905b849b6aba63bde8c4badac944ebb6c8e6e14817029cbe1bc16829133bd" dependencies = [ - "five8", - "js-sys", - "solana-atomic-u64", - "solana-sanitize", - "wasm-bindgen", + "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 = "solana-instruction" -version = "2.3.3" +name = "spl-type-length-value" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab5682934bd1f65f8d2c16f21cb532526fcc1a09f796e2cacdb091eee5774ad" +checksum = "d417eb548214fa822d93f84444024b4e57c13ed6719d4dcc68eec24fb481e9f5" dependencies = [ - "getrandom 0.2.17", - "js-sys", + "bytemuck", + "num-derive", "num-traits", - "solana-define-syscall", - "solana-pubkey", - "wasm-bindgen", + "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 = "solana-keypair" -version = "2.2.3" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd3f04aa1a05c535e93e121a95f66e7dcccf57e007282e8255535d24bf1e98bb" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "ed25519-dalek 1.0.1", - "five8", - "rand", - "solana-pubkey", - "solana-seed-phrase", - "solana-signature", - "solana-signer", - "wasm-bindgen", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "solana-pubkey" -version = "2.4.0" +name = "syn" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b62adb9c3261a052ca1f999398c388f1daf558a1b492f60a6d9e64857db4ff1" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ - "curve25519-dalek 4.1.3", - "five8", - "five8_const", - "getrandom 0.2.17", - "js-sys", - "num-traits", - "solana-atomic-u64", - "solana-decode-error", - "solana-define-syscall", - "solana-sanitize", - "solana-sha256-hasher", - "wasm-bindgen", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "solana-sanitize" -version = "2.2.1" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] [[package]] -name = "solana-seed-phrase" -version = "2.2.1" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36187af2324f079f65a675ec22b31c24919cb4ac22c79472e85d819db9bbbc15" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "hmac", - "pbkdf2", - "sha2 0.10.9", + "thiserror-impl 2.0.18", ] [[package]] -name = "solana-sha256-hasher" -version = "2.3.0" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa3feb32c28765f6aa1ce8f3feac30936f16c5c3f7eb73d63a5b8f6f8ecdc44" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "sha2 0.10.9", - "solana-define-syscall", - "solana-hash", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] -name = "solana-signature" -version = "2.3.0" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c8ec8e657aecfc187522fc67495142c12f35e55ddeca8698edbb738b8dbd8c" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ - "ed25519-dalek 1.0.1", - "five8", - "solana-sanitize", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] -name = "solana-signer" -version = "2.2.1" +name = "tinyvec" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ - "solana-pubkey", - "solana-signature", - "solana-transaction-error", + "tinyvec_macros", ] [[package]] -name = "solana-transaction-error" -version = "2.2.1" +name = "tinyvec_macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ - "solana-instruction", - "solana-sanitize", + "serde", ] [[package]] -name = "spki" -version = "0.7.3" +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ - "base64ct", - "der", + "serde_core", ] [[package]] -name = "subtle" -version = "2.6.1" +name = "toml_edit" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] [[package]] -name = "syn" -version = "2.0.114" +name = "toml_parser" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "winnow", ] [[package]] @@ -845,6 +3795,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" @@ -918,7 +3888,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -960,21 +3930,30 @@ checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] name = "wasm-solana" version = "0.1.0" dependencies = [ - "ed25519-dalek 2.2.0", + "base64 0.22.1", + "bincode", + "borsh 1.6.0", "hex", "js-sys", "serde", + "serde-wasm-bindgen", "serde_json", - "solana-keypair", - "solana-pubkey", - "solana-signer", + "solana-compute-budget-interface", + "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 3.0.0", + "solana-transaction", + "spl-stake-pool", "wasm-bindgen", "wasm-bindgen-test", ] @@ -1013,6 +3992,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + [[package]] name = "zerocopy" version = "0.8.33" @@ -1030,7 +4018,7 @@ checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1050,7 +4038,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 3cb7dad..c8f84de 100644 --- a/packages/wasm-solana/Cargo.toml +++ b/packages/wasm-solana/Cargo.toml @@ -12,17 +12,28 @@ all = "warn" [dependencies] wasm-bindgen = "0.2" js-sys = "0.3" -# Solana SDK crates +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +# 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" -# Ed25519 for deriving pubkey from 32-byte seed (solana-keypair expects 64-byte format) -ed25519-dalek = { version = "2.1", default-features = false, features = ["std"] } +# 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] wasm-bindgen-test = "0.3" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" hex = "0.4" [profile.release] diff --git a/packages/wasm-solana/js/builder.ts b/packages/wasm-solana/js/builder.ts new file mode 100644 index 0000000..00c4003 --- /dev/null +++ b/packages/wasm-solana/js/builder.ts @@ -0,0 +1,381 @@ +/** + * 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; +} + +// ============================================================================= +// 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 + | CreateAccountInstruction + | NonceAdvanceInstruction + | NonceInitializeInstruction + | AllocateInstruction + | AssignInstruction + | MemoInstruction + | ComputeBudgetInstruction + | StakeInitializeInstruction + | StakeDelegateInstruction + | StakeDeactivateInstruction + | StakeWithdrawInstruction + | StakeAuthorizeInstruction + | TokenTransferInstruction + | CreateAssociatedTokenAccountInstruction + | CloseAssociatedTokenAccountInstruction + | StakePoolDepositSolInstruction + | StakePoolWithdrawStakeInstruction; + +// ============================================================================= +// 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 217c524..bb116b6 100644 --- a/packages/wasm-solana/js/index.ts +++ b/packages/wasm-solana/js/index.ts @@ -6,7 +6,72 @@ void wasm; // Namespace exports for explicit imports export * as keypair from "./keypair.js"; export * as pubkey from "./pubkey.js"; +export * as transaction from "./transaction.js"; +export * as parser from "./parser.js"; +export * as builder from "./builder.js"; // Top-level class exports for convenience export { Keypair } from "./keypair.js"; export { Pubkey } from "./pubkey.js"; +export { Transaction } from "./transaction.js"; + +// Top-level function exports +export { parseTransaction } from "./parser.js"; +export { buildTransaction } from "./builder.js"; + +// Type exports +export type { AccountMeta, Instruction } from "./transaction.js"; +export type { + ParsedTransaction, + DurableNonce, + InstructionParams, + TransferParams, + CreateAccountParams, + NonceAdvanceParams, + CreateNonceAccountParams, + StakingActivateParams, + StakingDeactivateParams, + StakingWithdrawParams, + StakingDelegateParams, + StakingAuthorizeParams, + StakeInitializeParams, + SetComputeUnitLimitParams, + SetPriorityFeeParams, + TokenTransferParams, + CreateAtaParams, + CloseAtaParams, + MemoParams, + StakePoolDepositSolParams, + StakePoolWithdrawStakeParams, + 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, + // 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 new file mode 100644 index 0000000..0df5e72 --- /dev/null +++ b/packages/wasm-solana/js/parser.ts @@ -0,0 +1,321 @@ +/** + * High-level transaction parsing. + * + * Provides types and functions for parsing Solana transactions into semantic data + * matching BitGoJS's TxData format. + */ + +import { ParserNamespace } from "./wasm/wasm_solana.js"; + +// ============================================================================= +// Instruction Types - matching BitGoJS InstructionParams +// ============================================================================= + +/** SOL transfer parameters */ +export interface TransferParams { + type: "Transfer"; + fromAddress: string; + toAddress: string; + amount: string; +} + +/** Create account parameters */ +export interface CreateAccountParams { + type: "CreateAccount"; + fromAddress: string; + newAddress: string; + amount: string; + space: number; + owner: string; +} + +/** Nonce advance parameters */ +export interface NonceAdvanceParams { + type: "NonceAdvance"; + walletNonceAddress: string; + authWalletAddress: string; +} + +/** Create nonce account parameters */ +export interface CreateNonceAccountParams { + type: "CreateNonceAccount"; + fromAddress: string; + nonceAddress: string; + authAddress: string; + amount: string; +} + +/** Staking activate parameters */ +export interface StakingActivateParams { + type: "StakingActivate"; + fromAddress: string; + stakingAddress: string; + amount: string; + validator: string; + stakingType: "NATIVE" | "JITO" | "MARINADE"; +} + +/** Staking deactivate parameters */ +export interface StakingDeactivateParams { + type: "StakingDeactivate"; + stakingAddress: string; + fromAddress: string; +} + +/** Staking withdraw parameters */ +export interface StakingWithdrawParams { + type: "StakingWithdraw"; + fromAddress: string; + stakingAddress: string; + amount: string; +} + +/** Staking delegate parameters */ +export interface StakingDelegateParams { + type: "StakingDelegate"; + stakingAddress: string; + fromAddress: string; + validator: string; +} + +/** Staking authorize parameters */ +export interface StakingAuthorizeParams { + type: "StakingAuthorize"; + stakingAddress: string; + oldAuthorizeAddress: string; + newAuthorizeAddress: string; + authorizeType: "Staker" | "Withdrawer"; +} + +/** 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"; + units: number; +} + +/** Set priority fee parameters */ +export interface SetPriorityFeeParams { + type: "SetPriorityFee"; + fee: bigint; +} + +/** Token transfer parameters */ +export interface TokenTransferParams { + type: "TokenTransfer"; + fromAddress: string; + toAddress: string; + amount: string; + sourceAddress: string; + tokenAddress?: string; + programId: string; + decimalPlaces?: number; +} + +/** Create associated token account parameters */ +export interface CreateAtaParams { + type: "CreateAssociatedTokenAccount"; + mintAddress: string; + ataAddress: string; + ownerAddress: string; + payerAddress: string; + programId: string; +} + +/** Close associated token account parameters */ +export interface CloseAtaParams { + type: "CloseAssociatedTokenAccount"; + accountAddress: string; + destinationAddress: string; + authorityAddress: string; +} + +/** Memo parameters */ +export interface MemoParams { + type: "Memo"; + memo: string; +} + +/** 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; + isSigner: boolean; + isWritable: boolean; +} + +/** Unknown instruction parameters */ +export interface UnknownInstructionParams { + type: "Unknown"; + programId: string; + accounts: AccountMeta[]; + data: string; // base64 encoded +} + +/** Union of all instruction parameter types */ +export type InstructionParams = + | TransferParams + | CreateAccountParams + | NonceAdvanceParams + | CreateNonceAccountParams + | StakingActivateParams + | StakingDeactivateParams + | StakingWithdrawParams + | StakingDelegateParams + | StakingAuthorizeParams + | StakeInitializeParams + | SetComputeUnitLimitParams + | SetPriorityFeeParams + | TokenTransferParams + | CreateAtaParams + | CloseAtaParams + | MemoParams + | StakePoolDepositSolParams + | StakePoolWithdrawStakeParams + | UnknownInstructionParams; + +// ============================================================================= +// ParsedTransaction - matching BitGoJS TxData +// ============================================================================= + +/** Durable nonce information */ +export interface DurableNonce { + walletNonceAddress: string; + authWalletAddress: string; +} + +/** + * A fully parsed Solana transaction with decoded instructions. + * + * This structure matches BitGoJS's TxData interface for seamless integration. + */ +export interface ParsedTransaction { + /** The fee payer address (base58) */ + feePayer: string; + + /** Number of required signatures */ + numSignatures: number; + + /** The blockhash or nonce value (base58) */ + nonce: string; + + /** If this is a durable nonce transaction, contains the nonce info */ + durableNonce?: DurableNonce; + + /** All decoded instructions with semantic types */ + instructionsData: InstructionParams[]; + + /** All signatures (base64 encoded) */ + signatures: string[]; + + /** All account keys (base58 strings) */ + accountKeys: string[]; +} + +// ============================================================================= +// parseTransaction function +// ============================================================================= + +/** Raw instruction from WASM before post-processing */ +interface RawInstruction { + type: string; + fee?: string; // Fee comes as string from WASM + [key: string]: unknown; +} + +/** Raw parsed transaction from WASM before post-processing */ +interface RawParsedTransaction { + feePayer: string; + numSignatures: number; + nonce: string; + durableNonce?: DurableNonce; + instructionsData: RawInstruction[]; + signatures: string[]; + accountKeys: string[]; +} + +/** + * Parse a serialized Solana transaction into structured data. + * + * This is the main entry point for transaction parsing. It deserializes the + * transaction bytes and decodes all instructions into semantic types. + * + * Note: This returns the raw parsed data including NonceAdvance instructions. + * Consumers (like BitGoJS) may choose to filter NonceAdvance from instructionsData + * since that info is also available in durableNonce. + * + * @param bytes - The raw transaction bytes (wire format) + * @returns A ParsedTransaction with all instructions decoded + * @throws Error if the transaction cannot be parsed + * + * @example + * ```typescript + * import { parseTransaction } from '@bitgo/wasm-solana'; + * + * const txBytes = Buffer.from(base64EncodedTx, 'base64'); + * const parsed = parseTransaction(txBytes); + * + * console.log(parsed.feePayer); + * for (const instr of parsed.instructionsData) { + * if (instr.type === 'Transfer') { + * console.log(`Transfer ${instr.amount} from ${instr.fromAddress} to ${instr.toAddress}`); + * } + * } + * ``` + */ +export function parseTransaction(bytes: Uint8Array): ParsedTransaction { + const raw = ParserNamespace.parse_transaction(bytes) as RawParsedTransaction; + + // Post-process instructions: + // Convert SetPriorityFee.fee from string to BigInt + const instructionsData = raw.instructionsData.map((instr): InstructionParams => { + if (instr.type === "SetPriorityFee" && typeof instr.fee === "string") { + return { + type: "SetPriorityFee", + fee: BigInt(instr.fee), + }; + } + return instr as unknown as InstructionParams; + }); + + return { + ...raw, + instructionsData, + }; +} diff --git a/packages/wasm-solana/js/transaction.ts b/packages/wasm-solana/js/transaction.ts new file mode 100644 index 0000000..a29baf8 --- /dev/null +++ b/packages/wasm-solana/js/transaction.ts @@ -0,0 +1,132 @@ +import { WasmTransaction } from "./wasm/wasm_solana.js"; +import { Pubkey } from "./pubkey.js"; + +/** + * Account metadata for an instruction + */ +export interface AccountMeta { + /** The account public key as a base58 string */ + pubkey: string; + /** Whether this account is a signer */ + isSigner: boolean; + /** Whether this account is writable */ + isWritable: boolean; +} + +/** + * A decoded Solana instruction + */ +export interface Instruction { + /** The program ID (base58 string) that will execute this instruction */ + programId: string; + /** The accounts required by this instruction */ + accounts: AccountMeta[]; + /** The instruction data */ + data: Uint8Array; +} + +/** + * Solana Transaction wrapper for low-level deserialization and inspection. + * + * This class provides low-level access to transaction structure. + * For high-level semantic parsing with decoded instructions, use `parseTransaction()` instead. + * + * @example + * ```typescript + * import { Transaction, parseTransaction } from '@bitgo/wasm-solana'; + * + * // Low-level access: + * const tx = Transaction.fromBytes(txBytes); + * console.log(tx.feePayer); + * + * // High-level parsing (preferred): + * const parsed = parseTransaction(txBytes); + * console.log(parsed.instructionsData); // Decoded instruction types + * ``` + */ +export class Transaction { + private constructor(private _wasm: WasmTransaction) {} + + /** + * Deserialize a transaction from raw bytes + * @param bytes - The raw transaction bytes + * @returns A Transaction instance + */ + static fromBytes(bytes: Uint8Array): Transaction { + const wasm = WasmTransaction.from_bytes(bytes); + return new Transaction(wasm); + } + + /** + * Get the fee payer address as a base58 string + * Returns null if there are no account keys (shouldn't happen for valid transactions) + */ + get feePayer(): string | null { + return this._wasm.fee_payer ?? null; + } + + /** + * Get the recent blockhash as a base58 string + */ + get recentBlockhash(): string { + return this._wasm.recent_blockhash; + } + + /** + * Get the number of signatures in the transaction + */ + get numSignatures(): number { + return this._wasm.num_signatures; + } + + /** + * Get the signable message payload (what gets signed) + * This is the serialized message that signers sign + * @returns The message bytes + */ + signablePayload(): Uint8Array { + return this._wasm.signable_payload(); + } + + /** + * Serialize the transaction to bytes + * @returns The serialized transaction bytes + */ + toBytes(): Uint8Array { + return this._wasm.to_bytes(); + } + + /** + * Get all account keys as Pubkey instances + * @returns Array of account public keys + */ + accountKeys(): Pubkey[] { + const keys = Array.from(this._wasm.account_keys()) as string[]; + return keys.map((k) => Pubkey.fromBase58(k)); + } + + /** + * Get all signatures as byte arrays + * @returns Array of signature byte arrays + */ + signatures(): Uint8Array[] { + return Array.from(this._wasm.signatures()) as Uint8Array[]; + } + + /** + * Get all instructions in the transaction + * @returns Array of instructions with programId, accounts, and data + */ + instructions(): Instruction[] { + const rawInstructions = this._wasm.instructions(); + return Array.from(rawInstructions) as Instruction[]; + } + + /** + * Get the underlying WASM instance (internal use only) + * @internal + */ + get wasm(): WasmTransaction { + return this._wasm; + } +} diff --git a/packages/wasm-solana/src/builder/build.rs b/packages/wasm-solana/src/builder/build.rs new file mode 100644 index 0000000..36637b4 --- /dev/null +++ b/packages/wasm-solana/src/builder/build.rs @@ -0,0 +1,1201 @@ +//! 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_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 { + use super::Pubkey; + + pub fn memo_program() -> Pubkey { + "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" + .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() + } + + pub fn ata_program() -> Pubkey { + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + .parse() + .unwrap() + } + + 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. +/// +/// 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", + )) + } + } + + // ===== Stake Program ===== + IntentInstruction::StakeInitialize { + stake, + staker, + withdrawer, + } => { + 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, + )) + } + + // ===== SPL Token Program ===== + IntentInstruction::TokenTransfer { + source, + destination, + mint, + amount, + decimals, + authority, + program_id, + } => { + 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, + )) + } + + // ===== Jito Stake Pool ===== + IntentInstruction::StakePoolDepositSol { + stake_pool, + withdraw_authority, + reserve_stake, + funding_account, + destination_pool_account, + manager_fee_account, + referral_pool_account, + pool_mint, + lamports, + } => { + 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, + )) + } + } +} + +// ===== 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![]) +} + +// ===== 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), + ], + ) +} + +// ===== 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 +} + +// ===== 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::*; + + // 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")); + } + + #[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); + } + + #[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); + } + + #[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/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..e2fd1e4 --- /dev/null +++ b/packages/wasm-solana/src/builder/types.rs @@ -0,0 +1,272 @@ +//! 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 ===== + /// 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 ===== + /// 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, + }, + // ===== 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 { + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string() +} diff --git a/packages/wasm-solana/src/instructions/decode.rs b/packages/wasm-solana/src/instructions/decode.rs new file mode 100644 index 0000000..34771ed --- /dev/null +++ b/packages/wasm-solana/src/instructions/decode.rs @@ -0,0 +1,423 @@ +//! Instruction decoding using official Solana interface crates. + +use super::types::*; +use solana_compute_budget_interface::ComputeBudgetInstruction; +use solana_stake_interface::instruction::StakeInstruction; +use solana_system_interface::instruction::SystemInstruction; +use spl_stake_pool::instruction::StakePoolInstruction; + +/// Context for decoding an instruction - provides account addresses. +pub struct InstructionContext<'a> { + pub program_id: &'a str, + pub accounts: &'a [String], + pub data: &'a [u8], +} + +/// Decode a single instruction into a ParsedInstruction. +pub fn decode_instruction(ctx: InstructionContext) -> ParsedInstruction { + match ctx.program_id { + SYSTEM_PROGRAM_ID => decode_system_instruction(ctx), + STAKE_PROGRAM_ID => decode_stake_instruction(ctx), + COMPUTE_BUDGET_PROGRAM_ID => decode_compute_budget_instruction(ctx), + MEMO_PROGRAM_ID => decode_memo_instruction(ctx), + TOKEN_PROGRAM_ID | TOKEN_2022_PROGRAM_ID => decode_token_instruction(ctx), + ATA_PROGRAM_ID => decode_ata_instruction(ctx), + STAKE_POOL_PROGRAM_ID => decode_stake_pool_instruction(ctx), + _ => make_unknown(ctx), + } +} + +// ============================================================================= +// System Program Decoding +// ============================================================================= + +fn decode_system_instruction(ctx: InstructionContext) -> ParsedInstruction { + let Ok(instr) = bincode::deserialize::(ctx.data) else { + return make_unknown(ctx); + }; + + match instr { + SystemInstruction::Transfer { lamports } => { + if ctx.accounts.len() >= 2 { + ParsedInstruction::Transfer(TransferParams { + from_address: ctx.accounts[0].clone(), + to_address: ctx.accounts[1].clone(), + amount: lamports.to_string(), + }) + } else { + make_unknown(ctx) + } + } + SystemInstruction::CreateAccount { + lamports, + space, + owner, + } => { + if ctx.accounts.len() >= 2 { + ParsedInstruction::CreateAccount(CreateAccountParams { + from_address: ctx.accounts[0].clone(), + new_address: ctx.accounts[1].clone(), + amount: lamports.to_string(), + space, + owner: owner.to_string(), + }) + } else { + make_unknown(ctx) + } + } + SystemInstruction::AdvanceNonceAccount => { + if ctx.accounts.len() >= 3 { + ParsedInstruction::NonceAdvance(NonceAdvanceParams { + wallet_nonce_address: ctx.accounts[0].clone(), + auth_wallet_address: ctx.accounts[2].clone(), // authority is at index 2 + }) + } else { + make_unknown(ctx) + } + } + SystemInstruction::InitializeNonceAccount(authority) => { + // This is part of CreateNonceAccount flow - parsed as intermediate NonceInitialize + // Will be combined with CreateAccount in post-processing + // Accounts: [0] nonce, [1] recent_blockhashes_sysvar, [2] rent_sysvar + if ctx.accounts.len() >= 1 { + ParsedInstruction::NonceInitialize(NonceInitializeParams { + nonce_address: ctx.accounts[0].clone(), + auth_address: authority.to_string(), + }) + } else { + make_unknown(ctx) + } + } + _ => make_unknown(ctx), + } +} + +// ============================================================================= +// Stake Program Decoding +// ============================================================================= + +fn decode_stake_instruction(ctx: InstructionContext) -> ParsedInstruction { + let Ok(instr) = bincode::deserialize::(ctx.data) else { + return make_unknown(ctx); + }; + + match instr { + StakeInstruction::DelegateStake => { + // Accounts: [0] stake, [1] vote, [2] clock, [3] stake_history, [4] config, [5] authority + if ctx.accounts.len() >= 6 { + ParsedInstruction::StakingDelegate(StakingDelegateParams { + staking_address: ctx.accounts[0].clone(), + from_address: ctx.accounts[5].clone(), // authority + validator: ctx.accounts[1].clone(), // vote account + }) + } else { + make_unknown(ctx) + } + } + StakeInstruction::Deactivate => { + // Accounts: [0] stake, [1] clock, [2] authority + if ctx.accounts.len() >= 3 { + ParsedInstruction::StakingDeactivate(StakingDeactivateParams { + staking_address: ctx.accounts[0].clone(), + from_address: ctx.accounts[2].clone(), + }) + } else { + make_unknown(ctx) + } + } + StakeInstruction::Withdraw(lamports) => { + // Accounts: [0] stake, [1] recipient, [2] clock, [3] stake_history, [4] authority + if ctx.accounts.len() >= 5 { + ParsedInstruction::StakingWithdraw(StakingWithdrawParams { + staking_address: ctx.accounts[0].clone(), + from_address: ctx.accounts[4].clone(), + amount: lamports.to_string(), + }) + } else { + make_unknown(ctx) + } + } + StakeInstruction::Initialize(authorized, _lockup) => { + // This is part of StakingActivate flow - parsed as intermediate StakeInitialize + // Will be combined with CreateAccount + DelegateStake in post-processing + // Accounts: [0] stake, [1] rent_sysvar + if ctx.accounts.len() >= 1 { + ParsedInstruction::StakeInitialize(StakeInitializeParams { + staking_address: ctx.accounts[0].clone(), + staker: authorized.staker.to_string(), + withdrawer: authorized.withdrawer.to_string(), + }) + } else { + make_unknown(ctx) + } + } + StakeInstruction::Authorize(new_authority, stake_authorize) => { + // Accounts: [0] stake, [1] clock, [2] authority, [3] optional custodian + if ctx.accounts.len() >= 3 { + let auth_type = match stake_authorize { + solana_stake_interface::state::StakeAuthorize::Staker => "Staker", + solana_stake_interface::state::StakeAuthorize::Withdrawer => "Withdrawer", + }; + ParsedInstruction::StakingAuthorize(StakingAuthorizeParams { + staking_address: ctx.accounts[0].clone(), + old_authorize_address: ctx.accounts[2].clone(), + new_authorize_address: new_authority.to_string(), + authorize_type: auth_type.to_string(), + }) + } else { + make_unknown(ctx) + } + } + StakeInstruction::Split(_lamports) => { + // Accounts: [0] source stake, [1] dest stake, [2] authority + if ctx.accounts.len() >= 3 { + ParsedInstruction::StakingDeactivate(StakingDeactivateParams { + staking_address: ctx.accounts[0].clone(), + from_address: ctx.accounts[2].clone(), + }) + } else { + make_unknown(ctx) + } + } + _ => make_unknown(ctx), + } +} + +// ============================================================================= +// ComputeBudget Program Decoding +// ============================================================================= + +fn decode_compute_budget_instruction(ctx: InstructionContext) -> ParsedInstruction { + use borsh::BorshDeserialize; + + let Ok(instr) = ComputeBudgetInstruction::try_from_slice(ctx.data) else { + return make_unknown(ctx); + }; + + match instr { + ComputeBudgetInstruction::SetComputeUnitLimit(units) => { + ParsedInstruction::SetComputeUnitLimit(SetComputeUnitLimitParams { units }) + } + ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports) => { + ParsedInstruction::SetPriorityFee(SetPriorityFeeParams { + fee: micro_lamports.to_string(), + }) + } + _ => make_unknown(ctx), + } +} + +// ============================================================================= +// Memo Program Decoding +// ============================================================================= + +fn decode_memo_instruction(ctx: InstructionContext) -> ParsedInstruction { + // Memo data is just UTF-8 text + if let Ok(memo) = std::str::from_utf8(ctx.data) { + ParsedInstruction::Memo(MemoParams { + memo: memo.to_string(), + }) + } else { + make_unknown(ctx) + } +} + +// ============================================================================= +// Token Program Decoding (basic) +// ============================================================================= + +fn decode_token_instruction(ctx: InstructionContext) -> ParsedInstruction { + // SPL Token instruction format: first byte is discriminator + if ctx.data.is_empty() { + return make_unknown(ctx); + } + + let discriminator = ctx.data[0]; + + match discriminator { + // TransferChecked = 12 + 12 => { + // Accounts: [0] source, [1] mint, [2] destination, [3] owner/delegate + if ctx.accounts.len() >= 4 { + // Amount is a u64 at bytes 1-8, decimals at byte 9 + let amount = if ctx.data.len() >= 9 { + u64::from_le_bytes(ctx.data[1..9].try_into().unwrap_or([0; 8])) + } else { + 0 + }; + let decimals = if ctx.data.len() >= 10 { + Some(ctx.data[9]) + } else { + None + }; + ParsedInstruction::TokenTransfer(TokenTransferParams { + from_address: ctx.accounts[3].clone(), // owner + to_address: ctx.accounts[2].clone(), // destination + amount: amount.to_string(), + source_address: ctx.accounts[0].clone(), + token_address: Some(ctx.accounts[1].clone()), // mint + program_id: ctx.program_id.to_string(), + decimal_places: decimals, + }) + } else { + make_unknown(ctx) + } + } + // Transfer = 3 + 3 => { + // Accounts: [0] source, [1] destination, [2] owner/delegate + if ctx.accounts.len() >= 3 { + let amount = if ctx.data.len() >= 9 { + u64::from_le_bytes(ctx.data[1..9].try_into().unwrap_or([0; 8])) + } else { + 0 + }; + ParsedInstruction::TokenTransfer(TokenTransferParams { + from_address: ctx.accounts[2].clone(), + to_address: ctx.accounts[1].clone(), + amount: amount.to_string(), + source_address: ctx.accounts[0].clone(), + token_address: None, + program_id: ctx.program_id.to_string(), + decimal_places: None, // Not available in basic Transfer + }) + } else { + make_unknown(ctx) + } + } + // CloseAccount = 9 + 9 => { + // Accounts: [0] account, [1] destination, [2] owner + if ctx.accounts.len() >= 3 { + ParsedInstruction::CloseAssociatedTokenAccount(CloseAtaParams { + account_address: ctx.accounts[0].clone(), + destination_address: ctx.accounts[1].clone(), + authority_address: ctx.accounts[2].clone(), + }) + } else { + make_unknown(ctx) + } + } + _ => make_unknown(ctx), + } +} + +// ============================================================================= +// ATA Program Decoding +// ============================================================================= + +fn decode_ata_instruction(ctx: InstructionContext) -> ParsedInstruction { + // ATA program: Create instruction has no data (discriminator 0 or empty) + // Accounts: [0] payer, [1] ata, [2] owner, [3] mint, [4] system, [5] token + if ctx.accounts.len() >= 4 { + ParsedInstruction::CreateAssociatedTokenAccount(CreateAtaParams { + payer_address: ctx.accounts[0].clone(), + ata_address: ctx.accounts[1].clone(), + owner_address: ctx.accounts[2].clone(), + mint_address: ctx.accounts[3].clone(), + program_id: ctx.program_id.to_string(), + }) + } else { + make_unknown(ctx) + } +} + +// ============================================================================= +// 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 +// ============================================================================= + +fn make_unknown(ctx: InstructionContext) -> ParsedInstruction { + ParsedInstruction::Unknown(UnknownInstructionParams { + program_id: ctx.program_id.to_string(), + accounts: ctx + .accounts + .iter() + .map(|a| AccountMeta { + pubkey: a.clone(), + is_signer: false, // We don't have this info at decode time + is_writable: false, // We don't have this info at decode time + }) + .collect(), + data: ctx.data.to_vec(), + }) +} diff --git a/packages/wasm-solana/src/instructions/mod.rs b/packages/wasm-solana/src/instructions/mod.rs new file mode 100644 index 0000000..e6832ab --- /dev/null +++ b/packages/wasm-solana/src/instructions/mod.rs @@ -0,0 +1,9 @@ +//! Internal instruction decoders using official Solana interface crates. +//! +//! This module is NOT publicly exposed. It's used internally by `parseTransaction`. + +mod decode; +mod types; + +pub(crate) use decode::{decode_instruction, InstructionContext}; +pub(crate) use types::*; diff --git a/packages/wasm-solana/src/instructions/types.rs b/packages/wasm-solana/src/instructions/types.rs new file mode 100644 index 0000000..121e852 --- /dev/null +++ b/packages/wasm-solana/src/instructions/types.rs @@ -0,0 +1,332 @@ +//! Parsed instruction types matching BitGoJS InstructionParams. +//! +//! These types are designed to serialize to JSON that matches the TypeScript +//! interfaces in sdk-coin-sol/src/lib/iface.ts. + +use serde::Serialize; + +/// Program IDs as base58 strings. +pub const SYSTEM_PROGRAM_ID: &str = "11111111111111111111111111111111"; +pub const STAKE_PROGRAM_ID: &str = "Stake11111111111111111111111111111111111111"; +pub const COMPUTE_BUDGET_PROGRAM_ID: &str = "ComputeBudget111111111111111111111111111111"; +pub const MEMO_PROGRAM_ID: &str = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; +pub const TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"; +pub const TOKEN_2022_PROGRAM_ID: &str = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"; +pub const ATA_PROGRAM_ID: &str = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"; +pub const STAKE_POOL_PROGRAM_ID: &str = "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy"; + +/// A parsed instruction with type discriminant and params. +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "type")] +pub enum ParsedInstruction { + // System Program instructions + Transfer(TransferParams), + CreateAccount(CreateAccountParams), + NonceAdvance(NonceAdvanceParams), + CreateNonceAccount(CreateNonceAccountParams), + /// Intermediate type for SystemInstruction::InitializeNonceAccount + /// Will be combined with CreateAccount to form CreateNonceAccount + #[serde(rename = "NonceInitialize")] + NonceInitialize(NonceInitializeParams), + + // Stake Program instructions + StakingActivate(StakingActivateParams), + StakingDeactivate(StakingDeactivateParams), + StakingWithdraw(StakingWithdrawParams), + StakingDelegate(StakingDelegateParams), + StakingAuthorize(StakingAuthorizeParams), + /// Intermediate type for stake initialize - will be combined with CreateAccount + DelegateStake + #[serde(rename = "StakeInitialize")] + StakeInitialize(StakeInitializeParams), + + // ComputeBudget instructions + SetComputeUnitLimit(SetComputeUnitLimitParams), + SetPriorityFee(SetPriorityFeeParams), + + // Token instructions (basic support) + TokenTransfer(TokenTransferParams), + CreateAssociatedTokenAccount(CreateAtaParams), + CloseAssociatedTokenAccount(CloseAtaParams), + + // Memo + Memo(MemoParams), + + // Stake Pool (Jito liquid staking) instructions + StakePoolDepositSol(StakePoolDepositSolParams), + StakePoolWithdrawStake(StakePoolWithdrawStakeParams), + + // Fallback for unknown/custom instructions + Unknown(UnknownInstructionParams), +} + +// ============================================================================= +// System Program Params +// ============================================================================= + +#[derive(Debug, Clone, Serialize)] +pub struct TransferParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "toAddress")] + pub to_address: String, + pub amount: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CreateAccountParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "newAddress")] + pub new_address: String, + pub amount: String, + pub space: u64, + pub owner: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct NonceAdvanceParams { + #[serde(rename = "walletNonceAddress")] + pub wallet_nonce_address: String, + #[serde(rename = "authWalletAddress")] + pub auth_wallet_address: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CreateNonceAccountParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "nonceAddress")] + pub nonce_address: String, + #[serde(rename = "authAddress")] + pub auth_address: String, + pub amount: String, +} + +/// Intermediate type for SystemInstruction::InitializeNonceAccount +/// Will be combined with CreateAccount to form CreateNonceAccount +#[derive(Debug, Clone, Serialize)] +pub struct NonceInitializeParams { + #[serde(rename = "nonceAddress")] + pub nonce_address: String, + #[serde(rename = "authAddress")] + pub auth_address: String, +} + +// ============================================================================= +// Stake Program Params +// ============================================================================= + +#[derive(Debug, Clone, Serialize)] +pub struct StakingActivateParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "stakingAddress")] + pub staking_address: String, + pub amount: String, + pub validator: String, + #[serde(rename = "stakingType")] + pub staking_type: String, // "NATIVE", "JITO", "MARINADE" +} + +#[derive(Debug, Clone, Serialize)] +pub struct StakingDeactivateParams { + #[serde(rename = "stakingAddress")] + pub staking_address: String, + #[serde(rename = "fromAddress")] + pub from_address: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct StakingWithdrawParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "stakingAddress")] + pub staking_address: String, + pub amount: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct StakingDelegateParams { + #[serde(rename = "stakingAddress")] + pub staking_address: String, + #[serde(rename = "fromAddress")] + pub from_address: String, + pub validator: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct StakingAuthorizeParams { + #[serde(rename = "stakingAddress")] + pub staking_address: String, + #[serde(rename = "oldAuthorizeAddress")] + pub old_authorize_address: String, + #[serde(rename = "newAuthorizeAddress")] + pub new_authorize_address: String, + #[serde(rename = "authorizeType")] + pub authorize_type: String, // "Staker" or "Withdrawer" +} + +/// Intermediate type for StakeInstruction::Initialize +/// Will be combined with CreateAccount + DelegateStake to form StakingActivate +#[derive(Debug, Clone, Serialize)] +pub struct StakeInitializeParams { + #[serde(rename = "stakingAddress")] + pub staking_address: String, + pub staker: String, + pub withdrawer: String, +} + +// ============================================================================= +// ComputeBudget Params +// ============================================================================= + +#[derive(Debug, Clone, Serialize)] +pub struct SetComputeUnitLimitParams { + pub units: u32, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SetPriorityFeeParams { + /// Fee as string for BigInt compatibility in JavaScript + pub fee: String, +} + +// ============================================================================= +// Token Params +// ============================================================================= + +#[derive(Debug, Clone, Serialize)] +pub struct TokenTransferParams { + #[serde(rename = "fromAddress")] + pub from_address: String, + #[serde(rename = "toAddress")] + pub to_address: String, + pub amount: String, + #[serde(rename = "sourceAddress")] + pub source_address: String, + #[serde(rename = "tokenAddress", skip_serializing_if = "Option::is_none")] + pub token_address: Option, + #[serde(rename = "programId")] + pub program_id: String, + #[serde(rename = "decimalPlaces", skip_serializing_if = "Option::is_none")] + pub decimal_places: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CreateAtaParams { + #[serde(rename = "mintAddress")] + pub mint_address: String, + #[serde(rename = "ataAddress")] + pub ata_address: String, + #[serde(rename = "ownerAddress")] + pub owner_address: String, + #[serde(rename = "payerAddress")] + pub payer_address: String, + #[serde(rename = "programId")] + pub program_id: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CloseAtaParams { + #[serde(rename = "accountAddress")] + pub account_address: String, + #[serde(rename = "destinationAddress")] + pub destination_address: String, + #[serde(rename = "authorityAddress")] + pub authority_address: String, +} + +// ============================================================================= +// 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 +// ============================================================================= + +#[derive(Debug, Clone, Serialize)] +pub struct MemoParams { + pub memo: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct UnknownInstructionParams { + #[serde(rename = "programId")] + pub program_id: String, + pub accounts: Vec, + #[serde(with = "base64_bytes")] + pub data: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct AccountMeta { + pub pubkey: String, + #[serde(rename = "isSigner")] + pub is_signer: bool, + #[serde(rename = "isWritable")] + pub is_writable: bool, +} + +/// Custom serializer for bytes as base64. +mod base64_bytes { + use base64::prelude::*; + use serde::{Serialize, Serializer}; + + pub fn serialize(bytes: &[u8], serializer: S) -> Result + where + S: Serializer, + { + BASE64_STANDARD.encode(bytes).serialize(serializer) + } +} diff --git a/packages/wasm-solana/src/keypair.rs b/packages/wasm-solana/src/keypair.rs index a8c5f80..b4c4034 100644 --- a/packages/wasm-solana/src/keypair.rs +++ b/packages/wasm-solana/src/keypair.rs @@ -20,27 +20,15 @@ pub trait KeypairExt { impl KeypairExt for Keypair { /// Create a keypair from a 32-byte secret key (Ed25519 seed). fn from_secret_key_bytes(secret_key: &[u8]) -> Result { - if secret_key.len() != 32 { - return Err(WasmSolanaError::new(&format!( + let bytes: [u8; 32] = secret_key.try_into().map_err(|_| { + WasmSolanaError::new(&format!( "Secret key must be 32 bytes, got {}", secret_key.len() - ))); - } + )) + })?; - // Generate public key from secret to create full 64-byte format - use ed25519_dalek::SigningKey; - let bytes: [u8; 32] = secret_key - .try_into() - .map_err(|_| WasmSolanaError::new("Failed to convert secret key to array"))?; - let signing_key = SigningKey::from_bytes(&bytes); - let pubkey_bytes = signing_key.verifying_key().to_bytes(); - - let mut full_secret = [0u8; 64]; - full_secret[..32].copy_from_slice(secret_key); - full_secret[32..].copy_from_slice(&pubkey_bytes); - - Keypair::try_from(full_secret.as_slice()) - .map_err(|e| WasmSolanaError::new(&format!("Invalid keypair: {}", e))) + // Use official solana-keypair method that handles 32-byte seeds + Ok(Keypair::new_from_array(bytes)) } /// Create a keypair from a 64-byte Solana secret key (secret + public concatenated). diff --git a/packages/wasm-solana/src/lib.rs b/packages/wasm-solana/src/lib.rs index fa67d30..b63ec05 100644 --- a/packages/wasm-solana/src/lib.rs +++ b/packages/wasm-solana/src/lib.rs @@ -23,15 +23,20 @@ //! let pubkey = Pubkey::from_base58("FKjSjCqByQRwSzZoMXA7bKnDbJe41YgJTHFFzBeC42bH").unwrap(); //! ``` +pub mod builder; mod error; +mod instructions; pub mod keypair; +mod parser; pub mod pubkey; +pub mod transaction; pub mod wasm; // Re-export core types at crate root pub use error::WasmSolanaError; pub use keypair::{Keypair, KeypairExt}; pub use pubkey::{Pubkey, PubkeyExt}; +pub use transaction::{Transaction, TransactionExt}; // Re-export WASM types -pub use wasm::{WasmKeypair, WasmPubkey}; +pub use wasm::{BuilderNamespace, ParserNamespace, WasmKeypair, WasmPubkey, WasmTransaction}; diff --git a/packages/wasm-solana/src/parser.rs b/packages/wasm-solana/src/parser.rs new file mode 100644 index 0000000..452b34c --- /dev/null +++ b/packages/wasm-solana/src/parser.rs @@ -0,0 +1,280 @@ +//! High-level transaction parser. +//! +//! Provides a single `parse_transaction` function that deserializes transaction bytes +//! and decodes all instructions into semantic types matching BitGoJS's TxData format. +//! +//! The parser performs post-processing to combine sequential instructions into +//! compound types that match BitGoJS's semantic representation: +//! - CreateAccount + NonceInitialize → CreateNonceAccount +//! - CreateAccount + StakeInitialize + StakingDelegate → StakingActivate + +use crate::instructions::{ + decode_instruction, CreateNonceAccountParams, InstructionContext, ParsedInstruction, + StakingActivateParams, STAKE_PROGRAM_ID, SYSTEM_PROGRAM_ID, +}; +use crate::transaction::{Transaction, TransactionExt}; +use serde::Serialize; + +/// A fully parsed Solana transaction with decoded instructions. +/// +/// This structure matches BitGoJS's `TxData` interface for seamless integration. +#[derive(Debug, Clone, Serialize)] +pub struct ParsedTransaction { + /// The fee payer address (base58). + #[serde(rename = "feePayer")] + pub fee_payer: String, + + /// Number of required signatures. + #[serde(rename = "numSignatures")] + pub num_signatures: u8, + + /// The blockhash or nonce (base58). + pub nonce: String, + + /// If this is a durable nonce transaction, contains the nonce info. + #[serde(rename = "durableNonce", skip_serializing_if = "Option::is_none")] + pub durable_nonce: Option, + + /// All decoded instructions. + #[serde(rename = "instructionsData")] + pub instructions_data: Vec, + + /// All signatures (as bytes, base64-encoded for JSON). + #[serde(with = "signatures_serde")] + pub signatures: Vec>, + + /// All account keys (base58 strings). + #[serde(rename = "accountKeys")] + pub account_keys: Vec, +} + +/// Durable nonce information for nonce-based transactions. +#[derive(Debug, Clone, Serialize)] +pub struct DurableNonce { + /// The nonce account address (base58). + #[serde(rename = "walletNonceAddress")] + pub wallet_nonce_address: String, + + /// The nonce authority address (base58). + #[serde(rename = "authWalletAddress")] + pub auth_wallet_address: String, +} + +/// Parse a serialized Solana transaction into structured data. +/// +/// # Arguments +/// * `bytes` - The raw transaction bytes (wire format) +/// +/// # Returns +/// A `ParsedTransaction` with all instructions decoded to semantic types. +pub fn parse_transaction(bytes: &[u8]) -> Result { + // Deserialize the transaction + let tx = Transaction::from_bytes(bytes).map_err(|e| e.to_string())?; + + let message = &tx.message; + + // Extract fee payer (first account key) + let fee_payer = message + .account_keys + .first() + .map(|k| k.to_string()) + .ok_or("Transaction has no account keys")?; + + // Extract all account keys as base58 strings + let account_keys: Vec = message.account_keys.iter().map(|k| k.to_string()).collect(); + + // Extract signatures as byte arrays + let signatures: Vec> = tx.signatures.iter().map(|s| s.as_ref().to_vec()).collect(); + + // Decode all instructions + let mut instructions_data = Vec::with_capacity(message.instructions.len()); + let mut durable_nonce = None; + + for (idx, instruction) in message.instructions.iter().enumerate() { + // Get program ID + let program_id = message + .account_keys + .get(instruction.program_id_index as usize) + .map(|k| k.to_string()) + .ok_or_else(|| format!("Invalid program_id_index in instruction {}", idx))?; + + // Resolve account indices to addresses + let accounts: Vec = instruction + .accounts + .iter() + .filter_map(|&i| message.account_keys.get(i as usize).map(|k| k.to_string())) + .collect(); + + // Decode the instruction + let ctx = InstructionContext { + program_id: &program_id, + accounts: &accounts, + data: &instruction.data, + }; + let parsed = decode_instruction(ctx); + + // Check if this is a NonceAdvance instruction (first instruction = durable nonce tx) + if idx == 0 { + if let ParsedInstruction::NonceAdvance(ref params) = parsed { + durable_nonce = Some(DurableNonce { + wallet_nonce_address: params.wallet_nonce_address.clone(), + auth_wallet_address: params.auth_wallet_address.clone(), + }); + } + } + + instructions_data.push(parsed); + } + + // Post-process: combine sequential instructions into compound types + let instructions_data = combine_instructions(instructions_data); + + // The nonce is either the blockhash or, for durable nonce txs, still the blockhash + // (which is the nonce value from the nonce account) + let nonce = message.recent_blockhash.to_string(); + + Ok(ParsedTransaction { + fee_payer, + num_signatures: message.header.num_required_signatures, + nonce, + durable_nonce, + instructions_data, + signatures, + account_keys, + }) +} + +/// Combine sequential instructions into compound semantic types. +/// +/// This matches BitGoJS's behavior where certain instruction sequences are +/// represented as a single high-level instruction: +/// - CreateAccount + NonceInitialize → CreateNonceAccount +/// - CreateAccount + StakeInitialize + StakingDelegate → StakingActivate +fn combine_instructions(instructions: Vec) -> Vec { + let mut result = Vec::with_capacity(instructions.len()); + let mut i = 0; + + while i < instructions.len() { + // Try to match CreateAccount patterns + if let ParsedInstruction::CreateAccount(ref create) = instructions[i] { + // Pattern 1: CreateAccount + NonceInitialize → CreateNonceAccount + if i + 1 < instructions.len() { + if let ParsedInstruction::NonceInitialize(ref nonce_init) = instructions[i + 1] { + // Check if CreateAccount target matches NonceInitialize nonce address + // and owner is System Program (nonce accounts owned by system program) + if create.new_address == nonce_init.nonce_address + && create.owner == SYSTEM_PROGRAM_ID + { + result.push(ParsedInstruction::CreateNonceAccount( + CreateNonceAccountParams { + from_address: create.from_address.clone(), + nonce_address: nonce_init.nonce_address.clone(), + auth_address: nonce_init.auth_address.clone(), + amount: create.amount.clone(), + }, + )); + i += 2; // Skip both instructions + continue; + } + } + } + + // Pattern 2: CreateAccount + StakeInitialize + StakingDelegate → StakingActivate + if i + 2 < instructions.len() { + if let ( + ParsedInstruction::StakeInitialize(ref stake_init), + ParsedInstruction::StakingDelegate(ref delegate), + ) = (&instructions[i + 1], &instructions[i + 2]) + { + // Check if CreateAccount target matches StakeInitialize staking address + // and owner is Stake Program + if create.new_address == stake_init.staking_address + && create.owner == STAKE_PROGRAM_ID + && stake_init.staking_address == delegate.staking_address + { + result.push(ParsedInstruction::StakingActivate(StakingActivateParams { + from_address: create.from_address.clone(), + staking_address: stake_init.staking_address.clone(), + amount: create.amount.clone(), + validator: delegate.validator.clone(), + staking_type: "NATIVE".to_string(), + })); + i += 3; // Skip all three instructions + continue; + } + } + } + } + + // No pattern matched, keep the instruction as-is + result.push(instructions[i].clone()); + i += 1; + } + + result +} + +/// Serialize signatures as base64 strings for JSON output. +mod signatures_serde { + use base64::prelude::*; + use serde::{Serialize, Serializer}; + + pub fn serialize(signatures: &[Vec], serializer: S) -> Result + where + S: Serializer, + { + let encoded: Vec = signatures + .iter() + .map(|s| BASE64_STANDARD.encode(s)) + .collect(); + encoded.serialize(serializer) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use base64::prelude::*; + + // Test transaction from @solana/web3.js - a simple SOL transfer + const TEST_TX_BASE64: &str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDFVMqpim7tqEi2XL8R6KKkP0DYJvY3eiRXLlL1P9EjYgXKQC+k0FKnqyC4AZGJR7OhJXfpPP3NHOhS8t/6G7bLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1c7Oaj3RbyLIjU0/ZPpsmVfVUWAzc8g36fK5g6A0JoBAgIAAQwCAAAAoIYBAAAAAAA="; + + #[test] + fn test_parse_transfer_transaction() { + let bytes = BASE64_STANDARD.decode(TEST_TX_BASE64).unwrap(); + let parsed = parse_transaction(&bytes).unwrap(); + + // Check basic structure + assert_eq!(parsed.num_signatures, 1); + assert!(!parsed.fee_payer.is_empty()); + assert!(!parsed.nonce.is_empty()); + assert_eq!(parsed.instructions_data.len(), 1); + + // Check the instruction is a Transfer + match &parsed.instructions_data[0] { + ParsedInstruction::Transfer(params) => { + // Amount should be 100000 lamports (from the test tx) + assert_eq!(params.amount, "100000"); + } + other => panic!("Expected Transfer instruction, got {:?}", other), + } + } + + #[test] + fn test_parse_invalid_bytes() { + let result = parse_transaction(&[0, 1, 2, 3]); + assert!(result.is_err()); + } + + #[test] + fn test_parsed_transaction_serializes_to_json() { + let bytes = BASE64_STANDARD.decode(TEST_TX_BASE64).unwrap(); + let parsed = parse_transaction(&bytes).unwrap(); + + // Should serialize to valid JSON + let json = serde_json::to_string(&parsed).unwrap(); + assert!(json.contains("feePayer")); + assert!(json.contains("instructionsData")); + assert!(json.contains("Transfer")); + } +} diff --git a/packages/wasm-solana/src/transaction.rs b/packages/wasm-solana/src/transaction.rs new file mode 100644 index 0000000..339a93f --- /dev/null +++ b/packages/wasm-solana/src/transaction.rs @@ -0,0 +1,163 @@ +//! Solana transaction deserialization. +//! +//! Wraps `solana_transaction::Transaction` for WASM compatibility. +//! +//! # Wire Format +//! +//! Solana transactions use a compact binary format: +//! - Signatures (variable length array) +//! - Message (contains instructions, accounts, blockhash) +//! +//! This module deserializes base64-encoded transactions as used by +//! `@solana/web3.js` `Transaction.from()`. + +use crate::error::WasmSolanaError; + +/// Re-export the underlying Solana Transaction type. +pub use solana_transaction::Transaction; + +/// Extension trait for Transaction to add WASM-friendly methods. +pub trait TransactionExt { + /// Deserialize a transaction from base64-encoded wire format. + fn from_base64(base64_str: &str) -> Result; + + /// Deserialize a transaction from raw bytes (wire format). + fn from_bytes(bytes: &[u8]) -> Result; + + /// Get the fee payer address as base58 string. + fn fee_payer_string(&self) -> Option; + + /// Get the recent blockhash as base58 string. + fn blockhash_string(&self) -> String; + + /// Get the number of instructions. + fn num_instructions(&self) -> usize; + + /// Get the number of signatures. + fn num_signatures(&self) -> usize; + + /// Get the signable message bytes (what gets signed). + fn signable_payload(&self) -> Vec; + + /// Serialize transaction to bytes (wire format). + fn to_bytes(&self) -> Result, WasmSolanaError>; + + /// Serialize transaction to base64. + fn to_base64(&self) -> Result; +} + +impl TransactionExt for Transaction { + fn from_base64(base64_str: &str) -> Result { + // Decode base64 + use base64::prelude::*; + let bytes = BASE64_STANDARD + .decode(base64_str) + .map_err(|e| WasmSolanaError::new(&format!("Invalid base64: {}", e)))?; + + Self::from_bytes(&bytes) + } + + fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + .map_err(|e| WasmSolanaError::new(&format!("Failed to deserialize transaction: {}", e))) + } + + fn fee_payer_string(&self) -> Option { + self.message.account_keys.first().map(|p| p.to_string()) + } + + fn blockhash_string(&self) -> String { + self.message.recent_blockhash.to_string() + } + + fn num_instructions(&self) -> usize { + self.message.instructions.len() + } + + fn num_signatures(&self) -> usize { + self.signatures.len() + } + + fn signable_payload(&self) -> Vec { + self.message.serialize() + } + + fn to_bytes(&self) -> Result, WasmSolanaError> { + bincode::serialize(self) + .map_err(|e| WasmSolanaError::new(&format!("Failed to serialize transaction: {}", e))) + } + + fn to_base64(&self) -> Result { + use base64::prelude::*; + let bytes = self.to_bytes()?; + Ok(BASE64_STANDARD.encode(&bytes)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Test transaction from @solana/web3.js - a simple SOL transfer + // This is a real transaction serialized with Transaction.serialize() + const TEST_TX_BASE64: &str = "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDFVMqpim7tqEi2XL8R6KKkP0DYJvY3eiRXLlL1P9EjYgXKQC+k0FKnqyC4AZGJR7OhJXfpPP3NHOhS8t/6G7bLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1c7Oaj3RbyLIjU0/ZPpsmVfVUWAzc8g36fK5g6A0JoBAgIAAQwCAAAAoIYBAAAAAAA="; + + #[test] + fn test_deserialize_transaction() { + let tx = Transaction::from_base64(TEST_TX_BASE64).unwrap(); + + // Check we got valid data + assert!(tx.num_signatures() > 0); + assert!(tx.num_instructions() > 0); + } + + #[test] + fn test_fee_payer() { + let tx = Transaction::from_base64(TEST_TX_BASE64).unwrap(); + let fee_payer = tx.fee_payer_string(); + assert!(fee_payer.is_some()); + // Fee payer should be a valid base58 Solana address + let payer = fee_payer.unwrap(); + assert!(payer.len() >= 32 && payer.len() <= 44); + } + + #[test] + fn test_blockhash() { + let tx = Transaction::from_base64(TEST_TX_BASE64).unwrap(); + let blockhash = tx.blockhash_string(); + // Blockhash should be a valid base58 string + assert!(blockhash.len() >= 32 && blockhash.len() <= 44); + } + + #[test] + fn test_roundtrip() { + let tx = Transaction::from_base64(TEST_TX_BASE64).unwrap(); + let serialized = tx.to_base64().unwrap(); + + // Deserialize again + let tx2 = Transaction::from_base64(&serialized).unwrap(); + assert_eq!(tx.num_signatures(), tx2.num_signatures()); + assert_eq!(tx.num_instructions(), tx2.num_instructions()); + assert_eq!(tx.blockhash_string(), tx2.blockhash_string()); + } + + #[test] + fn test_signable_payload() { + let tx = Transaction::from_base64(TEST_TX_BASE64).unwrap(); + let payload = tx.signable_payload(); + // Message should have some content + assert!(!payload.is_empty()); + } + + #[test] + fn test_invalid_base64() { + let result = Transaction::from_base64("not valid base64!!!"); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_transaction() { + let result = Transaction::from_bytes(&[0, 1, 2, 3]); + assert!(result.is_err()); + } +} diff --git a/packages/wasm-solana/src/wasm/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 45092cd..17c26fc 100644 --- a/packages/wasm-solana/src/wasm/mod.rs +++ b/packages/wasm-solana/src/wasm/mod.rs @@ -1,5 +1,11 @@ +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; +pub use transaction::WasmTransaction; diff --git a/packages/wasm-solana/src/wasm/parser.rs b/packages/wasm-solana/src/wasm/parser.rs new file mode 100644 index 0000000..cb4bdde --- /dev/null +++ b/packages/wasm-solana/src/wasm/parser.rs @@ -0,0 +1,38 @@ +//! WASM binding for high-level transaction parsing. +//! +//! Exposes a single `parseTransaction` function that returns fully decoded +//! transaction data matching BitGoJS's TxData format. + +use crate::parser; +use wasm_bindgen::prelude::*; + +/// Namespace for transaction parsing operations. +#[wasm_bindgen] +pub struct ParserNamespace; + +#[wasm_bindgen] +impl ParserNamespace { + /// Parse a serialized Solana transaction into structured data. + /// + /// Takes raw transaction bytes and returns a JSON object with: + /// - `feePayer`: The fee payer address (base58) + /// - `numSignatures`: Number of required signatures + /// - `nonce`: The blockhash/nonce value (base58) + /// - `durableNonce`: Optional durable nonce info (if tx uses nonce) + /// - `instructionsData`: Array of decoded instructions with semantic types + /// - `signatures`: Array of signatures (base64 encoded) + /// - `accountKeys`: Array of all account addresses (base58) + /// + /// Each instruction in `instructionsData` has a `type` field identifying the + /// instruction type (e.g., "Transfer", "StakingActivate", "TokenTransfer"). + /// + /// @param bytes - The raw transaction bytes (wire format) + /// @returns A ParsedTransaction object as JSON + #[wasm_bindgen] + pub fn parse_transaction(bytes: &[u8]) -> Result { + let parsed = parser::parse_transaction(bytes).map_err(|e| JsValue::from_str(&e))?; + + serde_wasm_bindgen::to_value(&parsed) + .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))) + } +} diff --git a/packages/wasm-solana/src/wasm/transaction.rs b/packages/wasm-solana/src/wasm/transaction.rs new file mode 100644 index 0000000..4d6c295 --- /dev/null +++ b/packages/wasm-solana/src/wasm/transaction.rs @@ -0,0 +1,154 @@ +//! WASM bindings for Solana transaction deserialization. +//! +//! Wraps `solana_transaction::Transaction` for JavaScript. +//! +//! Note: For semantic transaction parsing with decoded instructions, +//! use `ParserNamespace.parse_transaction()` instead. + +use crate::error::WasmSolanaError; +use crate::transaction::{Transaction, TransactionExt}; +use wasm_bindgen::prelude::*; + +/// WASM wrapper for Solana transactions. +/// +/// This type provides low-level access to transaction structure. +/// For high-level semantic parsing, use `ParserNamespace.parse_transaction()`. +#[wasm_bindgen] +pub struct WasmTransaction { + inner: Transaction, +} + +#[wasm_bindgen] +impl WasmTransaction { + /// Deserialize a transaction from raw bytes. + #[wasm_bindgen] + pub fn from_bytes(bytes: &[u8]) -> Result { + Transaction::from_bytes(bytes).map(|inner| WasmTransaction { inner }) + } + + /// Get the fee payer address as a base58 string. + /// + /// Returns `null` if there are no account keys (shouldn't happen for valid transactions). + #[wasm_bindgen(getter)] + pub fn fee_payer(&self) -> Option { + self.inner.fee_payer_string() + } + + /// Get the recent blockhash as a base58 string. + #[wasm_bindgen(getter)] + pub fn recent_blockhash(&self) -> String { + self.inner.blockhash_string() + } + + /// Get the number of instructions in the transaction. + #[wasm_bindgen(getter)] + pub fn num_instructions(&self) -> usize { + self.inner.num_instructions() + } + + /// Get the number of signatures in the transaction. + #[wasm_bindgen(getter)] + pub fn num_signatures(&self) -> usize { + self.inner.num_signatures() + } + + /// Get the signable message payload (what gets signed). + /// + /// This is the serialized message that signers sign. + #[wasm_bindgen] + pub fn signable_payload(&self) -> js_sys::Uint8Array { + let bytes = self.inner.signable_payload(); + js_sys::Uint8Array::from(&bytes[..]) + } + + /// Serialize the transaction to bytes. + #[wasm_bindgen] + pub fn to_bytes(&self) -> Result { + let bytes = self.inner.to_bytes()?; + Ok(js_sys::Uint8Array::from(&bytes[..])) + } + + /// Get all account keys as an array of base58 strings. + #[wasm_bindgen] + pub fn account_keys(&self) -> js_sys::Array { + let arr = js_sys::Array::new(); + for key in &self.inner.message.account_keys { + arr.push(&JsValue::from_str(&key.to_string())); + } + arr + } + + /// Get all signatures as an array of byte arrays. + /// + /// Each signature is returned as a Uint8Array. + #[wasm_bindgen] + pub fn signatures(&self) -> js_sys::Array { + let arr = js_sys::Array::new(); + for sig in &self.inner.signatures { + let bytes: &[u8] = sig.as_ref(); + arr.push(&js_sys::Uint8Array::from(bytes)); + } + arr + } + + /// Get all instructions as an array. + /// + /// Each instruction is a JS object with programId, accounts, and data. + #[wasm_bindgen] + pub fn instructions(&self) -> js_sys::Array { + let arr = js_sys::Array::new(); + let msg = &self.inner.message; + + for instruction in &msg.instructions { + let obj = js_sys::Object::new(); + + // Get the program ID + if let Some(program_id) = msg.account_keys.get(instruction.program_id_index as usize) { + let _ = + js_sys::Reflect::set(&obj, &"programId".into(), &program_id.to_string().into()); + } + + // Build accounts array with signer/writable flags + let accounts = js_sys::Array::new(); + for &account_index in &instruction.accounts { + if let Some(pubkey) = msg.account_keys.get(account_index as usize) { + let account_obj = js_sys::Object::new(); + + let _ = js_sys::Reflect::set( + &account_obj, + &"pubkey".into(), + &pubkey.to_string().into(), + ); + + // Use official Solana methods for signer/writable flags + let is_signer = msg.is_signer(account_index as usize); + let is_writable = msg.is_maybe_writable(account_index as usize, None); + let _ = + js_sys::Reflect::set(&account_obj, &"isSigner".into(), &is_signer.into()); + let _ = js_sys::Reflect::set( + &account_obj, + &"isWritable".into(), + &is_writable.into(), + ); + + accounts.push(&account_obj); + } + } + let _ = js_sys::Reflect::set(&obj, &"accounts".into(), &accounts); + + // Set instruction data + let data = js_sys::Uint8Array::from(&instruction.data[..]); + let _ = js_sys::Reflect::set(&obj, &"data".into(), &data); + + arr.push(&obj); + } + arr + } +} + +impl WasmTransaction { + /// Get the inner Transaction for internal Rust use. + pub fn inner(&self) -> &Transaction { + &self.inner + } +} diff --git a/packages/wasm-solana/test/bitgojs-compat.ts b/packages/wasm-solana/test/bitgojs-compat.ts new file mode 100644 index 0000000..cd00a43 --- /dev/null +++ b/packages/wasm-solana/test/bitgojs-compat.ts @@ -0,0 +1,303 @@ +/** + * Compatibility tests using BitGoJS test fixtures. + * + * These tests verify that our parseTransaction output matches + * what BitGoJS's Transaction.toJson() produces. + */ +import * as assert from "assert"; +import { parseTransaction } from "../js/parser.js"; + +// Helper to decode base64 in tests +function base64ToBytes(base64: string): Uint8Array { + const binary = Buffer.from(base64, "base64"); + return new Uint8Array(binary); +} + +describe("BitGoJS Compatibility", () => { + describe("Transfer with memo and durable nonce", () => { + // From BitGoJS: test/resources/sol.ts - TRANSFER_UNSIGNED_TX_WITH_MEMO_AND_DURABLE_NONCE + const TX_BASE64 = + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAMGReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0Fv+hKJ+pxZaLwHGEyk2Svp5PfAC5ZEi/wYI1tPTHHhbqkYG1L37ZDq6w2tS3G+tFODYWdhMXF+kwlYEF+3o4nVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFSlNamSkhBk0k6HFg2jh8fDW13bySu4HkH6hAQQVEjQan1RcZLFaO4IqEX3PSl4jPA1wxRbIas0TYBi6pQAAA4zLa+S+r7Oi2P/ekQAXl/f2a+hWHVrYcWpX5BLO40IEDAwMBBQAEBAAAAAMCAAIMAgAAAOCTBAAAAAAABAAJdGVzdCBtZW1v"; + + // Expected values from BitGoJS test/unit/transaction.ts lines 33-60 + // Note: WASM returns all instructions including NonceAdvance; BitGoJS may filter it for its own use + const EXPECTED = { + feePayer: "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe", + nonce: "GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi", + numSignatures: 1, // header.num_required_signatures + durableNonce: { + walletNonceAddress: "8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh", + authWalletAddress: "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe", + }, + // instructionsData includes all instructions including NonceAdvance + instructionsData: [ + { + type: "NonceAdvance", + walletNonceAddress: "8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh", + authWalletAddress: "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe", + }, + { + type: "Transfer", + fromAddress: "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe", + toAddress: "CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S", + amount: "300000", + }, + { + type: "Memo", + memo: "test memo", + }, + ], + }; + + it("should parse feePayer correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + assert.strictEqual(parsed.feePayer, EXPECTED.feePayer); + }); + + it("should parse nonce correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + assert.strictEqual(parsed.nonce, EXPECTED.nonce); + }); + + it("should parse numSignatures correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + assert.strictEqual(parsed.numSignatures, EXPECTED.numSignatures); + }); + + it("should detect durable nonce transaction", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + assert.ok(parsed.durableNonce, "Should detect durable nonce"); + assert.strictEqual( + parsed.durableNonce.walletNonceAddress, + EXPECTED.durableNonce.walletNonceAddress, + ); + assert.strictEqual( + parsed.durableNonce.authWalletAddress, + EXPECTED.durableNonce.authWalletAddress, + ); + }); + + it("should have NonceAdvance in both instructionsData and durableNonce", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + // WASM returns all instructions including NonceAdvance + const nonceAdvance = parsed.instructionsData.find((i) => i.type === "NonceAdvance"); + assert.ok(nonceAdvance, "NonceAdvance should be in instructionsData"); + assert.ok(parsed.durableNonce, "durableNonce should also be populated"); + // Verify the data matches + if (nonceAdvance && nonceAdvance.type === "NonceAdvance" && parsed.durableNonce) { + assert.strictEqual(nonceAdvance.walletNonceAddress, parsed.durableNonce.walletNonceAddress); + assert.strictEqual(nonceAdvance.authWalletAddress, parsed.durableNonce.authWalletAddress); + } + }); + + it("should parse Transfer instruction correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + // Transfer is at index 1 (after NonceAdvance) + const instr = parsed.instructionsData[1]; + + assert.strictEqual(instr.type, "Transfer"); + if (instr.type === "Transfer") { + const expectedTransfer = EXPECTED.instructionsData[1]; + if (expectedTransfer.type === "Transfer") { + assert.strictEqual(instr.fromAddress, expectedTransfer.fromAddress); + assert.strictEqual(instr.toAddress, expectedTransfer.toAddress); + assert.strictEqual(instr.amount, expectedTransfer.amount); + } + } + }); + + it("should parse Memo instruction correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + // Memo is at index 2 (after NonceAdvance and Transfer) + const instr = parsed.instructionsData[2]; + + assert.strictEqual(instr.type, "Memo"); + if (instr.type === "Memo") { + const expectedMemo = EXPECTED.instructionsData[2]; + if (expectedMemo.type === "Memo") { + assert.strictEqual(instr.memo, expectedMemo.memo); + } + } + }); + + it("should have correct number of instructions", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + // 3 instructions: NonceAdvance + Transfer + Memo + assert.strictEqual(parsed.instructionsData.length, 3); + }); + }); + + describe("Multi transfer transaction", () => { + // From BitGoJS: test/resources/sol.ts - MULTI_TRANSFER_SIGNED + const TX_BASE64 = + "ARbBf3TOkZIuuO2ziM3aACNNdYKDcumvwrylryRXRabSipz6t4VY0ccLsH7v9v8o/k9TVaToi9eAKBR0C0NRzgYBAAMLReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0FLR9EoOL6wqR8uLpnq0nwpNHchcLqBetRGhm70JDF+8kze2o1mtPDaZbuLoBDbpF4Ym6uNOoiXV4Z/XzIP2qDiVfSSHY6HxxiRep+SggDoFZcJjEpbyDbmNXstOeVFqelv+hKJ+pxZaLwHGEyk2Svp5PfAC5ZEi/wYI1tPTHHhbqOP64stlmOImTCUdTdWfXmX4VEgLlAxGjAYzAqkGvGpqRgbUvftkOrrDa1Lcb60U4NhZ2ExcX6TCVgQX7ejidWvmf90gv+iLyF+MaUVKbB3PxFvBm0rWUtT2LJWOlSvUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUpTWpkpIQZNJOhxYNo4fHw1td28kruB5B+oQEEFRI0Gp9UXGSxWjuCKhF9z0peIzwNcMUWyGrNE2AYuqUAAAOMy2vkvq+zotj/3pEAF5f39mvoVh1a2HFqV+QSzuNCBCAgDBAoABAQAAAAIAgAGDAIAAADgkwQAAAAAAAgCAAIMAgAAAOCTBAAAAAAACAIABQwCAAAA4JMEAAAAAAAIAgAHDAIAAADgkwQAAAAAAAgCAAEMAgAAAOCTBAAAAAAACAIAAwwCAAAA4JMEAAAAAAAJAAl0ZXN0IG1lbW8="; + + // Expected values from BitGoJS test/unit/transaction.ts lines 63-141 + const EXPECTED_FEE_PAYER = "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe"; + const EXPECTED_NONCE = "GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi"; + const EXPECTED_TRANSFER_RECIPIENTS = [ + "CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S", + "6B55XMiaS6tUZw5Tt3G1RaXAqdrvN38yXVDJmWvKLkiM", + "C1UjpxcXNBpp1UyvYsuNBNZ5Da1G1i49g3yTvC23Ny7e", + "CpUYXh9xXoWfkBVaBQRZ8nAgDbT16GZeQdqveeBS1hmk", + "64s6NjmEokdhicHEd432X5Ut2EDfDmVqdvGh4rASn1gd", + "6nXxL2jMSdkgfHm13Twvn1gzRAPdrWnWLfu89PJL3Aqe", + ]; + + it("should parse multi-transfer with correct structure", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + assert.strictEqual(parsed.feePayer, EXPECTED_FEE_PAYER); + assert.strictEqual(parsed.nonce, EXPECTED_NONCE); + // NonceAdvance + 6 Transfers + 1 Memo = 8 instructions + assert.strictEqual(parsed.instructionsData.length, 8); + assert.ok(parsed.durableNonce, "Should have durableNonce"); + assert.strictEqual(parsed.instructionsData[0].type, "NonceAdvance"); + }); + + it("should parse all transfer recipients correctly", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + // Transfers are at indices 1-6 (index 0 is NonceAdvance) + const transfers = parsed.instructionsData.slice(1, 7); + assert.strictEqual(transfers.length, 6); + + for (let i = 0; i < transfers.length; i++) { + const transfer = transfers[i]; + assert.strictEqual(transfer.type, "Transfer", `Instruction ${i + 1} should be Transfer`); + if (transfer.type === "Transfer") { + assert.strictEqual(transfer.toAddress, EXPECTED_TRANSFER_RECIPIENTS[i]); + assert.strictEqual(transfer.amount, "300000"); + assert.strictEqual(transfer.fromAddress, EXPECTED_FEE_PAYER); + } + } + }); + + it("should have memo as last instruction", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + const lastInstr = parsed.instructionsData[parsed.instructionsData.length - 1]; + + assert.strictEqual(lastInstr.type, "Memo"); + if (lastInstr.type === "Memo") { + assert.strictEqual(lastInstr.memo, "test memo"); + } + }); + }); + + describe("Staking activate transaction", () => { + // From BitGoJS: test/resources/sol.ts - STAKING_ACTIVATE_SIGNED_TX + const TX_BASE64 = + "AgqGWxEJnQ6oPZd9ysQx+RoWZiNC5caG1vZfCKihyobmUMA/mj7tUVV3j02GUl25Cm7letLefgUz9WB+kXAe4ABUzgW/NnG7GeZGxTVAsEWxGK93sc/cNVFODjkf97ap2bugoN48UG3jBA0JvcNa35xPVrJVdB8VW8dWe/jfxSgMAgAHCUXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItBYnsvugEnYfm5Gbz5TLtMncgFHZ8JMpkxTTlJIzJovekAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALH5eXiaHEfRPEjYei8nFxDrf5MmCVEXMWKQvWwp2vXZBqHYF5E3VCqYNDe9/ip6slV/U1yKeHIraKSdwAAAAAAGodgXpQIFC2gHkebObbiOHltxUPYfxnkKTrTRAAAAAAan1RcYx3TJKFZjmGkdXraLXrijm0ttXHNVWyEAAAAABqfVFxksXFEhjMlMPUrxf1ja7gibof1E49vZigAAAAAGp9UXGTWE0P7tm7NDHRMga+VEKBtXuFZsxTdf9AAAAOMy2vkvq+zotj/3pEAF5f39mvoVh1a2HFqV+QSzuNCBAwICAAE0AAAAAOCTBAAAAAAAyAAAAAAAAAAGodgXkTdUKpg0N73+KnqyVX9TXIp4citopJ3AAAAAAAQCAQd0AAAAAEXlebz5JTz2i0ff8fs6OlwsIbrFsjwJrhKm4FVr8ItBReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0EAAAAAAAAAAAAAAAAAAAAAReV5vPklPPaLR9/x+zo6XCwhusWyPAmuEqbgVWvwi0EEBgEDBggFAAQCAAAA"; + + it("should parse staking transaction structure", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + assert.strictEqual(parsed.feePayer, "5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe"); + assert.ok(parsed.instructionsData.length >= 1, "Should have instructions"); + + // Check that we can identify system and stake program instructions + const types = parsed.instructionsData.map((i) => i.type); + assert.ok( + types.includes("CreateAccount") || + types.includes("StakingActivate") || + types.includes("StakingDelegate"), + `Should have staking-related instructions, got: ${types.join(", ")}`, + ); + }); + }); + + describe("Token transfer transaction", () => { + // From BitGoJS: test/resources/sol.ts - TOKEN_TRANSFER_SIGNED_TX_WITH_MEMO_AND_DURABLE_NONCE + const TX_BASE64 = + "AV6dvFclQvoTuCoia6uKVEUuUnV6Vzuzoyrbn9r/hvlDupmR6Y+zRtKCyIoAu7Yn4SDswSP5ihpsRl+sla53rQABAAYKAGymKVqOJEQemBHH67uu8ISJV4rtwTejLrjw7VSeW6dv+hKJ+pxZaLwHGEyk2Svp5PfAC5ZEi/wYI1tPTHHhbpXS8VwMObd6fTnfCKrnxvwQ5LFhipVbiG+aiTNM1eFsqRgbUvftkOrrDa1Lcb60U4NhZ2ExcX6TCVgQX7ejidUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAA0QOJ+87lKPIIYR3MxzSzEJJUDLK41Y0QDy6qLO202l4FSlNamSkhBk0k6HFg2jh8fDW13bySu4HkH6hAQQVEjQan1RcZLFaO4IqEX3PSl4jPA1wxRbIas0TYBi6pQAAABt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKnjMtr5L6vs6LY/96RABeX9/Zr6FYdWthxalfkEs7jQgQQEAwEIAAQEAAAABQAJA4CWmAAAAAAACQQCBgMACgzgkwQAAAAAAAkHAAl0ZXN0IG1lbW8="; + + it("should parse token transfer transaction", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + // Should have 4 instructions: NonceAdvance, SetPriorityFee, TokenTransfer, Memo + assert.strictEqual(parsed.instructionsData.length, 4); + assert.ok(parsed.durableNonce, "Should have durableNonce"); + + const types = parsed.instructionsData.map((i) => i.type); + assert.strictEqual(types[0], "NonceAdvance", "First should be NonceAdvance"); + assert.strictEqual(types[1], "SetPriorityFee", "Second should be SetPriorityFee"); + assert.strictEqual(types[2], "TokenTransfer", "Third should be TokenTransfer"); + assert.strictEqual(types[3], "Memo", "Fourth should be Memo"); + + // Check token transfer details + const tokenTransfer = parsed.instructionsData[2]; + if (tokenTransfer.type === "TokenTransfer") { + assert.strictEqual(tokenTransfer.amount, "300000"); + } + }); + }); + + describe("Simple unsigned transfer", () => { + // From BitGoJS: test/resources/sol.ts - RAW_TX_UNSIGNED + const TX_BASE64 = + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAIF1NAQCUWYPPTiKY7R/E6KZUKc6Cfr4EUtPm/5/SxQojC7/8v6bBS5ivQMOPXcf/+IbTe8TTN0fjWV33cOwFlm7v5/ZxIQXcf05+tDimmyGgnt1z0tG4opHSR2L2GlM6FGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGp9UXGSxWjuCKhF9z0peIzwNcMUWyGrNE2AYuqUAAAOghhIq8A3U5dDmSv3/3VTX6V+0obalzpFvB2Pemp8/uAgMDAgQABAQAAAADAgABDAIAAACghgEAAAAAAA=="; + + it("should parse basic unsigned transfer", () => { + const bytes = base64ToBytes(TX_BASE64); + const parsed = parseTransaction(bytes); + + // This is a durable nonce transaction: NonceAdvance + Transfer + assert.strictEqual(parsed.instructionsData.length, 2); + assert.strictEqual(parsed.instructionsData[0].type, "NonceAdvance"); + assert.strictEqual(parsed.instructionsData[1].type, "Transfer"); + assert.ok(parsed.durableNonce, "Should have durableNonce"); + + if (parsed.instructionsData[1].type === "Transfer") { + // 100000 lamports = 0x186a0 + assert.strictEqual(parsed.instructionsData[1].amount, "100000"); + } + }); + }); + + 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"); + }); + }); +}); diff --git a/packages/wasm-solana/test/builder.ts b/packages/wasm-solana/test/builder.ts new file mode 100644 index 0000000..ffc784a --- /dev/null +++ b/packages/wasm-solana/test/builder.ts @@ -0,0 +1,694 @@ +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 BigInt + assert.strictEqual(priorityFee.fee, BigInt(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); + }); + }); + + // ===== 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"); + }); + }); +}); diff --git a/packages/wasm-solana/test/parser.ts b/packages/wasm-solana/test/parser.ts new file mode 100644 index 0000000..42b5234 --- /dev/null +++ b/packages/wasm-solana/test/parser.ts @@ -0,0 +1,107 @@ +import * as assert from "assert"; +import { parseTransaction, type ParsedTransaction } from "../js/parser.js"; + +// Helper to decode base64 in tests +function base64ToBytes(base64: string): Uint8Array { + const binary = Buffer.from(base64, "base64"); + return new Uint8Array(binary); +} + +describe("parseTransaction", () => { + // Test transaction from @solana/web3.js - a simple SOL transfer (100000 lamports) + const TEST_TX_BASE64 = + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDFVMqpim7tqEi2XL8R6KKkP0DYJvY3eiRXLlL1P9EjYgXKQC+k0FKnqyC4AZGJR7OhJXfpPP3NHOhS8t/6G7bLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1c7Oaj3RbyLIjU0/ZPpsmVfVUWAzc8g36fK5g6A0JoBAgIAAQwCAAAAoIYBAAAAAAA="; + + const TEST_TX_BYTES = base64ToBytes(TEST_TX_BASE64); + + it("should parse a SOL transfer transaction", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + + // Check basic structure + assert.ok(parsed.feePayer); + assert.ok(parsed.nonce); + assert.strictEqual(parsed.numSignatures, 1); + assert.ok(parsed.instructionsData.length > 0); + assert.ok(parsed.signatures.length > 0); + assert.ok(parsed.accountKeys.length > 0); + }); + + it("should decode SOL transfer instruction correctly", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + + assert.strictEqual(parsed.instructionsData.length, 1); + const instr = parsed.instructionsData[0]; + + // Should be a Transfer instruction + assert.strictEqual(instr.type, "Transfer"); + + // Type guard to access Transfer-specific fields + if (instr.type === "Transfer") { + assert.ok(instr.fromAddress); + assert.ok(instr.toAddress); + // Amount should be 100000 lamports (from test tx) + assert.strictEqual(instr.amount, "100000"); + } + }); + + it("should include fee payer as first account key", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + + assert.strictEqual(parsed.feePayer, parsed.accountKeys[0]); + }); + + it("should have signatures as base64 strings", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + + assert.ok(parsed.signatures.length > 0); + // Signatures should be base64 encoded (string) + for (const sig of parsed.signatures) { + assert.strictEqual(typeof sig, "string"); + // Base64 of 64 bytes is 88 characters + assert.ok(sig.length > 0); + } + }); + + it("should reject invalid bytes", () => { + const invalidBytes = new Uint8Array([0, 1, 2, 3]); + assert.throws(() => parseTransaction(invalidBytes)); + }); + + it("should set durableNonce for nonce transactions", () => { + // This is a regular (non-nonce) transaction, so durableNonce should be undefined + const parsed = parseTransaction(TEST_TX_BYTES); + assert.strictEqual(parsed.durableNonce, undefined); + }); + + it("should serialize to valid JSON", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + const json = JSON.stringify(parsed); + + // Should be valid JSON + const reparsed = JSON.parse(json) as ParsedTransaction; + assert.strictEqual(reparsed.feePayer, parsed.feePayer); + assert.strictEqual(reparsed.instructionsData.length, parsed.instructionsData.length); + }); + + describe("instruction type discrimination", () => { + it("should have type field on all instructions", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + + for (const instr of parsed.instructionsData) { + assert.ok("type" in instr, "Instruction should have type field"); + assert.strictEqual(typeof instr.type, "string"); + } + }); + + it("Transfer instruction should have correct fields", () => { + const parsed = parseTransaction(TEST_TX_BYTES); + const transfer = parsed.instructionsData[0]; + + if (transfer.type === "Transfer") { + assert.ok("fromAddress" in transfer); + assert.ok("toAddress" in transfer); + assert.ok("amount" in transfer); + } + }); + }); +}); diff --git a/packages/wasm-solana/test/transaction.ts b/packages/wasm-solana/test/transaction.ts new file mode 100644 index 0000000..528d3a2 --- /dev/null +++ b/packages/wasm-solana/test/transaction.ts @@ -0,0 +1,127 @@ +import * as assert from "assert"; +import { Transaction } from "../js/transaction.js"; + +// Helper to decode base64 in tests +function base64ToBytes(base64: string): Uint8Array { + const binary = Buffer.from(base64, "base64"); + return new Uint8Array(binary); +} + +describe("Transaction", () => { + // Test transaction from @solana/web3.js - a simple SOL transfer + // This is a real transaction serialized with Transaction.serialize() + const TEST_TX_BASE64 = + "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDFVMqpim7tqEi2XL8R6KKkP0DYJvY3eiRXLlL1P9EjYgXKQC+k0FKnqyC4AZGJR7OhJXfpPP3NHOhS8t/6G7bLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1c7Oaj3RbyLIjU0/ZPpsmVfVUWAzc8g36fK5g6A0JoBAgIAAQwCAAAAoIYBAAAAAAA="; + + const TEST_TX_BYTES = base64ToBytes(TEST_TX_BASE64); + + it("should deserialize transaction from bytes", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + + assert.ok(tx.numSignatures > 0); + assert.ok(tx.instructions().length > 0); + }); + + it("should get fee payer", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const feePayer = tx.feePayer; + + assert.ok(feePayer); + // Fee payer should be a valid base58 Solana address + assert.ok(feePayer.length >= 32 && feePayer.length <= 44); + }); + + it("should get recent blockhash", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const blockhash = tx.recentBlockhash; + + // Blockhash should be a valid base58 string + assert.ok(blockhash.length >= 32 && blockhash.length <= 44); + }); + + it("should get account keys", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const keys = tx.accountKeys(); + + assert.ok(Array.isArray(keys)); + assert.ok(keys.length >= 1); + // First key should be the fee payer + assert.strictEqual(keys[0].toBase58(), tx.feePayer); + }); + + it("should roundtrip bytes", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const serialized = tx.toBytes(); + + const tx2 = Transaction.fromBytes(serialized); + assert.strictEqual(tx.numSignatures, tx2.numSignatures); + assert.strictEqual(tx.instructions().length, tx2.instructions().length); + assert.strictEqual(tx.recentBlockhash, tx2.recentBlockhash); + }); + + it("should get signable payload", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const payload = tx.signablePayload(); + + assert.ok(payload instanceof Uint8Array); + assert.ok(payload.length > 0); + }); + + it("should get signatures as bytes", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const sigs = tx.signatures(); + + assert.ok(Array.isArray(sigs)); + assert.strictEqual(sigs.length, tx.numSignatures); + + // Each signature should be 64 bytes + for (const sig of sigs) { + assert.ok(sig instanceof Uint8Array); + assert.strictEqual(sig.length, 64); + } + }); + + it("should reject invalid transaction bytes", () => { + const invalidBytes = new Uint8Array([0, 1, 2, 3]); + assert.throws(() => Transaction.fromBytes(invalidBytes), /deserialize/); + }); + + it("should get instructions", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const instructions = tx.instructions(); + + assert.ok(Array.isArray(instructions)); + assert.ok(instructions.length > 0); + + // Check first instruction structure + const instr = instructions[0]; + assert.ok(typeof instr.programId === "string"); + assert.ok(Array.isArray(instr.accounts)); + assert.ok(instr.data instanceof Uint8Array); + }); + + it("should get instruction accounts with signer/writable flags", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const instructions = tx.instructions(); + + assert.ok(instructions.length > 0); + const instr = instructions[0]; + assert.ok(instr.accounts.length > 0); + + // Check account structure + const account = instr.accounts[0]; + assert.ok(typeof account.pubkey === "string"); + assert.ok(typeof account.isSigner === "boolean"); + assert.ok(typeof account.isWritable === "boolean"); + }); + + it("should have System Program as program ID for SOL transfer", () => { + const tx = Transaction.fromBytes(TEST_TX_BYTES); + const instructions = tx.instructions(); + + assert.ok(instructions.length > 0); + const instr = instructions[0]; + // System program ID is 11111111111111111111111111111111 + assert.strictEqual(instr.programId, "11111111111111111111111111111111"); + }); +});