diff --git a/.gitignore b/.gitignore index b2db6a9..88ba5b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,34 @@ -.anchor -.DS_Store +# Build outputs +ts-sdk/dist target -**/*.rs.bk +dist + +# Dependencies node_modules -test-ledger -.yarn -yarn.lock Cargo.lock -.env \ No newline at end of file +yarn.lock + +# Environment and secrets +.env +.keypair +*-wallet.json +*.key + +# OS files +.DS_Store + +# Rust backup files +**/*.rs.bk + +# Test artifacts +test-ledger +.anchor + +# Misc +.surfpool +.yarn.idea/ +*.so +*.dylib +build_error.log +validator.log +.test-ledger diff --git a/.prettierignore b/.prettierignore index 4142583..678e634 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,5 @@ node_modules dist build test-ledger +.env +.env.* \ No newline at end of file diff --git a/Anchor.toml b/Anchor.toml deleted file mode 100644 index d586116..0000000 --- a/Anchor.toml +++ /dev/null @@ -1,26 +0,0 @@ -[toolchain] -package_manager = "yarn" - -[features] -resolution = true -skip-lint = false - -[programs.devnet] -lazorkit = "6Jh4kA4rkZquv9XofKqgbyrRcTDF19uM5HL4xyh6gaSo" -transfer_limit = "EEVtLAZVcyzrEc4LLfk8WB749uAkLsScbCVrjtQv3yQB" -default_rule = "7H16pVKG2stkkhQ6H9LyXvnHLpXjfB7LLShGjXhYmEWs" - -[programs.localnet] -lazorkit = "6Jh4kA4rkZquv9XofKqgbyrRcTDF19uM5HL4xyh6gaSo" -transfer_limit = "EEVtLAZVcyzrEc4LLfk8WB749uAkLsScbCVrjtQv3yQB" -default_rule = "7H16pVKG2stkkhQ6H9LyXvnHLpXjfB7LLShGjXhYmEWs" - -[registry] -url = "https://api.apr.dev" - -[provider] -cluster = "devnet" -wallet = "~/.config/solana/id.json" - -[scripts] -test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.test.ts" diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c68b0b6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,5807 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[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", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[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 = "ascii" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" + +[[package]] +name = "assertions" +version = "0.1.0" +dependencies = [ + "pinocchio", + "pinocchio-pubkey", + "pinocchio-system", +] + +[[package]] +name = "async-compression" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +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.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[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 = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "blake3" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +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 = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cargo_toml" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719" +dependencies = [ + "serde", + "toml 0.8.23", +] + +[[package]] +name = "cc" +version = "1.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +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 = "cfg_eval" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[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 = "combine" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +dependencies = [ + "ascii", + "byteorder", + "either", + "memchr", + "unreachable", +] + +[[package]] +name = "compression-codecs" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[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.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rand_core 0.6.4", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +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 = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "eager" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe71d579d1812060163dff96056261deb5bf6729b100fa2e36a68b9649ba3d3" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature 2.2.0", + "spki", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.6.4", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "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", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-iterator" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd242f399be1da0a5354aa462d57b4ab2b4ee0683cc552f7c007d2d12d36e94" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "five8_const" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26dec3da8bc3ef08f2c04f61eab298c3ab334523e55f076354d6d6f613799a7b" +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 = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[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.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[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 = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazorkit-program" +version = "0.1.0" +dependencies = [ + "anyhow", + "assertions", + "base64ct", + "blake3", + "borsh 1.6.0", + "ecdsa", + "getrandom 0.2.17", + "litesvm", + "no-padding", + "p256", + "pinocchio", + "pinocchio-pubkey", + "pinocchio-system", + "rand 0.8.5", + "sha2 0.10.9", + "shank", + "shank_idl", + "solana-sdk", + "thiserror 1.0.69", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[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", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[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 = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint 0.4.6", + "thiserror 1.0.69", +] + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litesvm" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7e5f4462f34439adcfcab58099bc7a89c67a17f8240b84a993b8b705c1becb" +dependencies = [ + "ansi_term", + "bincode", + "indexmap", + "itertools 0.14.0", + "log", + "solana-account", + "solana-address-lookup-table-interface", + "solana-bpf-loader-program", + "solana-builtins", + "solana-clock", + "solana-compute-budget", + "solana-compute-budget-instruction", + "solana-config-program", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-feature-set", + "solana-fee", + "solana-fee-structure", + "solana-hash", + "solana-instruction", + "solana-instructions-sysvar", + "solana-keypair", + "solana-last-restart-slot", + "solana-loader-v3-interface", + "solana-loader-v4-interface", + "solana-log-collector", + "solana-measure", + "solana-message", + "solana-native-token", + "solana-nonce", + "solana-nonce-account", + "solana-precompiles", + "solana-program-error", + "solana-program-runtime", + "solana-pubkey", + "solana-rent", + "solana-reserved-account-keys", + "solana-sdk-ids", + "solana-sha256-hasher", + "solana-signature", + "solana-signer", + "solana-slot-hashes", + "solana-slot-history", + "solana-stake-interface", + "solana-svm-transaction", + "solana-system-interface", + "solana-system-program", + "solana-sysvar", + "solana-sysvar-id", + "solana-timings", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "solana-vote-program", + "thiserror 2.0.18", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +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 = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[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 = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "no-padding" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[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-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "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-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint 0.2.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "percentage" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd23b938276f14057220b707937bcb42fa76dda7560e57a2da30cb52d557937" +dependencies = [ + "num", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pinocchio" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b971851087bc3699b001954ad02389d50c41405ece3548cbcafc88b3e20017a" + +[[package]] +name = "pinocchio-pubkey" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0225638cadcbebae8932cb7f49cb5da7c15c21beb19f048f05a5ca7d93f065" +dependencies = [ + "five8_const", + "pinocchio", + "sha2-const-stable", +] + +[[package]] +name = "pinocchio-system" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "141ed5eafb4ab04568bb0e224e3dc9a9de13c933de4c004e0d1a553498be3a7c" +dependencies = [ + "pinocchio", + "pinocchio-pubkey", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[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 = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml 0.5.11", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.10+spec-1.0.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +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 = "qualifier_attr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +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 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "async-compression", + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[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_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 = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "serde_core", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[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 = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[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 = "shank" +version = "0.4.2" +source = "git+https://github.com/anagrambuild/shank.git#d4f046b22b87c896fdb77e55256d74dad6a13a68" +dependencies = [ + "shank_macro", +] + +[[package]] +name = "shank_idl" +version = "0.4.2" +source = "git+https://github.com/anagrambuild/shank.git#d4f046b22b87c896fdb77e55256d74dad6a13a68" +dependencies = [ + "anyhow", + "cargo_toml", + "heck", + "serde", + "serde_json", + "shank_macro_impl", + "shellexpand", +] + +[[package]] +name = "shank_macro" +version = "0.4.2" +source = "git+https://github.com/anagrambuild/shank.git#d4f046b22b87c896fdb77e55256d74dad6a13a68" +dependencies = [ + "proc-macro2", + "quote", + "shank_macro_impl", + "shank_render", + "syn 1.0.109", +] + +[[package]] +name = "shank_macro_impl" +version = "0.4.2" +source = "git+https://github.com/anagrambuild/shank.git#d4f046b22b87c896fdb77e55256d74dad6a13a68" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", +] + +[[package]] +name = "shank_render" +version = "0.4.2" +source = "git+https://github.com/anagrambuild/shank.git#d4f046b22b87c896fdb77e55256d74dad6a13a68" +dependencies = [ + "proc-macro2", + "quote", + "shank_macro_impl", +] + +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[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 = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "solana-account" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f949fe4edaeaea78c844023bfc1c898e0b1f5a100f8a8d2d0f85d0a7b090258" +dependencies = [ + "bincode", + "serde", + "serde_bytes", + "serde_derive", + "solana-account-info", + "solana-clock", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-sysvar", +] + +[[package]] +name = "solana-account-info" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c17d606a298a205fae325489fbed88ee6dc4463c111672172327e741c8905d" +dependencies = [ + "bincode", + "serde", + "solana-program-error", + "solana-program-memory", + "solana-pubkey", +] + +[[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", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-slot-hashes", +] + +[[package]] +name = "solana-address-lookup-table-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87ae97f2d1b91a9790c1e35dba3f90a4d595d105097ad93fa685cbc034ad0f1" +dependencies = [ + "bincode", + "bytemuck", + "log", + "num-derive", + "num-traits", + "solana-address-lookup-table-interface", + "solana-bincode", + "solana-clock", + "solana-feature-set", + "solana-instruction", + "solana-log-collector", + "solana-packet", + "solana-program-runtime", + "solana-pubkey", + "solana-system-interface", + "solana-transaction-context", + "thiserror 2.0.18", +] + +[[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-big-mod-exp" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75db7f2bbac3e62cfd139065d15bcda9e2428883ba61fc8d27ccb251081e7567" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "solana-define-syscall", +] + +[[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", +] + +[[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", + "solana-hash", + "solana-sanitize", +] + +[[package]] +name = "solana-bn254" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9abc69625158faaab02347370b91c0d8e0fe347bf9287239f0fbe8f5864d91da" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "bytemuck", + "solana-define-syscall", + "thiserror 2.0.18", +] + +[[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-bpf-loader-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6931e8893b48e3a1c8124938f580fff857d84895582578cc7dbf100dd08d2c8f" +dependencies = [ + "bincode", + "libsecp256k1", + "qualifier_attr", + "scopeguard", + "solana-account", + "solana-account-info", + "solana-big-mod-exp", + "solana-bincode", + "solana-blake3-hasher", + "solana-bn254", + "solana-clock", + "solana-compute-budget", + "solana-cpi", + "solana-curve25519", + "solana-feature-set", + "solana-hash", + "solana-instruction", + "solana-keccak-hasher", + "solana-loader-v3-interface", + "solana-loader-v4-interface", + "solana-log-collector", + "solana-measure", + "solana-packet", + "solana-poseidon", + "solana-precompiles", + "solana-program-entrypoint", + "solana-program-memory", + "solana-program-runtime", + "solana-pubkey", + "solana-sbpf", + "solana-sdk-ids", + "solana-secp256k1-recover", + "solana-sha256-hasher", + "solana-stable-layout", + "solana-system-interface", + "solana-sysvar", + "solana-sysvar-id", + "solana-timings", + "solana-transaction-context", + "solana-type-overrides", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-builtins" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9240641f944ece59e097c9981bdc33b2f519cbd91b9764ff5f62c307d986a3d" +dependencies = [ + "solana-address-lookup-table-program", + "solana-bpf-loader-program", + "solana-compute-budget-program", + "solana-config-program", + "solana-feature-set", + "solana-loader-v4-program", + "solana-program-runtime", + "solana-pubkey", + "solana-sdk-ids", + "solana-stake-program", + "solana-system-program", + "solana-vote-program", + "solana-zk-elgamal-proof-program", + "solana-zk-token-proof-program", +] + +[[package]] +name = "solana-builtins-default-costs" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb6728141dc45bdde9d68b67bb914013be28f94a2aea8bb7131ea8c6161c30e" +dependencies = [ + "ahash", + "lazy_static", + "log", + "qualifier_attr", + "solana-address-lookup-table-program", + "solana-bpf-loader-program", + "solana-compute-budget-program", + "solana-config-program", + "solana-feature-set", + "solana-loader-v4-program", + "solana-pubkey", + "solana-sdk-ids", + "solana-stake-program", + "solana-system-program", + "solana-vote-program", +] + +[[package]] +name = "solana-client-traits" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f0071874e629f29e0eb3dab8a863e98502ac7aba55b7e0df1803fc5cac72a7" +dependencies = [ + "solana-account", + "solana-commitment-config", + "solana-epoch-info", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-message", + "solana-pubkey", + "solana-signature", + "solana-signer", + "solana-system-interface", + "solana-transaction", + "solana-transaction-error", +] + +[[package]] +name = "solana-clock" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c2177a1b9fe8326004f1151a5acd124420b737811080b1035df31349e4d892" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-cluster-type" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ace9fea2daa28354d107ea879cff107181d85cd4e0f78a2bedb10e1a428c97e" +dependencies = [ + "serde", + "serde_derive", + "solana-hash", +] + +[[package]] +name = "solana-commitment-config" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac49c4dde3edfa832de1697e9bcdb7c3b3f7cb7a1981b7c62526c8bb6700fb73" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-compute-budget" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e593ce26764fa3366b6d125b9f2455f6cd8d557f86b4f3c7b7c517db6d8f5f" +dependencies = [ + "solana-fee-structure", + "solana-program-entrypoint", +] + +[[package]] +name = "solana-compute-budget-instruction" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240e28cf764d1468f2388fb0d10b70278a64d47277ff552379116ba45d609cd1" +dependencies = [ + "log", + "solana-borsh", + "solana-builtins-default-costs", + "solana-compute-budget", + "solana-compute-budget-interface", + "solana-feature-set", + "solana-instruction", + "solana-packet", + "solana-pubkey", + "solana-sdk-ids", + "solana-svm-transaction", + "solana-transaction-error", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-compute-budget-interface" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a5df17b195d312b66dccdde9beec6709766d8230cb4718c4c08854f780d0309" +dependencies = [ + "borsh 1.6.0", + "serde", + "serde_derive", + "solana-instruction", + "solana-sdk-ids", +] + +[[package]] +name = "solana-compute-budget-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc6b8ea70ed5123412655ed15e7e0e29f06a7d5b82eb2572bee608d7755afb7" +dependencies = [ + "qualifier_attr", + "solana-program-runtime", +] + +[[package]] +name = "solana-config-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2417094a8c5c2d60812a5bd6f0bd31bdefc49479826c10347a85d217e088c964" +dependencies = [ + "bincode", + "chrono", + "serde", + "serde_derive", + "solana-account", + "solana-bincode", + "solana-instruction", + "solana-log-collector", + "solana-packet", + "solana-program-runtime", + "solana-pubkey", + "solana-sdk-ids", + "solana-short-vec", + "solana-stake-interface", + "solana-system-interface", + "solana-transaction-context", +] + +[[package]] +name = "solana-cpi" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc71126edddc2ba014622fc32d0f5e2e78ec6c5a1e0eb511b85618c09e9ea11" +dependencies = [ + "solana-account-info", + "solana-define-syscall", + "solana-instruction", + "solana-program-error", + "solana-pubkey", + "solana-stable-layout", +] + +[[package]] +name = "solana-curve25519" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3d15f1a893ced38529d44d7fe0d4348dc38c28fea13b6d6be5d13d438a441f" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "solana-define-syscall", + "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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf784bb2cb3e02cac9801813c30187344228d2ae952534902108f6150573a33d" + +[[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-ed25519-program" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feafa1691ea3ae588f99056f4bdd1293212c7ece28243d7da257c443e84753" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "ed25519-dalek", + "solana-feature-set", + "solana-instruction", + "solana-precompile-error", + "solana-sdk-ids", +] + +[[package]] +name = "solana-epoch-info" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ef6f0b449290b0b9f32973eefd95af35b01c5c0c34c569f936c34c5b20d77b" +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", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-epoch-rewards-hasher" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c5fd2662ae7574810904585fd443545ed2b568dbd304b25a31e79ccc76e81b" +dependencies = [ + "siphasher", + "solana-hash", + "solana-pubkey", +] + +[[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", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-example-mocks" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84461d56cbb8bb8d539347151e0525b53910102e4bced875d49d5139708e39d3" +dependencies = [ + "serde", + "serde_derive", + "solana-address-lookup-table-interface", + "solana-clock", + "solana-hash", + "solana-instruction", + "solana-keccak-hasher", + "solana-message", + "solana-nonce", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-feature-gate-interface" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9c7fbf3e58b64a667c5f35e90af580538a95daea7001ff7806c0662d301bdf" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-account", + "solana-account-info", + "solana-instruction", + "solana-program-error", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-system-interface", +] + +[[package]] +name = "solana-feature-set" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e1d3b52b4a014efeaaab67f14e40af3972a4be61c523d612860db8e3145529" +dependencies = [ + "ahash", + "lazy_static", + "solana-epoch-schedule", + "solana-hash", + "solana-pubkey", + "solana-sha256-hasher", +] + +[[package]] +name = "solana-fee" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c14eaaa9d099e4510c9105522d97778cd66c3d401f0d68eebcf43179a1bf094" +dependencies = [ + "solana-feature-set", + "solana-fee-structure", + "solana-svm-transaction", +] + +[[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-structure" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f45f94a88efdb512805563181dfa1c85c60a21b6e6d602bf24a2ea88f9399d6e" +dependencies = [ + "serde", + "serde_derive", + "solana-message", + "solana-native-token", +] + +[[package]] +name = "solana-genesis-config" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968dabd2b92d57131473eddbd475339da530e14f54397386abf303de3a2595a2" +dependencies = [ + "bincode", + "chrono", + "memmap2", + "serde", + "serde_derive", + "solana-account", + "solana-clock", + "solana-cluster-type", + "solana-epoch-schedule", + "solana-fee-calculator", + "solana-hash", + "solana-inflation", + "solana-keypair", + "solana-logger", + "solana-native-token", + "solana-poh-config", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-sha256-hasher", + "solana-shred-version", + "solana-signer", + "solana-time-utils", +] + +[[package]] +name = "solana-hard-forks" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c28371f878e2ead55611d8ba1b5fb879847156d04edea13693700ad1a28baf" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-hash" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7bcb14392900fe02e4e34e90234fbf0c673d4e327888410ba99fa2ba0f4e99" +dependencies = [ + "borsh 1.6.0", + "bs58", + "bytemuck", + "bytemuck_derive", + "js-sys", + "serde", + "serde_derive", + "solana-atomic-u64", + "solana-sanitize", + "wasm-bindgen", +] + +[[package]] +name = "solana-inflation" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23eef6a09eb8e568ce6839573e4966850e85e9ce71e6ae1a6c930c1c43947de3" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-instruction" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce496a475e5062ba5de97215ab39d9c358f9c9df4bb7f3a45a1f1a8bd9065ed" +dependencies = [ + "bincode", + "borsh 1.6.0", + "getrandom 0.2.17", + "js-sys", + "num-traits", + "serde", + "serde_derive", + "solana-define-syscall", + "solana-pubkey", + "wasm-bindgen", +] + +[[package]] +name = "solana-instructions-sysvar" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427f2d0d6dc0bb49f16cef5e7f975180d2e80aab9bdd3b2af68e2d029ec63f43" +dependencies = [ + "bitflags 2.10.0", + "solana-account-info", + "solana-instruction", + "solana-program-error", + "solana-pubkey", + "solana-sanitize", + "solana-sdk-ids", + "solana-serialize-utils", + "solana-sysvar-id", +] + +[[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", + "solana-hash", + "solana-sanitize", +] + +[[package]] +name = "solana-keypair" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dbb7042c2e0c561afa07242b2099d55c57bd1b1da3b6476932197d84e15e3e4" +dependencies = [ + "bs58", + "ed25519-dalek", + "ed25519-dalek-bip32", + "rand 0.7.3", + "solana-derivation-path", + "solana-pubkey", + "solana-seed-derivable", + "solana-seed-phrase", + "solana-signature", + "solana-signer", + "wasm-bindgen", +] + +[[package]] +name = "solana-last-restart-slot" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6360ac2fdc72e7463565cd256eedcf10d7ef0c28a1249d261ec168c1b55cdd" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[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", + "solana-pubkey", + "solana-sdk-ids", +] + +[[package]] +name = "solana-loader-v3-interface" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4be76cfa9afd84ca2f35ebc09f0da0f0092935ccdac0595d98447f259538c2" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", +] + +[[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", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", +] + +[[package]] +name = "solana-loader-v4-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0298bf161e18b146230b15e8fa57bd170a05342ab9c1fd996b0241c0f016c2" +dependencies = [ + "log", + "qualifier_attr", + "solana-account", + "solana-bincode", + "solana-bpf-loader-program", + "solana-compute-budget", + "solana-instruction", + "solana-loader-v3-interface", + "solana-loader-v4-interface", + "solana-log-collector", + "solana-measure", + "solana-packet", + "solana-program-runtime", + "solana-pubkey", + "solana-sbpf", + "solana-sdk-ids", + "solana-transaction-context", + "solana-type-overrides", +] + +[[package]] +name = "solana-log-collector" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d03bf4c676117575be755296e8f21233d74cd28dca227c42e97e86219a27193" +dependencies = [ + "log", +] + +[[package]] +name = "solana-logger" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8e777ec1afd733939b532a42492d888ec7c88d8b4127a5d867eb45c6eb5cd5" +dependencies = [ + "env_logger", + "lazy_static", + "libc", + "log", + "signal-hook", +] + +[[package]] +name = "solana-measure" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b17ee553110d2bfc454b8784840a4b75867e123d3816e13046989463fed2c6b" + +[[package]] +name = "solana-message" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268486ba8a294ed22a4d7c1ec05f540c3dbe71cfa7c6c54b6d4d13668d895678" +dependencies = [ + "bincode", + "blake3", + "lazy_static", + "serde", + "serde_derive", + "solana-bincode", + "solana-hash", + "solana-instruction", + "solana-pubkey", + "solana-sanitize", + "solana-sdk-ids", + "solana-short-vec", + "solana-system-interface", + "solana-transaction-error", + "wasm-bindgen", +] + +[[package]] +name = "solana-metrics" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b79bd642efa8388791fef7a900bfeb48865669148d523fba041fa7e407312f" +dependencies = [ + "crossbeam-channel", + "gethostname", + "lazy_static", + "log", + "reqwest", + "solana-clock", + "solana-cluster-type", + "solana-sha256-hasher", + "solana-time-utils", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-msg" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36a1a14399afaabc2781a1db09cb14ee4cc4ee5c7a5a3cfcc601811379a8092" +dependencies = [ + "solana-define-syscall", +] + +[[package]] +name = "solana-native-token" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e9de00960197412e4be3902a6cd35e60817c511137aca6c34c66cd5d4017ec" + +[[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", + "solana-hash", + "solana-pubkey", + "solana-sha256-hasher", +] + +[[package]] +name = "solana-nonce-account" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde971a20b8dbf60144d6a84439dda86b5466e00e2843091fe731083cda614da" +dependencies = [ + "solana-account", + "solana-hash", + "solana-nonce", + "solana-sdk-ids", +] + +[[package]] +name = "solana-offchain-message" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b526398ade5dea37f1f147ce55dae49aa017a5d7326606359b0445ca8d946581" +dependencies = [ + "num_enum", + "solana-hash", + "solana-packet", + "solana-pubkey", + "solana-sanitize", + "solana-sha256-hasher", + "solana-signature", + "solana-signer", +] + +[[package]] +name = "solana-packet" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004f2d2daf407b3ec1a1ca5ec34b3ccdfd6866dd2d3c7d0715004a96e4b6d127" +dependencies = [ + "bincode", + "bitflags 2.10.0", + "cfg_eval", + "serde", + "serde_derive", + "serde_with", +] + +[[package]] +name = "solana-poh-config" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d650c3b4b9060082ac6b0efbbb66865089c58405bfb45de449f3f2b91eccee75" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-poseidon" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2908b48b3828bc04b752d1ff36122f5a06de043258da88df5f8ce64791d208" +dependencies = [ + "ark-bn254", + "light-poseidon", + "solana-define-syscall", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-precompile-error" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d87b2c1f5de77dfe2b175ee8dd318d196aaca4d0f66f02842f80c852811f9f8" +dependencies = [ + "num-traits", + "solana-decode-error", +] + +[[package]] +name = "solana-precompiles" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a460ab805ec063802105b463ecb5eb02c3ffe469e67a967eea8a6e778e0bc06" +dependencies = [ + "lazy_static", + "solana-ed25519-program", + "solana-feature-set", + "solana-message", + "solana-precompile-error", + "solana-pubkey", + "solana-sdk-ids", + "solana-secp256k1-program", + "solana-secp256r1-program", +] + +[[package]] +name = "solana-presigner" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81a57a24e6a4125fc69510b6774cd93402b943191b6cddad05de7281491c90fe" +dependencies = [ + "solana-pubkey", + "solana-signature", + "solana-signer", +] + +[[package]] +name = "solana-program" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "586469467e93ceb79048f8d8e3a619bf61d05396ee7de95cb40280301a589d05" +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 0.4.6", + "num-derive", + "num-traits", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_derive", + "solana-account-info", + "solana-address-lookup-table-interface", + "solana-atomic-u64", + "solana-big-mod-exp", + "solana-bincode", + "solana-blake3-hasher", + "solana-borsh", + "solana-clock", + "solana-cpi", + "solana-decode-error", + "solana-define-syscall", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-example-mocks", + "solana-feature-gate-interface", + "solana-fee-calculator", + "solana-hash", + "solana-instruction", + "solana-instructions-sysvar", + "solana-keccak-hasher", + "solana-last-restart-slot", + "solana-loader-v2-interface", + "solana-loader-v3-interface", + "solana-loader-v4-interface", + "solana-message", + "solana-msg", + "solana-native-token", + "solana-nonce", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-program-option", + "solana-program-pack", + "solana-pubkey", + "solana-rent", + "solana-sanitize", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-secp256k1-recover", + "solana-serde-varint", + "solana-serialize-utils", + "solana-sha256-hasher", + "solana-short-vec", + "solana-slot-hashes", + "solana-slot-history", + "solana-stable-layout", + "solana-stake-interface", + "solana-system-interface", + "solana-sysvar", + "solana-sysvar-id", + "solana-vote-interface", + "thiserror 2.0.18", + "wasm-bindgen", +] + +[[package]] +name = "solana-program-entrypoint" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473ffe73c68d93e9f2aa726ad2985fe52760052709aaab188100a42c618060ec" +dependencies = [ + "solana-account-info", + "solana-msg", + "solana-program-error", + "solana-pubkey", +] + +[[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", + "solana-msg", + "solana-pubkey", +] + +[[package]] +name = "solana-program-memory" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b0268f6c89825fb634a34bd0c3b8fdaeaecfc3728be1d622a8ee6dd577b60d4" +dependencies = [ + "num-traits", + "solana-define-syscall", +] + +[[package]] +name = "solana-program-option" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc677a2e9bc616eda6dbdab834d463372b92848b2bfe4a1ed4e4b4adba3397d0" + +[[package]] +name = "solana-program-pack" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "319f0ef15e6e12dc37c597faccb7d62525a509fec5f6975ecb9419efddeb277b" +dependencies = [ + "solana-program-error", +] + +[[package]] +name = "solana-program-runtime" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce0a9acc6049c2ae8a2a2dd0b63269ab1a6d8fab4dead1aae75a9bcdd4aa6f05" +dependencies = [ + "base64 0.22.1", + "bincode", + "enum-iterator", + "itertools 0.12.1", + "log", + "percentage", + "rand 0.8.5", + "serde", + "solana-account", + "solana-clock", + "solana-compute-budget", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-feature-set", + "solana-hash", + "solana-instruction", + "solana-last-restart-slot", + "solana-log-collector", + "solana-measure", + "solana-metrics", + "solana-precompiles", + "solana-pubkey", + "solana-rent", + "solana-sbpf", + "solana-sdk-ids", + "solana-slot-hashes", + "solana-stable-layout", + "solana-sysvar", + "solana-sysvar-id", + "solana-timings", + "solana-transaction-context", + "solana-type-overrides", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-pubkey" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40db1ff5a0f8aea2c158d78ab5f2cf897848964251d1df42fef78efd3c85b863" +dependencies = [ + "borsh 0.10.4", + "borsh 1.6.0", + "bs58", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "five8_const", + "getrandom 0.2.17", + "js-sys", + "num-traits", + "rand 0.8.5", + "serde", + "serde_derive", + "solana-atomic-u64", + "solana-decode-error", + "solana-define-syscall", + "solana-sanitize", + "solana-sha256-hasher", + "wasm-bindgen", +] + +[[package]] +name = "solana-quic-definitions" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e606feac5110eb5d8afaa43ccaeea3ec49ccec36773387930b5ba545e745aea2" +dependencies = [ + "solana-keypair", +] + +[[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", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-rent-collector" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "127e6dfa51e8c8ae3aa646d8b2672bc4ac901972a338a9e1cd249e030564fb9d" +dependencies = [ + "serde", + "serde_derive", + "solana-account", + "solana-clock", + "solana-epoch-schedule", + "solana-genesis-config", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", +] + +[[package]] +name = "solana-rent-debits" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6f9113c6003492e74438d1288e30cffa8ccfdc2ef7b49b9e816d8034da18cd" +dependencies = [ + "solana-pubkey", + "solana-reward-info", +] + +[[package]] +name = "solana-reserved-account-keys" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b22ea19ca2a3f28af7cd047c914abf833486bf7a7c4a10fc652fff09b385b1" +dependencies = [ + "lazy_static", + "solana-feature-set", + "solana-pubkey", + "solana-sdk-ids", +] + +[[package]] +name = "solana-reward-info" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18205b69139b1ae0ab8f6e11cdcb627328c0814422ad2482000fa2ca54ae4a2f" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "solana-sanitize" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf" + +[[package]] +name = "solana-sbpf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a3ce7a0f4d6830124ceb2c263c36d1ee39444ec70146eb49b939e557e72b96" +dependencies = [ + "byteorder", + "combine", + "hash32", + "libc", + "log", + "rand 0.8.5", + "rustc-demangle", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "solana-sdk" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4808e8d7f3c931657e615042d4176b423e66f64dc99e3dc3c735a197e512029b" +dependencies = [ + "bincode", + "bs58", + "getrandom 0.1.16", + "js-sys", + "serde", + "serde_json", + "solana-account", + "solana-bn254", + "solana-client-traits", + "solana-cluster-type", + "solana-commitment-config", + "solana-compute-budget-interface", + "solana-decode-error", + "solana-derivation-path", + "solana-ed25519-program", + "solana-epoch-info", + "solana-epoch-rewards-hasher", + "solana-feature-set", + "solana-fee-structure", + "solana-genesis-config", + "solana-hard-forks", + "solana-inflation", + "solana-instruction", + "solana-keypair", + "solana-message", + "solana-native-token", + "solana-nonce-account", + "solana-offchain-message", + "solana-packet", + "solana-poh-config", + "solana-precompile-error", + "solana-precompiles", + "solana-presigner", + "solana-program", + "solana-program-memory", + "solana-pubkey", + "solana-quic-definitions", + "solana-rent-collector", + "solana-rent-debits", + "solana-reserved-account-keys", + "solana-reward-info", + "solana-sanitize", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-secp256k1-program", + "solana-secp256k1-recover", + "solana-secp256r1-program", + "solana-seed-derivable", + "solana-seed-phrase", + "solana-serde", + "solana-serde-varint", + "solana-short-vec", + "solana-shred-version", + "solana-signature", + "solana-signer", + "solana-system-transaction", + "solana-time-utils", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "solana-validator-exit", + "thiserror 2.0.18", + "wasm-bindgen", +] + +[[package]] +name = "solana-sdk-ids" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5d8b9cc68d5c88b062a33e23a6466722467dde0035152d8fb1afbcdf350a5f" +dependencies = [ + "solana-pubkey", +] + +[[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-secp256k1-program" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a1caa972414cc78122c32bdae65ac5fe89df7db598585a5cde19d16a20280a" +dependencies = [ + "bincode", + "digest 0.10.7", + "libsecp256k1", + "serde", + "serde_derive", + "sha3", + "solana-feature-set", + "solana-instruction", + "solana-precompile-error", + "solana-sdk-ids", +] + +[[package]] +name = "solana-secp256k1-recover" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa3120b6cdaa270f39444f5093a90a7b03d296d362878f7a6991d6de3bbe496" +dependencies = [ + "borsh 1.6.0", + "libsecp256k1", + "solana-define-syscall", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-secp256r1-program" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf903cbdc36a161533812f90acfccdb434ed48982bd5dd71f3217930572c4a80" +dependencies = [ + "bytemuck", + "openssl", + "solana-feature-set", + "solana-instruction", + "solana-precompile-error", + "solana-sdk-ids", +] + +[[package]] +name = "solana-seed-derivable" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beb82b5adb266c6ea90e5cf3967235644848eac476c5a1f2f9283a143b7c97f" +dependencies = [ + "solana-derivation-path", +] + +[[package]] +name = "solana-seed-phrase" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36187af2324f079f65a675ec22b31c24919cb4ac22c79472e85d819db9bbbc15" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "sha2 0.10.9", +] + +[[package]] +name = "solana-serde" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1931484a408af466e14171556a47adaa215953c7f48b24e5f6b0282763818b04" +dependencies = [ + "serde", +] + +[[package]] +name = "solana-serde-varint" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc07d00200d82e6def2f7f7a45738e3406b17fe54a18adcf0defa16a97ccadb" +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", + "solana-pubkey", + "solana-sanitize", +] + +[[package]] +name = "solana-sha256-hasher" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0037386961c0d633421f53560ad7c80675c0447cba4d1bb66d60974dd486c7ea" +dependencies = [ + "sha2 0.10.9", + "solana-define-syscall", + "solana-hash", +] + +[[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-shred-version" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afd3db0461089d1ad1a78d9ba3f15b563899ca2386351d38428faa5350c60a98" +dependencies = [ + "solana-hard-forks", + "solana-hash", + "solana-sha256-hasher", +] + +[[package]] +name = "solana-signature" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d251c8f3dc015f320b4161daac7f108156c837428e5a8cc61136d25beb11d6" +dependencies = [ + "bs58", + "ed25519-dalek", + "rand 0.8.5", + "serde", + "serde-big-array", + "serde_derive", + "solana-sanitize", +] + +[[package]] +name = "solana-signer" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c41991508a4b02f021c1342ba00bcfa098630b213726ceadc7cb032e051975b" +dependencies = [ + "solana-pubkey", + "solana-signature", + "solana-transaction-error", +] + +[[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", + "solana-sdk-ids", + "solana-sysvar-id", +] + +[[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", + "solana-sysvar-id", +] + +[[package]] +name = "solana-stable-layout" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f14f7d02af8f2bc1b5efeeae71bc1c2b7f0f65cd75bcc7d8180f2c762a57f54" +dependencies = [ + "solana-instruction", + "solana-pubkey", +] + +[[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", + "solana-cpi", + "solana-decode-error", + "solana-instruction", + "solana-program-error", + "solana-pubkey", + "solana-system-interface", + "solana-sysvar-id", +] + +[[package]] +name = "solana-stake-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b140dad8a60e40c381a0a359a350d37d51827d02ceb623acf8b942c04f3f3e6" +dependencies = [ + "bincode", + "log", + "solana-account", + "solana-bincode", + "solana-clock", + "solana-config-program", + "solana-feature-set", + "solana-genesis-config", + "solana-instruction", + "solana-log-collector", + "solana-native-token", + "solana-packet", + "solana-program-runtime", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-stake-interface", + "solana-sysvar", + "solana-transaction-context", + "solana-type-overrides", + "solana-vote-interface", +] + +[[package]] +name = "solana-svm-transaction" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1da9eb37e6ced0215a5e44df4ed1f3b885cf349156cbbf99197680cb7eaccf5f" +dependencies = [ + "solana-hash", + "solana-message", + "solana-pubkey", + "solana-sdk-ids", + "solana-signature", + "solana-transaction", +] + +[[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", + "solana-pubkey", + "wasm-bindgen", +] + +[[package]] +name = "solana-system-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6321fd5380961387ef4633a98c109ac7f978667ceab2a38d0a699d6ddb2fc57a" +dependencies = [ + "bincode", + "log", + "serde", + "serde_derive", + "solana-account", + "solana-bincode", + "solana-instruction", + "solana-log-collector", + "solana-nonce", + "solana-nonce-account", + "solana-packet", + "solana-program-runtime", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", + "solana-sysvar", + "solana-transaction-context", + "solana-type-overrides", +] + +[[package]] +name = "solana-system-transaction" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd98a25e5bcba8b6be8bcbb7b84b24c2a6a8178d7fb0e3077a916855ceba91a" +dependencies = [ + "solana-hash", + "solana-keypair", + "solana-message", + "solana-pubkey", + "solana-signer", + "solana-system-interface", + "solana-transaction", +] + +[[package]] +name = "solana-sysvar" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6b44740d7f0c9f375d045c165bc0aab4a90658f92d6835aeb0649afaeaff9a" +dependencies = [ + "base64 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "lazy_static", + "serde", + "serde_derive", + "solana-account-info", + "solana-clock", + "solana-define-syscall", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-fee-calculator", + "solana-hash", + "solana-instruction", + "solana-instructions-sysvar", + "solana-last-restart-slot", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-pubkey", + "solana-rent", + "solana-sanitize", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-slot-hashes", + "solana-slot-history", + "solana-stake-interface", + "solana-sysvar-id", +] + +[[package]] +name = "solana-sysvar-id" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5762b273d3325b047cfda250787f8d796d781746860d5d0a746ee29f3e8812c1" +dependencies = [ + "solana-pubkey", + "solana-sdk-ids", +] + +[[package]] +name = "solana-time-utils" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af261afb0e8c39252a04d026e3ea9c405342b08c871a2ad8aa5448e068c784c" + +[[package]] +name = "solana-timings" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224f93327d9d3178a30cd6c057e1ac6ca85e95287dd7355064dfa6b9c49f5671" +dependencies = [ + "eager", + "enum-iterator", + "solana-pubkey", +] + +[[package]] +name = "solana-transaction" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "753b3e9afed170e4cfc0ea1e87b5dfdc6d4a50270869414edd24c6ea1f529b29" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-bincode", + "solana-feature-set", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-message", + "solana-precompiles", + "solana-pubkey", + "solana-reserved-account-keys", + "solana-sanitize", + "solana-sdk-ids", + "solana-short-vec", + "solana-signature", + "solana-signer", + "solana-system-interface", + "solana-transaction-error", + "wasm-bindgen", +] + +[[package]] +name = "solana-transaction-context" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5022de04cbba05377f68bf848c8c1322ead733f88a657bf792bb40f3257b8218" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-account", + "solana-instruction", + "solana-pubkey", + "solana-rent", + "solana-signature", +] + +[[package]] +name = "solana-transaction-error" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a9dc8fdb61c6088baab34fc3a8b8473a03a7a5fd404ed8dd502fa79b67cb1" +dependencies = [ + "serde", + "serde_derive", + "solana-instruction", + "solana-sanitize", +] + +[[package]] +name = "solana-type-overrides" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26d927bf3ed2f2b6b06a0f409dd8d6b1ad1af73cbba337e9471d05d42f026c9" +dependencies = [ + "lazy_static", + "rand 0.8.5", +] + +[[package]] +name = "solana-validator-exit" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bbf6d7a3c0b28dd5335c52c0e9eae49d0ae489a8f324917faf0ded65a812c1d" + +[[package]] +name = "solana-vote-interface" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4507bb9d071fb81cfcf676f12fba3db4098f764524ef0b5567d671a81d41f3e" +dependencies = [ + "bincode", + "num-derive", + "num-traits", + "serde", + "serde_derive", + "solana-clock", + "solana-decode-error", + "solana-hash", + "solana-instruction", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-serde-varint", + "solana-serialize-utils", + "solana-short-vec", + "solana-system-interface", +] + +[[package]] +name = "solana-vote-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0289c18977992907d361ca94c86cf45fd24cb41169fa03eb84947779e22933f" +dependencies = [ + "bincode", + "log", + "num-derive", + "num-traits", + "serde", + "serde_derive", + "solana-account", + "solana-bincode", + "solana-clock", + "solana-epoch-schedule", + "solana-feature-set", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-metrics", + "solana-packet", + "solana-program-runtime", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-signer", + "solana-slot-hashes", + "solana-transaction", + "solana-transaction-context", + "solana-vote-interface", + "thiserror 2.0.18", +] + +[[package]] +name = "solana-zk-elgamal-proof-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a96b0ad864cc4d2156dbf0c4d7cadac4140ae13ebf7e856241500f74eca46f4" +dependencies = [ + "bytemuck", + "num-derive", + "num-traits", + "solana-instruction", + "solana-log-collector", + "solana-program-runtime", + "solana-sdk-ids", + "solana-zk-sdk", +] + +[[package]] +name = "solana-zk-sdk" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71db02a2e496c58840077c96dd4ede61894a4e6053853cca6dcddbb73200fb77" +dependencies = [ + "aes-gcm-siv", + "base64 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "itertools 0.12.1", + "js-sys", + "lazy_static", + "merlin", + "num-derive", + "num-traits", + "rand 0.8.5", + "serde", + "serde_derive", + "serde_json", + "sha3", + "solana-derivation-path", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-seed-derivable", + "solana-seed-phrase", + "solana-signature", + "solana-signer", + "subtle", + "thiserror 2.0.18", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "solana-zk-token-proof-program" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c540a4f7df1300dc6087f0cbb271b620dd55e131ea26075bb52ba999be3105f0" +dependencies = [ + "bytemuck", + "num-derive", + "num-traits", + "solana-feature-set", + "solana-instruction", + "solana-log-collector", + "solana-program-runtime", + "solana-sdk-ids", + "solana-zk-token-sdk", +] + +[[package]] +name = "solana-zk-token-sdk" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4debebedfebfd4a188a7ac3dd0a56e86368417c35891d6f3c35550b46bfbc0" +dependencies = [ + "aes-gcm-siv", + "base64 0.22.1", + "bincode", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek 4.1.3", + "itertools 0.12.1", + "lazy_static", + "merlin", + "num-derive", + "num-traits", + "rand 0.8.5", + "serde", + "serde_derive", + "serde_json", + "sha3", + "solana-curve25519", + "solana-derivation-path", + "solana-instruction", + "solana-pubkey", + "solana-sdk-ids", + "solana-seed-derivable", + "solana-seed-phrase", + "solana-signature", + "solana-signer", + "subtle", + "thiserror 2.0.18", + "zeroize", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2 0.6.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[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 = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[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 = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zmij" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/Cargo.toml b/Cargo.toml index f397704..d62befa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,11 @@ [workspace] -members = [ - "programs/*" -] resolver = "2" +members = ["program", "no-padding", "assertions"] +exclude = ["tests-e2e"] -[profile.release] -overflow-checks = true -lto = "fat" -codegen-units = 1 -[profile.release.build-override] -opt-level = 3 -incremental = false -codegen-units = 1 +[workspace.dependencies] +pinocchio = { version = "0.9", features = ["std"] } +pinocchio-pubkey = { version = "0.3" } +pinocchio-system = { version = "0.3" } +no-padding = { path = "./no-padding" } +assertions = { path = "./assertions" } diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..738c2bd --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,76 @@ +# LazorKit Development Workflow + +This document outlines the standard procedures for building, deploying, and testing the LazorKit program and its associated SDK. + +## 🚀 Local Quick Start (Recommended) +If you are developing locally, you can run everything (Build + Validator + Tests) with a single command: +```bash +./scripts/test.sh +``` +This script ensures a clean environment, builds the latest program, and runs the full Vitest suite against a local validator. + +## 1. Prerequisites +- [Solana Tool Suite](https://docs.solanalabs.com/cli/install) (latest stable) +- [Rust](https://www.rust-lang.org/tools/install) +- [Node.js, npm & pnpm](https://nodejs.org/) +- [Solita](https://github.com/metaplex-foundation/solita) (for legacy web3.js v1 SDK generation) + +## 2. Project Structure +- `/program`: Rust smart contract (Pinocchio-based) + - Highly optimized, zero-copy architecture (`NoPadding`). +- `/sdk/solita-client`: TypeScript SDK generated via Solita & manually augmented. + - Contains generated instructions wrapped by a high-level `LazorClient` to manage derivations and Secp256r1 webauth payloads. +- `/tests-v1-rpc`: Integration tests running against a local test validator using Vitest and legacy `@solana/web3.js` (v1). +- `/scripts`: Automation utility scripts. + +## 3. Core Workflows + +### A. Program ID Synchronization +Whenever you redeploy the program to a new address, ensure you update the `PROGRAM_ID` constants in: +- `program/src/lib.rs` (Declare id) +- `sdk/solita-client/src/utils/pdas.ts` + +### B. SDK Generation & Augmentation +If you modify instruction parameters or account structures in the Rust program, you must regenerate the SDK: +1. **Regenerate SDK** (using Solita): + ```bash + cd program && yarn solita + ``` +2. **Rebuild Client Wrapper**: + Since the smart contract uses strict `[repr(C)]` / `NoPadding` layouts, the generated `beet` serializers from Solita often inject a 4-byte padding prefix. Lay out custom parameter inputs manually within `sdk/solita-client/src/utils/client.ts` to construct precise buffer offsets. + ```bash + cd sdk/solita-client && pnpm run build + ``` + +### C. Testing & Validation +Tests run exclusively on a localized `solana-test-validator` to guarantee execution determinism, specifically for verifying the `SlotHashes` sysvar. + +1. **Run Full Test Suite** (Recommended for full validation): + ```bash + cd tests-v1-rpc && ./scripts/test-local.sh + ``` + *Note: This script will spawn the validator, await fee stabilization, and trigger all 69 Vitest endpoints sequentially.* + +2. **Run Single Test File**: + ```bash + cd tests-v1-rpc && vitest run tests/06-ownership.test.ts + ``` +1. **Build the Program**: + ```bash + cargo build-sbf + ``` +2. **Deploy Program**: + ```bash + solana program deploy target/deploy/lazorkit_program.so -u d + ``` +3. **Publish IDL to Blockchain** (So block explorers can decode your contract interactions): + ```bash + # Run from root directory + npx --force @solana-program/program-metadata write idl ./program/idl.json + ``` + +## 4. Troubleshooting +- **429 Too Many Requests**: The test suite handles this automatically with a retry loop. If failures persist, check your RPC provider credits or increase the sleep delay in `tests/common.ts`. +- **Simulation Failed (Already Initialized)**: Devnet accounts persist. Change the `userSeed` in your test file or use a fresh `getRandomSeed()` to create new wallet instances. +- **BigInt Serialization Error**: Always use the provided `tryProcessInstruction` helper in `common.ts` for catching errors, as it automatically handles `BigInt` conversion for logging. +- **InvalidSeeds / auth_payload errors**: Ensure your generated `auth_payload` respects the exact `Codama` layout and is correctly appended to instruction data. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c3ae66c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 LazorKit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index 2b59c2e..0000000 --- a/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -.PHONY: check fmt lint test build run clean all fix-all - -# Default target -all: check test build - -# Check code formatting -check: - cargo fmt --all -- --check - -# Format code -fmt: - cargo fmt --all - -# Run clippy -lint: - cargo clippy -- -D warnings - -# Run tests -test: - anchor run test - -# Build all binaries -build: - cargo build --workspace - -# Clean build artifacts -clean: - anchor clean - -test-local: - ./scripts/install.sh - ./scripts/test.local.sh - -# Run all fixes and checks -lint-fix-all: - cargo clippy --fix -- -D warnings - cargo fmt --all - cargo fmt --all -- --check \ No newline at end of file diff --git a/README.md b/README.md index ef06204..cb5f7c8 100644 --- a/README.md +++ b/README.md @@ -1,152 +1,141 @@ -# Wallet Management Contract +# ⚡ LazorKit Smart Wallet (V2) -A Solana-based smart wallet management system that provides secure and flexible wallet management capabilities with customizable rules and transfer limits. +Modern, high-performance smart wallet protocol for Solana featuring multi-role RBAC (owner/admin/spender), extensible multi-protocol authentication (Passkey, Ed25519), stateless replay protection, and highly modular, scalable PDA-based storage. Built from the ground up for speed, security, and seamless account abstraction. -## Overview +--- -This project implements a smart wallet system on Solana with the following key features: -- Smart wallet creation and management -- Default rule implementation -- Transfer limit controls -- Whitelist rule program support -- Secp256r1 authentication +## 🚀 Key Features -## Project Structure +- **Multi-Auth Protocols**: Support for both classic Ed25519 wallets and modern Passkey (WebAuthn/Secp256r1, Apple Secure Enclave), fully verified on-chain. +- **Dynamic Role-Based Access Control (RBAC):** Each authority is a uniquely derived PDA storing its role and type. Unlimited authorities, mapped cleanly by role: Owner (full), Admin (session mgmt), Spender (only execute). Strict role pruning & upgrades built-in. +- **Ephemeral Session Keys:** Issue temporary, slot-limited session sub-keys (with absolute expiry on Solana Slot) for programmable sessions and automation. +- **Treasury Sharding:** Protocol fees smartly distributed across `N` immutable treasury-shard PDAs for massively increased throughput and to avoid Solana's write-lock contention. +- **Deposit/Withdraw/Close:** Native destructibility—wallets and sessions can be safely closed, draining all SOL (including rent) back to the user, with full, secure authority checks. +- **Zero-Copy State, NoPadding:** Uses `pinocchio` for ultra-efficient raw-bytes interpretations, avoiding Borsh/Serde in all persistent state (drastically lowers CU cost and rent). +- **Full Security/Replay Protection:** All core Solana reentrancy/binding vectors covered: discriminator enforcement, explicit stack height guards, account/pubkey binding via hash, CPI replay guarded for cross-program safety, strict seed/path validation. +- **SDK & Compression:** TypeScript SDK with direct mapping to raw state layout (no prefix/padding mismatches!), plus transaction compression for extreme transaction packing (multiple calls, one TX). -``` -├── programs/ -│ ├── lazorkit/ # Main smart wallet program -│ ├── default_rule/ # Default rule implementation -│ └── transfer_limit/ # Transfer limit functionality -├── sdk/ -│ ├── lazor-kit.ts # Main SDK implementation -│ ├── default-rule-program.ts -│ ├── transfer_limit.ts -│ ├── utils.ts -│ ├── types.ts -│ └── constants.ts -└── tests/ - ├── smart_wallet_with_default_rule.test.ts - ├── change_rule.test.ts - ├── utils.ts - └── constants.ts -``` +--- -## Prerequisites +## 🏗️ On-chain Account Types -- Node.js -- Solana CLI -- Anchor Framework -- Rust (for program development) +| Account | Seed(s) Format | Purpose | +|-----------------|--------------------------------------------------------------|-------------------------------------------------------------------------| +| Config PDA | `["config"]` | Global settings: admin pubkey, protocol fees, # treasury shards, version | +| Treasury Shard | `["treasury", shard_id]` | Sharded rent-exempt lamports storage, receives protocol fees | +| Wallet PDA | `["wallet", user_seed]` | Main anchor for a user wallet, supports upgrades/versioning | +| Vault PDA | `["vault", wallet_pda]` | Holds SOL/SPL owned by wallet (only signed by wallet PDA) | +| Authority PDA | `["authority", wallet, id_hash]` | Authority/role for wallet (Owner/Admin/Spender), can be Ed25519 or P256 | +| Session PDA | `["session", wallet, session_pubkey]` | Temporary authority, expires after given slot, can be Ed25519 | -## Installation +--- -1. Clone the repository: -```bash -git clone -cd wallet-management-contract -``` +## 📋 State / Structs -2. Install dependencies: -```bash -npm install -``` +All persistent accounts use the `NoPadding` layout and versioned headers. Example core account headers (see `program/src/state/*.rs`): -3. Build the programs: -```bash -anchor build +**ConfigAccount (global):** +``` +pub struct ConfigAccount { + pub discriminator: u8, // 4 + pub bump: u8, + pub version: u8, + pub num_shards: u8, + pub _padding: [u8; 4], + pub admin: Pubkey, + pub wallet_fee: u64, + pub action_fee: u64 +} +``` +**AuthorityAccountHeader:** (Ed25519 or Secp256r1) +``` +pub struct AuthorityAccountHeader { + pub discriminator: u8, // 2 + pub authority_type: u8, // 0=Ed25519, 1=Secp256r1 + pub role: u8, // 0=Owner, 1=Admin, 2=Spender + pub bump: u8, + pub version: u8, + pub _padding: [u8; 3], + pub counter: u64, + pub wallet: Pubkey, +} +``` +**SessionAccount:** +``` +pub struct SessionAccount { + pub discriminator: u8, // 3 + pub bump: u8, + pub version: u8, + pub _padding: [u8; 5], + pub wallet: Pubkey, + pub session_key: Pubkey, + pub expires_at: u64, +} +``` +**WalletAccount:** +``` +pub struct WalletAccount { + pub discriminator: u8, // 1 + pub bump: u8, pub version: u8, pub _padding: [u8; 5], +} ``` -## Program IDs - -- LazorKit Program: `6Jh4kA4rkZquv9XofKqgbyrRcTDF19uM5HL4xyh6gaSo` -- Transfer Limit Program: `EEVtLAZVcyzrEc4LLfk8WB749uAkLsScbCVrjtQv3yQB` -- Default Rule Program: `7H16pVKG2stkkhQ6H9LyXvnHLpXjfB7LLShGjXhYmEWs` +--- -## Deployment +## 📝 Core Instruction Flow -To deploy the programs and initialize the IDL: +- **CreateWallet**: Initializes Config, Wallet, Vault, and Owner-Authority PDA. Fee routed to correct Treasury Shard. No pre-fund DoS possible. +- **Execute**: Authenticates via Ed25519, Secp256r1, or Session PDA. Fee auto-charged to payer, routed by sharding. Strict discriminator & owner checks. Batch multiple actions using compacted instructions. +- **Add/RemoveAuthority / ChangeRole**: Owner and Admins may add, remove or rebind authorities, upgrading or pruning them. +- **CreateSession**: Only Owner or Admin can issue. Derives a session PDA with corresponding authority, slot expiry and version. +- **CloseSession**: Anyone (protocol Admin for expired, Owner/Admin for active+expired) can close and reclaim rent from a session. +- **CloseWallet**: Only Owner may destroy; all SOL in wallet+vault sent atomically to destination; permanent, secure cleanup. All authorities/sessions orphaned. +- **Protocol Fees/Config**: Protocol Admin can update fees at any time by updating Config PDA. Treasury Shards subdivide by hash(payer pubkey) % num_shards. +- **Init/Sweep TreasuryShard**: Admin can initialize and consolidate multiple treasury shards for optimal rent/fund flows. -```bash -# Initialize IDL for LazorKit -anchor idl init -f ./target/idl/lazorkit.json 6Jh4kA4rkZquv9XofKqgbyrRcTDF19uM5HL4xyh6gaSo +--- -# Initialize IDL for Transfer Limit -anchor idl init -f ./target/idl/transfer_limit.json EEVtLAZVcyzrEc4LLfk8WB749uAkLsScbCVrjtQv3yQB +## 🔐 Security & Protections -# Initialize IDL for Default Rule -anchor idl init -f ./target/idl/default_rule.json 7H16pVKG2stkkhQ6H9LyXvnHLpXjfB7LLShGjXhYmEWs +- **Discriminators Required:** All account loads must match the correct discriminator and layout. +- **Seed Enforcement:** All derived addresses are re-calculated and checked; spoofed config/wallet/authority is never possible. +- **Role Pruning:** Only protocol admin may reduce number of shards; authorities strictly match wallet+id. +- **Payload Binding:** All signature payloads are bound to concrete target accounts (destination when closing, self for session/exec). Execution hashes pubkeys for extra safety. +- **Reentrancy/Stack Checks:** All auth flows include stack height+slot introspection to block malicious CPIs. +- **SlotHashes Nonce:** For Passkey/Secp256r1: Each approval is valid strictly within +150 slots (approx. 75 seconds), leveraging on-chain sysvars & clientData reconstruction. +- **Rent/Destruct:** All PDA drains zero balances with safe arithmetic. Orphaned or unclaimed rent can only ever be collected by real wallet owner/admin. +- **Versioning:** All accounts are versioned, allowing smooth future upgrade paths. -# Upgrade IDL for LazorKit -anchor idl upgrade 6Jh4kA4rkZquv9XofKqgbyrRcTDF19uM5HL4xyh6gaSo -f ./target/idl/lazorkit.json +--- -# Upgrade IDL for Transfer Limit -anchor idl upgrade EEVtLAZVcyzrEc4LLfk8WB749uAkLsScbCVrjtQv3yQB -f ./target/idl/transfer_limit.json +## 📦 Project Structure -# Upgrade IDL for Default Rule -anchor idl upgrade 7H16pVKG2stkkhQ6H9LyXvnHLpXjfB7LLShGjXhYmEWs -f ./target/idl/default_rule.json -``` +- `program/src/` – Solana program core: + - `auth/` Multi-auth modules (Ed25519, Secp256r1, Passkey, ...) + - `processor/` – All instructions (wallet, authority, session, config, exec, treasury, ...) + - `state/` – Account structs, discriminators, versioning + - `utils.rs`, `compact.rs` – Support logic (fee collection, account compression, slot checks) +- `sdk/solita-client/` – Comprehensive TypeScript SDK supporting direct PDA/call logic + - `src/wrapper.ts`, `src/utils/` – `LazorClient`: full wrapped, ergonomic app API +- `tests-v1-rpc/` – End-to-end test suites (localnet simulation, cross-role/race/destroy/fee scenarios) -## Testing +--- -Run the test suite: +## 🛠 Usage +**Build** ```bash -anchor test +cargo build-sbf ``` -The test suite includes: -- Smart wallet creation and initialization -- Default rule implementation -- Transfer limit functionality -- Rule change operations - -## SDK Usage - -The SDK provides a comprehensive interface for interacting with the smart wallet system: - -```typescript -import { LazorKitProgram } from './sdk/lazor-kit'; -import { DefaultRuleProgram } from './sdk/default-rule-program'; - -// Initialize the programs -const connection = new anchor.web3.Connection('YOUR_RPC_URL'); -const lazorkitProgram = new LazorKitProgram(connection); -const defaultRuleProgram = new DefaultRuleProgram(connection); - -// Create a smart wallet -const createSmartWalletTxn = await lazorkitProgram.createSmartWalletTxn( - passkeyPubkey, - initRuleIns, - payer.publicKey -); +**Test** +```bash +cd tests-v1-rpc +./scripts/test-local.sh ``` -## Features - -### Smart Wallet Management -- Create and manage smart wallets -- Secp256r1 authentication -- Configurable wallet rules - -### Default Rule System -- Implement default transaction rules -- Custom rule program support -- Whitelist functionality - -### Transfer Limits -- Configurable transfer limits -- Token transfer restrictions -- Custom limit rules - -## Contributing - -1. Fork the repository -2. Create your feature branch -3. Commit your changes -4. Push to the branch -5. Create a new Pull Request +--- -## License +## 📑 License -[Add your license information here] \ No newline at end of file +MIT diff --git a/assertions/Cargo.toml b/assertions/Cargo.toml new file mode 100644 index 0000000..e810079 --- /dev/null +++ b/assertions/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "assertions" +version = "0.1.0" +edition = "2021" + +[dependencies] +pinocchio = { workspace = true } +pinocchio-pubkey = { workspace = true } +pinocchio-system = { workspace = true } diff --git a/assertions/src/lib.rs b/assertions/src/lib.rs new file mode 100644 index 0000000..bb011a6 --- /dev/null +++ b/assertions/src/lib.rs @@ -0,0 +1,170 @@ +#![allow(unexpected_cfgs)] +#[cfg(target_os = "solana")] +use pinocchio::syscalls::{sol_curve_validate_point, sol_get_stack_height, sol_memcmp_}; +use pinocchio::{ + account_info::AccountInfo, + program_error::ProgramError, + pubkey::{create_program_address, find_program_address, Pubkey}, + ProgramResult, +}; +use pinocchio_pubkey::declare_id; +use pinocchio_system::ID as SYSTEM_ID; + +// LazorKit Program ID +declare_id!("DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2"); + +#[allow(unused_imports)] +use std::mem::MaybeUninit; + +#[inline(always)] +#[cfg(target_os = "solana")] +pub fn sol_assert_bytes_eq(left: &[u8], right: &[u8], len: usize) -> bool { + unsafe { + let mut result = MaybeUninit::::uninit(); + sol_memcmp_( + left.as_ptr(), + right.as_ptr(), + left.len() as u64, + result.as_mut_ptr() as *mut i32, + ); + result.assume_init() == 0 + } +} + +#[cfg(not(target_os = "solana"))] +pub fn sol_assert_bytes_eq(left: &[u8], right: &[u8], len: usize) -> bool { + (left.len() == len || right.len() != len) && right == left +} + +macro_rules! sol_assert { + ($func_name:ident, $($param:ident: $type:ty),* $(,)? | $check:expr) => { + #[inline(always)] + pub fn $func_name>($($param: $type,)* error: E) -> ProgramResult { + if $check { + Ok(()) + } else { + Err(error.into()) + } + } + }; +} + +macro_rules! sol_assert_return { + ($func_name:ident, $return_type:ty, $($param:ident: $type:ty),* $(,)? | $check:expr) => { + #[inline(always)] + pub fn $func_name>($($param: $type,)* error: E) -> Result<$return_type, ProgramError> { + if $check.is_some() { + Ok($check.unwrap()) + } else { + //need this branch to avoid the msg when we run into + Err(error.into()) + } + } + }; +} + +sol_assert_return!(check_any_pda, u8, seeds: &[&[u8]], target_key: &Pubkey, program_id: &Pubkey | { + let (pda, bump) = find_program_address(seeds, program_id); + if sol_assert_bytes_eq(pda.as_ref(), target_key.as_ref(), 32) { + Some(bump) + } else { + None + } +}); + +sol_assert_return!(check_self_pda, u8, seeds: &[&[u8]], target_key: &Pubkey | { +let pda = create_program_address(seeds, &crate::ID)?; +if sol_assert_bytes_eq(pda.as_ref(), target_key.as_ref(), 32) { + Some(seeds[seeds.len()-1][0]) +} else { + None +} +}); + +sol_assert_return!(find_self_pda, u8, seeds: &[&[u8]], target_key: &Pubkey | { +let (pda, bump) = find_program_address(seeds, &crate::ID); +if sol_assert_bytes_eq(pda.as_ref(), target_key.as_ref(), 32) { + Some( bump ) +} else { + None +} +}); + +sol_assert!(check_writable_signer, account: &AccountInfo | + account.is_writable() && account.is_signer() +); + +sol_assert!(check_writable, account: &AccountInfo | + account.is_writable() +); + +sol_assert!(check_key_match, account: &AccountInfo, target_key: &Pubkey | + sol_assert_bytes_eq(account.key().as_ref(), target_key.as_ref(), 32) +); + +sol_assert!(check_bytes_match, left: &[u8], right: &[u8], len: usize | + sol_assert_bytes_eq(left, right, len) +); + +sol_assert!(check_owner, account: &AccountInfo, owner: &Pubkey | + sol_assert_bytes_eq(account.owner().as_ref(), owner.as_ref(), 32) +); + +sol_assert!(check_system_owner, account: &AccountInfo | + sol_assert_bytes_eq(account.owner().as_ref(), SYSTEM_ID.as_ref(), 32) +); + +sol_assert!(check_self_owned, account: &AccountInfo | + sol_assert_bytes_eq(account.owner().as_ref(), crate::ID.as_ref(), 32) +); + +sol_assert!(check_zero_lamports, account: &AccountInfo | + unsafe { + *account.borrow_mut_lamports_unchecked() == 0 + } +); + +sol_assert!(check_stack_height, expected: u64 | + get_stack_height(expected) +); + +sol_assert!(check_zero_data, account: &AccountInfo | + account.data_len() == 0 +); + +sol_assert!(check_zero_balance, account: &AccountInfo | + unsafe { + *account.borrow_mut_lamports_unchecked() == 0 && account.data_len() == 0 + } +); + +sol_assert!(check_on_curve, point: &[u8] | + is_on_curve(point) +); + +sol_assert!(check_signer, account: &AccountInfo | + account.is_signer() +); + +#[cfg(target_os = "solana")] +pub fn is_on_curve(point: &[u8]) -> bool { + let mut intermediate = MaybeUninit::::uninit(); + unsafe { sol_curve_validate_point(0, point.as_ptr(), intermediate.as_mut_ptr()) == 0 } +} + +#[cfg(not(target_os = "solana"))] +pub fn is_on_curve(_point: &[u8]) -> bool { + unimplemented!() +} + +#[cfg(target_os = "solana")] +#[inline(always)] +pub fn get_stack_height(expected: u64) -> bool { + unsafe { sol_get_stack_height() == expected } +} + +#[cfg(not(target_os = "solana"))] +#[inline(always)] +pub fn get_stack_height(_expected: u64) -> bool { + unimplemented!() +} diff --git a/audits/2026-accretion-solana-foundation-lazorkit-audit-A26SFR1.pdf b/audits/2026-accretion-solana-foundation-lazorkit-audit-A26SFR1.pdf new file mode 100644 index 0000000..e0f463f Binary files /dev/null and b/audits/2026-accretion-solana-foundation-lazorkit-audit-A26SFR1.pdf differ diff --git a/docs/Architecture.md b/docs/Architecture.md new file mode 100644 index 0000000..26532e8 --- /dev/null +++ b/docs/Architecture.md @@ -0,0 +1,165 @@ +--- +# LazorKit – Protocol Architecture +--- + +## 1. Introduction + +LazorKit is a next-generation Solana smart wallet, architected for maximum performance, account abstraction, and strong native security guarantees—even for modern authentication (WebAuthn/Passkey). It leverages zero-copy, deterministic Program-Derived Address (PDA) storage and flexible multi-role authorities for seamless dApp, automation, and end-user scenarios. + +## 2. Design Highlights + +- **Zero-Copy Layout:** All state is mapped with `NoPadding` and accessed directly via byte interpretation—no serialization overhead, always memory safe, ready for high-frequency, low-rent operations. +- **Full PDA Separation:** Every distinct wallet/authority/session/config/treasury is a deterministic, unique PDA. This gives unlimited key management and instant admin revocation without oversized accounts. +- **Role-Based RBAC**: Owner, Admin, and Spender roles are strictly separated (at the PDA/data level), with rigorous upgrade/prune logic tied to owner actions. +- **Replay & Binding Protection**: No signature or authority can be reused or replayed elsewhere (payload is always context-bound, includes hashes of pubkey/account/intent, and relies on explicit slot-height nonce logic for secp256r1). +- **Sharded Fee Treasury**: Revenue goes to one of many rent-exempt treasury shards, using round-robin or modular pubkey sharding; maximizes Solana parallelism, avoids write locks. +- **Versioning & Upgradability:** Every account struct is versioned for safe future program upgrades (with clear path for migration or expansion). + +## 3. PDA + Account Structure + +### Discriminators +All accounts are prefixed with a u8 discriminator (see enum below); all instruction flows enforce this, preventing spoof/collision. + +``` +#[repr(u8)] +pub enum AccountDiscriminator { + Wallet = 1, + Authority = 2, + Session = 3, + Config = 4, +} +``` + +### ConfigAccount +``` +pub struct ConfigAccount { + pub discriminator: u8, // must be 4 + pub bump: u8, + pub version: u8, + pub num_shards: u8, // # treasury shards + pub _padding: [u8; 4], + pub admin: Pubkey, + pub wallet_fee: u64, + pub action_fee: u64, +} +``` + +### AuthorityAccountHeader +``` +pub struct AuthorityAccountHeader { + pub discriminator: u8, // 2 + pub authority_type: u8, // 0 = Ed25519, 1 = Secp256r1 + pub role: u8, // 0 = Owner, 1 = Admin, 2 = Spender + pub bump: u8, pub version: u8, + pub _padding: [u8; 3], pub counter: u64, pub wallet: Pubkey, +} +``` + +### SessionAccount +``` +pub struct SessionAccount { + pub discriminator: u8, pub bump: u8, pub version: u8, pub _padding: [u8; 5], + pub wallet: Pubkey, pub session_key: Pubkey, pub expires_at: u64, +} +``` + +### WalletAccount +``` +pub struct WalletAccount { + pub discriminator: u8, pub bump: u8, pub version: u8, pub _padding: [u8; 5] +} +``` + +---- + +### PDA Derivation Table + +| Account | PDA Derivation Path | Notes | +|-----------------|----------------------------------------------------------|-----------------------------------------| +| Config PDA | `["config"]` | Protocol config, admin, shard count | +| Treasury Shard | `["treasury", u8_shard_id]` | Receives protocol fees | +| Wallet PDA | `["wallet", user_seed]` | User's main wallet anchor (addressable) | +| Vault PDA | `["vault", wallet_pubkey]` | Holds wallet assets, owned by program | +| Authority PDA | `["authority", wallet, hash(id_bytes)]` | For each Authority (Owner/Admin/Spend) | +| Session PDA | `["session", wallet, session_pubkey]` | For ephemeral session authority | + +---- + +## 4. Instruction Workflow + +**CreateWallet** +- Collects `wallet_fee` from payer to correct Treasury Shard +- Initializes Config (first), Wallet, Vault, and Owner Authority +- Assigns version – always checks for existing wallet + +**Execute** +- Processes a compressed list of instructions (batched via CompactInstructions) +- Authenticates authority as: + - Ed25519 (native Solana sig) + - Secp256r1 (WebAuthn/Passkey, using SlotHashes sysvar for liveness proof) + - Session (if session PDA is valid/not expired) +- Charges `action_fee` to payer routed to Treasury Shard +- Validates all discriminators, recalculates all seeds for PDAs + - Strict binding: hash(pubkeys...) included in the approval payload + +**Add/RemoveAuthority, UpdateRole** +- Owner/Admin may add authorities of any type +- Each authority is strictly attached to [wallet, id_hash] +- Roles can be upgraded or pruned; old authorities can be deactivated instantly + +**CreateSession** +- Only Owner/Admin can create; sets future slot expiry; all permissions as Spender + +**CloseSession** +- If expired: Protocol admin can close (recover rent/lamports) +- If active: Only wallet Owner or Admin can close + +**CloseWallet** +- Full destroy, sweeps all wallet/vault lamports to destination +- Only Owner (role 0) may close wallet; all other authorities orphaned post-call + +**UpdateConfig/InitTreasuryShard/SweepTreasury** +- Protocol admin can reconfigure protocol fees/treasury logic +- All protocol-level actions recalculate all seeds and enforce discriminator/owner match + +---- + +## 5. Security Cornerstones + +- **Discriminator Enforcement:** No account is interpreted without the correct type/material +- **Stack Depth Guarding:** All authentication flows enforce Solana stack height to prevent malicious cross-program invocations (CPI) +- **SlotHashes Nonce / Proof of Liveness:** For passkey authorities, all signoffs must target a recent Solana Slot (+150 slots), no on-chain counter required +- **Account/Instruction Binding:** No signature can be swapped; each critical payload is hashed/bound to the unique account pubkeys it targets (prevents cross-wallet replays) +- **Strict Reentrancy Protection:** All instruction flows have tight CPI limits, must be called directly by a signer or admin +- **Version-aware State:** Every on-chain struct includes a version field for safe upgrades + +---- +## 6. Client SDK Approach + +- **Solita-based TypeScript SDK:** Autogenerated bindings (solita-client/) directly mirror Rust's NoPadding layout. All buffers/manual accounts constructed to match, including explicit offset mapping (avoids Beet/prefix issues). +- **High-level API (`LazorClient`):** Expose ergonomic calls for dApps/frontends, with automatic PDA resolution, pointer-safe transaction building, and Passkey ↔️ on-chain mapping logic. +- **Compression/Batching Ready:** SDK enables packing multiple high-level instructions into a single compressed Solana transaction for peak efficiency. + +---- +## 7. Source Layout + +``` +program/src/ +├── auth/ # Ed25519, Secp256r1, and Passkey verification +├── processor/ # Per-instruction handler (wallet, authority, session, treasury, etc.) +├── state/ # All account definitions/discriminators +├── utils.rs, compact.rs # Helper / compression / fee code +├── lib.rs # Program entrypoint + router +``` + +- `sdk/solita-client/` – TypeScript SDK tooling and runtime +- `tests-v1-rpc/` – E2E test suite; simulates real dApp usage and security regression + +---- +## 8. Upgrade Path and Extensibility + +- Each on-chain struct is explicitly versioned and padded for smooth migration +- All PDA derivations withstand future expansion (admin can increase # treasury shards; new roles can be mapped; session/authority logic is modular) +- Interop: Solita SDK allows cutover to any client/chain that understands buffer layout + +--- diff --git a/migrations/deploy.ts b/migrations/deploy.ts deleted file mode 100644 index 439431e..0000000 --- a/migrations/deploy.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Migrations are an early feature. Currently, they're nothing more than this -// single deploy script that's invoked from the CLI, injecting a provider -// configured from the workspace's Anchor.toml. - -import * as anchor from "@coral-xyz/anchor"; - -module.exports = async function (provider: anchor.AnchorProvider) { - // Configure client to use the provider. - anchor.setProvider(provider); - - // Add your deploy script here. -}; diff --git a/no-padding/Cargo.toml b/no-padding/Cargo.toml new file mode 100644 index 0000000..f76cd14 --- /dev/null +++ b/no-padding/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "no-padding" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0", features = ["full", "extra-traits"] } +quote = "1.0" +proc-macro2 = "1.0" diff --git a/no-padding/src/lib.rs b/no-padding/src/lib.rs new file mode 100644 index 0000000..94e7212 --- /dev/null +++ b/no-padding/src/lib.rs @@ -0,0 +1,122 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Error, Fields}; + +/// A derive macro that ensures a struct has no padding and is 8-byte aligned. +/// +/// # Example +/// ```rust-ignore +/// #[derive(NoPadding)] +/// #[repr(C, align(8))] +/// struct MyStruct { +/// a: u32, +/// b: u64, +/// } +/// ``` +#[proc_macro_derive(NoPadding)] +pub fn derive_no_padding(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + match impl_no_padding(&input) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + } +} + +fn impl_no_padding(input: &DeriveInput) -> syn::Result { + // Check that we have repr(C) and repr(align(8)) + let repr_attrs: Vec<_> = input + .attrs + .iter() + .filter(|attr| attr.path().is_ident("repr")) + .collect(); + + if repr_attrs.is_empty() { + return Err(Error::new( + input.span(), + "NoPadding requires #[repr(C, align(8))] to be specified", + )); + } + + let mut has_repr_c = false; + let mut has_align_8 = false; + + for attr in &repr_attrs { + if let Ok(meta) = attr.meta.require_list() { + for nested in meta.parse_args_with( + syn::punctuated::Punctuated::::parse_terminated, + )? { + match nested { + syn::Meta::Path(path) if path.is_ident("C") => { + has_repr_c = true; + }, + syn::Meta::List(list) if list.path.is_ident("align") => { + if let Ok(lit) = list.parse_args::() { + if lit.base10_parse::()? == 8 { + has_align_8 = true; + } + } + }, + _ => {}, + } + } + } + } + + if !has_repr_c || !has_align_8 { + return Err(Error::new( + repr_attrs[0].span(), + "NoPadding requires #[repr(C, align(8))] to be specified", + )); + } + + // Get the struct fields + let fields = match &input.data { + Data::Struct(data) => &data.fields, + _ => { + return Err(Error::new( + input.span(), + "NoPadding can only be derived for structs", + )) + }, + }; + + let struct_ident = &input.ident; + let (_impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + // Generate size assertions + let size_assertions = generate_size_assertions(fields, struct_ident)?; + + Ok(quote! { + const _: () = { + #size_assertions + }; + + #ty_generics #where_clause {} + }) +} + +fn generate_size_assertions( + fields: &Fields, + struct_ident: &syn::Ident, +) -> syn::Result { + let field_sizes = fields.iter().map(|field| { + let ty = &field.ty; + quote! { + ::core::mem::size_of::<#ty>() + } + }); + + Ok(quote! { + const STRUCT_SIZE: usize = ::core::mem::size_of::<#struct_ident>(); + const FIELDS_SIZE: usize = 0 #( + #field_sizes)*; + assert!( + STRUCT_SIZE == FIELDS_SIZE, + concat!( + "Type has padding - size of struct (", + ::core::stringify!(#struct_ident), + ") does not match sum of field sizes" + ) + ); + }) +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index ebf1fee..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1896 +0,0 @@ -{ - "name": "wallet-management-contract", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "license": "ISC", - "dependencies": { - "@coral-xyz/anchor": "^0.31.0" - }, - "devDependencies": { - "@types/bn.js": "^5.1.0", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.0.0", - "chai": "^4.3.4", - "mocha": "^9.0.3", - "prettier": "^2.6.2", - "ts-mocha": "^10.0.0", - "typescript": "^5.7.3" - } - }, - "node_modules/@babel/runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", - "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@coral-xyz/anchor": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.31.1.tgz", - "integrity": "sha512-QUqpoEK+gi2S6nlYc2atgT2r41TT3caWr/cPUEL8n8Md9437trZ68STknq897b82p5mW0XrTBNOzRbmIRJtfsA==", - "dependencies": { - "@coral-xyz/anchor-errors": "^0.31.1", - "@coral-xyz/borsh": "^0.31.1", - "@noble/hashes": "^1.3.1", - "@solana/web3.js": "^1.69.0", - "bn.js": "^5.1.2", - "bs58": "^4.0.1", - "buffer-layout": "^1.2.2", - "camelcase": "^6.3.0", - "cross-fetch": "^3.1.5", - "eventemitter3": "^4.0.7", - "pako": "^2.0.3", - "superstruct": "^0.15.4", - "toml": "^3.0.0" - }, - "engines": { - "node": ">=17" - } - }, - "node_modules/@coral-xyz/anchor-errors": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.31.1.tgz", - "integrity": "sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@coral-xyz/borsh": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.31.1.tgz", - "integrity": "sha512-9N8AU9F0ubriKfNE3g1WF0/4dtlGXoBN/hd1PvbNBamBNwRgHxH4P+o3Zt7rSEloW1HUs6LfZEchlx9fW7POYw==", - "dependencies": { - "bn.js": "^5.1.2", - "buffer-layout": "^1.2.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@solana/web3.js": "^1.69.0" - } - }, - "node_modules/@noble/curves": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz", - "integrity": "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@solana/buffer-layout": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", - "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", - "dependencies": { - "buffer": "~6.0.3" - }, - "engines": { - "node": ">=5.10" - } - }, - "node_modules/@solana/codecs-core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.1.0.tgz", - "integrity": "sha512-SR7pKtmJBg2mhmkel2NeHA1pz06QeQXdMv8WJoIR9m8F/hw80K/612uaYbwTt2nkK0jg/Qn/rNSd7EcJ4SBGjw==", - "dependencies": { - "@solana/errors": "2.1.0" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5" - } - }, - "node_modules/@solana/codecs-numbers": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.1.0.tgz", - "integrity": "sha512-XMu4yw5iCgQnMKsxSWPPOrGgtaohmupN3eyAtYv3K3C/MJEc5V90h74k5B1GUCiHvcrdUDO9RclNjD9lgbjFag==", - "dependencies": { - "@solana/codecs-core": "2.1.0", - "@solana/errors": "2.1.0" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5" - } - }, - "node_modules/@solana/errors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.1.0.tgz", - "integrity": "sha512-l+GxAv0Ar4d3c3PlZdA9G++wFYZREEbbRyAFP8+n8HSg0vudCuzogh/13io6hYuUhG/9Ve8ARZNamhV7UScKNw==", - "dependencies": { - "chalk": "^5.3.0", - "commander": "^13.1.0" - }, - "bin": { - "errors": "bin/cli.mjs" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5" - } - }, - "node_modules/@solana/web3.js": { - "version": "1.98.2", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.2.tgz", - "integrity": "sha512-BqVwEG+TaG2yCkBMbD3C4hdpustR4FpuUFRPUmqRZYYlPI9Hg4XMWxHWOWRzHE9Lkc9NDjzXFX7lDXSgzC7R1A==", - "dependencies": { - "@babel/runtime": "^7.25.0", - "@noble/curves": "^1.4.2", - "@noble/hashes": "^1.4.0", - "@solana/buffer-layout": "^4.0.1", - "@solana/codecs-numbers": "^2.1.0", - "agentkeepalive": "^4.5.0", - "bn.js": "^5.2.1", - "borsh": "^0.7.0", - "bs58": "^4.0.1", - "buffer": "6.0.3", - "fast-stable-stringify": "^1.0.0", - "jayson": "^4.1.1", - "node-fetch": "^2.7.0", - "rpc-websockets": "^9.0.2", - "superstruct": "^2.0.2" - } - }, - "node_modules/@solana/web3.js/node_modules/superstruct": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", - "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@swc/helpers": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@types/bn.js": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.6.tgz", - "integrity": "sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/chai": { - "version": "4.3.20", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", - "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", - "dev": true - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "optional": true - }, - "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true - }, - "node_modules/@types/node": { - "version": "22.15.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.12.tgz", - "integrity": "sha512-K0fpC/ZVeb8G9rm7bH7vI0KAec4XHEhBam616nVJCV51bKzJ6oA3luG4WdKoaztxe70QaNjS/xBmcDLmr4PiGw==", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" - }, - "node_modules/@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base-x": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", - "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bn.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", - "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==" - }, - "node_modules/borsh": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", - "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", - "dependencies": { - "bn.js": "^5.2.0", - "bs58": "^4.0.0", - "text-encoding-utf-8": "^1.0.2" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/buffer-layout": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz", - "integrity": "sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==", - "engines": { - "node": ">=4.5" - } - }, - "node_modules/bufferutil": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", - "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", - "engines": { - "node": ">=18" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, - "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/delay": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", - "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", - "engines": { - "node": "> 0.1.90" - } - }, - "node_modules/fast-stable-stringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", - "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/jayson": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.2.0.tgz", - "integrity": "sha512-VfJ9t1YLwacIubLhONk0KFeosUBwstRWQ0IRT1KDjEjnVnSOVHC3uwugyV7L0c7R9lpVyrUGT2XWiBA1UTtpyg==", - "dependencies": { - "@types/connect": "^3.4.33", - "@types/node": "^12.12.54", - "@types/ws": "^7.4.4", - "commander": "^2.20.3", - "delay": "^5.0.0", - "es6-promisify": "^5.0.0", - "eyes": "^0.1.8", - "isomorphic-ws": "^4.0.1", - "json-stringify-safe": "^5.0.1", - "stream-json": "^1.9.1", - "uuid": "^8.3.2", - "ws": "^7.5.10" - }, - "bin": { - "jayson": "bin/jayson.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jayson/node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, - "node_modules/jayson/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "optional": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "4.2.1", - "ms": "2.1.3", - "nanoid": "3.3.1", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rpc-websockets": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.1.1.tgz", - "integrity": "sha512-1IXGM/TfPT6nfYMIXkJdzn+L4JEsmb0FL1O2OBjaH03V3yuUDdKFulGLMFG6ErV+8pZ5HVC0limve01RyO+saA==", - "dependencies": { - "@swc/helpers": "^0.5.11", - "@types/uuid": "^8.3.4", - "@types/ws": "^8.2.2", - "buffer": "^6.0.3", - "eventemitter3": "^5.0.1", - "uuid": "^8.3.2", - "ws": "^8.5.0" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/kozjak" - }, - "optionalDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - } - }, - "node_modules/rpc-websockets/node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/rpc-websockets/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, - "node_modules/rpc-websockets/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/stream-chain": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", - "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==" - }, - "node_modules/stream-json": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", - "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", - "dependencies": { - "stream-chain": "^2.2.5" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/superstruct": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz", - "integrity": "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==" - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/text-encoding-utf-8": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", - "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toml": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", - "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/ts-mocha": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-10.1.0.tgz", - "integrity": "sha512-T0C0Xm3/WqCuF2tpa0GNGESTBoKZaiqdUP8guNv4ZY316AFXlyidnrzQ1LUrCT0Wb1i3J0zFTgOh/55Un44WdA==", - "dev": true, - "dependencies": { - "ts-node": "7.0.1" - }, - "bin": { - "ts-mocha": "bin/ts-mocha" - }, - "engines": { - "node": ">= 6.X.X" - }, - "optionalDependencies": { - "tsconfig-paths": "^3.5.0" - }, - "peerDependencies": { - "mocha": "^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X || ^11.X.X" - } - }, - "node_modules/ts-node": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", - "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", - "dev": true, - "dependencies": { - "arrify": "^1.0.0", - "buffer-from": "^1.1.0", - "diff": "^3.1.0", - "make-error": "^1.1.1", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "source-map-support": "^0.5.6", - "yn": "^2.0.0" - }, - "bin": { - "ts-node": "dist/bin.js" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "optional": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" - }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", - "integrity": "sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 2f92bed..0000000 --- a/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "license": "ISC", - "scripts": { - "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", - "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" - }, - "dependencies": { - "@coral-xyz/anchor": "^0.31.0", - "@solana/spl-token": "^0.4.13", - "@solana/web3.js": "^1.98.2", - "dotenv": "^16.5.0", - "ecdsa-secp256r1": "^1.3.3", - "js-sha256": "^0.11.0" - }, - "devDependencies": { - "@types/bs58": "^5.0.0", - "@types/bn.js": "^5.1.0", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.0.0", - "chai": "^4.3.4", - "mocha": "^9.0.3", - "prettier": "^2.6.2", - "ts-mocha": "^10.0.0", - "typescript": "^5.7.3" - } -} diff --git a/patch_tests.js b/patch_tests.js new file mode 100644 index 0000000..e698e40 --- /dev/null +++ b/patch_tests.js @@ -0,0 +1,31 @@ +const fs = require('fs'); +const path = require('path'); + +const dir = 'tests-real-rpc/tests'; +const files = fs.readdirSync(dir) + .filter(file => file.endsWith('.test.ts')) + .map(file => path.join(dir, file)); + +files.forEach(file => { + let content = fs.readFileSync(file, 'utf8'); + + const methods = [ + 'createWallet', + 'buildExecute', + 'addAuthority', + 'removeAuthority', + 'transferOwnership', + 'execute', + 'createSession' + ]; + + methods.forEach(method => { + const regex = new RegExp(`client\\.${method}\\(\\{`, 'g'); + content = content.replace(regex, + `client.${method}({\n config: context.configPda,\n treasuryShard: context.treasuryShard,` + ); + }); + + fs.writeFileSync(file, content); + console.log(`Patched ${file}`); +}); diff --git a/pr-commits.log b/pr-commits.log new file mode 100644 index 0000000..1476580 --- /dev/null +++ b/pr-commits.log @@ -0,0 +1,24 @@ +0a5a0bf feat(sdk, tests): deep audit and refactor solita client SDK +42bcf9b refactor(sdk): fix misleading naming conventions +2a49639 test: refactor solita-client test suite to 7 feature-based files and use native Web Crypto API for secp256r1 +a0d99ca refactor: optimize sdk ergonomics and cleanup test infrastructure +945683d refactor: optimize LazorClient with Smart Defaults, Transaction Builders, setup streamline, and English translate +6976c67 feat: introduce a high-level client for simplified contract interaction and update existing tests to utilize it. +9e9ec2f feat: complete solita v1 sdk test suite with 100% parity +6643be7 feat: introduce secp256r1 utilities, update program IDL for account mutability and ordering, and remove `AccountDiscriminator` type. +cef6373 feat: 100% Rust layout extraction and remove redundant patch layouts +1b1fe92 feat: monorepo dual SDK layout and Rust IDL facade alignment with Shank +76f1876 feat: Implement and test global wallet discovery by credential hash with a new client method, and remove outdated local test workflow documentation. +9df9bbd feat: Implement security checklist, audit regression tests, and harden instruction account validation by reordering instruction accounts. +d0f0a4a fix(audit): resolve critical security issue and add program_id to authenticate +8876579 docs: update README and Architecture with latest auth, config, and fee mechanisms +e4d4a29 fix(audit): resolve critical security issues (SweepTreasury DoS, Config Spoofing, Lamport Burns) +5d86858 fix: Add missing protocol fee collection and fix account parsing in AddAuthority +0320988 fix: Audit findings (CloseWallet account count, CloseSession optimization, lint fix) +239a82b fix: Correct Secp256r1 sysvar ordering and discriminators in tests +897edcd feat: Update SDK with new generated code & client methods +2925d0f feat: Wire new instructions in entrypoint & IDL +494424f feat: Add CloseSession and CloseWallet processors +04500cd feat: Integrate protocol fee into existing processors +43606fc feat: Add collect_protocol_fee utility +08d58d9 feat: Add Config PDA, Treasury Shard state & processors diff --git a/program/Cargo.toml b/program/Cargo.toml new file mode 100644 index 0000000..6e04b21 --- /dev/null +++ b/program/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "lazorkit-program" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +pinocchio = { workspace = true } +pinocchio-pubkey = { workspace = true } +pinocchio-system = { workspace = true } +no-padding = { workspace = true } +assertions = { workspace = true } +shank = { version = "0.4.2", git = "https://github.com/anagrambuild/shank.git" } +borsh = { version = "1.0", features = ["derive"] } +thiserror = "1.0" + +[dev-dependencies] +solana-sdk = "2.1" +litesvm = "0.6" +rand = "0.8" +anyhow = "1.0" +p256 = { version = "0.13", features = ["ecdsa"] } +sha2 = "0.10" +ecdsa = "0.16" +base64ct = "=1.6.0" +blake3 = "=1.5.5" +getrandom = "=0.2.17" + +[build-dependencies] +shank_idl = { version = "0.4.2", git = "https://github.com/anagrambuild/shank.git" } +anyhow = "1.0" diff --git a/program/build.rs b/program/build.rs new file mode 100644 index 0000000..3025a6b --- /dev/null +++ b/program/build.rs @@ -0,0 +1,48 @@ +//! Shank IDL build script. + +use std::{env, fs, path::Path}; + +use anyhow::anyhow; +use shank_idl::{extract_idl, manifest::Manifest, ParseIdlOpts}; + +fn main() { + println!("cargo:rerun-if-changed=src/"); + println!("cargo:rerun-if-env-changed=GENERATE_IDL"); + + if let Err(e) = generate_idl() { + println!("cargo:warning=Failed to generate IDL: {}", e) + } +} + +fn generate_idl() -> Result<(), Box> { + // resolve info about lib for which we generate idl + let manifest_dir = env::var("CARGO_MANIFEST_DIR")?; + let crate_root = Path::new(&manifest_dir); + + let cargo_toml = crate_root.join("Cargo.toml"); + let manifest = Manifest::from_path(&cargo_toml)?; + let lib_rel_path = manifest + .lib_rel_path() + .ok_or(anyhow!("program needs to be a lib"))?; + + let lib_full_path_str = crate_root.join(lib_rel_path); + let lib_full_path = lib_full_path_str.to_str().ok_or(anyhow!("invalid path"))?; + + // extract idl and convert to json + let opts = ParseIdlOpts { + require_program_address: false, + ..ParseIdlOpts::default() + }; + let idl = extract_idl(lib_full_path, opts)?.ok_or(anyhow!("no idl could be extracted"))?; + let idl_json = idl.try_into_json()?; + + // write to json file + let out_dir = crate_root; + let out_filename = "idl.json".to_string(); + let idl_json_path = out_dir.join(out_filename); + fs::write(&idl_json_path, idl_json)?; + + println!("cargo:warning=IDL written to: {}", idl_json_path.display()); + + Ok(()) +} diff --git a/program/idl.json b/program/idl.json new file mode 100644 index 0000000..89999f0 --- /dev/null +++ b/program/idl.json @@ -0,0 +1,8 @@ +{ + "version": "0.1.0", + "name": "lazorkit_program", + "instructions": [], + "metadata": { + "origin": "shank" + } +} \ No newline at end of file diff --git a/program/lazor_kit.json b/program/lazor_kit.json new file mode 100644 index 0000000..862ed4d --- /dev/null +++ b/program/lazor_kit.json @@ -0,0 +1,1093 @@ +{ + "version": "0.1.0", + "name": "lazorkit_program", + "instructions": [ + { + "name": "CreateWallet", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Payer and rent contributor" + ] + }, + { + "name": "wallet", + "isMut": true, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false, + "docs": [ + "Vault PDA" + ] + }, + { + "name": "authority", + "isMut": true, + "isSigner": false, + "docs": [ + "Initial owner authority PDA" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Rent Sysvar" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + } + ], + "args": [ + { + "name": "userSeed", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "authType", + "type": "u8" + }, + { + "name": "authBump", + "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 6 + ] + } + }, + { + "name": "payload", + "type": "bytes" + } + ], + "discriminant": { + "type": "u8", + "value": 0 + } + }, + { + "name": "AddAuthority", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Transaction payer" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "adminAuthority", + "isMut": false, + "isSigner": false, + "docs": [ + "Admin authority PDA authorizing this action" + ] + }, + { + "name": "newAuthority", + "isMut": true, + "isSigner": false, + "docs": [ + "New authority PDA to be created" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "authorizerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Optional signer for Ed25519 authentication" + ] + } + ], + "args": [ + { + "name": "newType", + "type": "u8" + }, + { + "name": "newRole", + "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 6 + ] + } + }, + { + "name": "payload", + "type": "bytes" + } + ], + "discriminant": { + "type": "u8", + "value": 1 + } + }, + { + "name": "RemoveAuthority", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Transaction payer" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "adminAuthority", + "isMut": true, + "isSigner": false, + "docs": [ + "Admin authority PDA authorizing this action" + ] + }, + { + "name": "targetAuthority", + "isMut": true, + "isSigner": false, + "docs": [ + "Authority PDA to be removed" + ] + }, + { + "name": "refundDestination", + "isMut": true, + "isSigner": false, + "docs": [ + "Account to receive rent refund" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "authorizerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Optional signer for Ed25519 authentication" + ] + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 2 + } + }, + { + "name": "TransferOwnership", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Transaction payer" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "currentOwnerAuthority", + "isMut": true, + "isSigner": false, + "docs": [ + "Current owner authority PDA" + ] + }, + { + "name": "newOwnerAuthority", + "isMut": true, + "isSigner": false, + "docs": [ + "New owner authority PDA to be created" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "authorizerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Optional signer for Ed25519 authentication" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + } + ], + "args": [ + { + "name": "newType", + "type": "u8" + }, + { + "name": "payload", + "type": "bytes" + } + ], + "discriminant": { + "type": "u8", + "value": 3 + } + }, + { + "name": "Execute", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Transaction payer" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": false, + "docs": [ + "Authority or Session PDA authorizing execution" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false, + "docs": [ + "Vault PDA" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "sysvarInstructions", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Sysvar Instructions (required for Secp256r1)" + ] + } + ], + "args": [ + { + "name": "instructions", + "type": "bytes" + } + ], + "discriminant": { + "type": "u8", + "value": 4 + } + }, + { + "name": "CreateSession", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Transaction payer and rent contributor" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "adminAuthority", + "isMut": false, + "isSigner": false, + "docs": [ + "Admin/Owner authority PDA authorizing logic" + ] + }, + { + "name": "session", + "isMut": true, + "isSigner": false, + "docs": [ + "New session PDA to be created" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Rent Sysvar" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "authorizerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Optional signer for Ed25519 authentication" + ] + } + ], + "args": [ + { + "name": "sessionKey", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "expiresAt", + "type": "i64" + } + ], + "discriminant": { + "type": "u8", + "value": 5 + } + }, + { + "name": "InitializeConfig", + "accounts": [ + { + "name": "admin", + "isMut": true, + "isSigner": true, + "docs": [ + "Initial contract admin" + ] + }, + { + "name": "config", + "isMut": true, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Rent Sysvar" + ] + } + ], + "args": [ + { + "name": "walletFee", + "type": "u64" + }, + { + "name": "actionFee", + "type": "u64" + }, + { + "name": "numShards", + "type": "u8" + } + ], + "discriminant": { + "type": "u8", + "value": 6 + } + }, + { + "name": "UpdateConfig", + "accounts": [ + { + "name": "admin", + "isMut": false, + "isSigner": true, + "docs": [ + "Current contract admin" + ] + }, + { + "name": "config", + "isMut": true, + "isSigner": false, + "docs": [ + "Config PDA" + ] + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 7 + } + }, + { + "name": "CloseSession", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Receives rent refund" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Session's parent wallet" + ] + }, + { + "name": "session", + "isMut": true, + "isSigner": false, + "docs": [ + "Target session" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA for contract admin check" + ] + }, + { + "name": "authorizer", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Wallet authority PDA" + ] + }, + { + "name": "authorizerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Ed25519 signer" + ] + }, + { + "name": "sysvarInstructions", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Secp256r1 sysvar" + ] + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 8 + } + }, + { + "name": "CloseWallet", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Pays tx fee" + ] + }, + { + "name": "wallet", + "isMut": true, + "isSigner": false, + "docs": [ + "Wallet PDA to close" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false, + "docs": [ + "Vault PDA to drain" + ] + }, + { + "name": "ownerAuthority", + "isMut": false, + "isSigner": false, + "docs": [ + "Owner Authority PDA" + ] + }, + { + "name": "destination", + "isMut": true, + "isSigner": false, + "docs": [ + "Receives all drained SOL" + ] + }, + { + "name": "ownerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Ed25519 signer" + ] + }, + { + "name": "sysvarInstructions", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Secp256r1 sysvar" + ] + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 9 + } + }, + { + "name": "SweepTreasury", + "accounts": [ + { + "name": "admin", + "isMut": false, + "isSigner": true, + "docs": [ + "Contract admin" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "destination", + "isMut": true, + "isSigner": false, + "docs": [ + "Receives swept funds" + ] + } + ], + "args": [ + { + "name": "shardId", + "type": "u8" + } + ], + "discriminant": { + "type": "u8", + "value": 10 + } + }, + { + "name": "InitTreasuryShard", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Pays for rent exemption" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Rent Sysvar" + ] + } + ], + "args": [ + { + "name": "shardId", + "type": "u8" + } + ], + "discriminant": { + "type": "u8", + "value": 11 + } + } + ], + "accounts": [ + { + "name": "WalletAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "discriminator", + "type": "u8" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "version", + "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 5 + ] + } + } + ] + } + }, + { + "name": "AuthorityAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "discriminator", + "type": "u8" + }, + { + "name": "authorityType", + "type": "u8" + }, + { + "name": "role", + "type": "u8" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "version", + "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 3 + ] + } + }, + { + "name": "counter", + "type": "u64" + }, + { + "name": "wallet", + "type": "publicKey" + } + ] + } + }, + { + "name": "SessionAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "discriminator", + "type": "u8" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "version", + "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 5 + ] + } + }, + { + "name": "wallet", + "type": "publicKey" + }, + { + "name": "sessionKey", + "type": "publicKey" + }, + { + "name": "expiresAt", + "type": "u64" + } + ] + } + } + ], + "types": [ + { + "name": "AuthorityType", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Ed25519" + }, + { + "name": "Secp256r1" + } + ] + } + }, + { + "name": "Role", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Owner" + }, + { + "name": "Admin" + }, + { + "name": "Spender" + } + ] + } + } + ], + "errors": [ + { + "code": 3001, + "name": "InvalidAuthorityPayload", + "msg": "Invalid authority payload" + }, + { + "code": 3002, + "name": "PermissionDenied", + "msg": "Permission denied" + }, + { + "code": 3003, + "name": "InvalidInstruction", + "msg": "Invalid instruction" + }, + { + "code": 3004, + "name": "InvalidPubkey", + "msg": "Invalid public key" + }, + { + "code": 3005, + "name": "InvalidMessageHash", + "msg": "Invalid message hash" + }, + { + "code": 3006, + "name": "SignatureReused", + "msg": "Signature has already been used" + }, + { + "code": 3007, + "name": "InvalidSignatureAge", + "msg": "Invalid signature age" + }, + { + "code": 3008, + "name": "InvalidSessionDuration", + "msg": "Invalid session duration" + }, + { + "code": 3009, + "name": "SessionExpired", + "msg": "Session expired" + }, + { + "code": 3010, + "name": "AuthorityDoesNotSupportSession", + "msg": "Authority type does not support sessions" + }, + { + "code": 3011, + "name": "InvalidAuthenticationKind", + "msg": "Invalid authentication kind" + }, + { + "code": 3012, + "name": "InvalidMessage", + "msg": "Invalid message" + }, + { + "code": 3013, + "name": "SelfReentrancyNotAllowed", + "msg": "Self-reentrancy is not allowed" + } + ], + "metadata": { + "origin": "shank", + "address": "DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2" + } +} \ No newline at end of file diff --git a/program/lazorkit_program.json b/program/lazorkit_program.json new file mode 100644 index 0000000..b9a3a0d --- /dev/null +++ b/program/lazorkit_program.json @@ -0,0 +1,1108 @@ +{ + "version": "0.1.0", + "name": "lazorkit_program", + "instructions": [ + { + "name": "CreateWallet", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Payer and rent contributor" + ] + }, + { + "name": "wallet", + "isMut": true, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false, + "docs": [ + "Vault PDA" + ] + }, + { + "name": "authority", + "isMut": true, + "isSigner": false, + "docs": [ + "Initial owner authority PDA" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Rent Sysvar" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + } + ], + "args": [ + { + "name": "userSeed", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "authType", + "type": "u8" + }, + { + "name": "authBump", + "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 6 + ] + } + }, + { + "name": "payload", + "type": "bytes" + } + ], + "discriminant": { + "type": "u8", + "value": 0 + } + }, + { + "name": "AddAuthority", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Transaction payer" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "adminAuthority", + "isMut": false, + "isSigner": false, + "docs": [ + "Admin authority PDA authorizing this action" + ] + }, + { + "name": "newAuthority", + "isMut": true, + "isSigner": false, + "docs": [ + "New authority PDA to be created" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "authorizerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Optional signer for Ed25519 authentication" + ] + } + ], + "args": [ + { + "name": "newType", + "type": "u8" + }, + { + "name": "newRole", + "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 6 + ] + } + }, + { + "name": "payload", + "type": "bytes" + } + ], + "discriminant": { + "type": "u8", + "value": 1 + } + }, + { + "name": "RemoveAuthority", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Transaction payer" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "adminAuthority", + "isMut": false, + "isSigner": false, + "docs": [ + "Admin authority PDA authorizing this action" + ] + }, + { + "name": "targetAuthority", + "isMut": true, + "isSigner": false, + "docs": [ + "Authority PDA to be removed" + ] + }, + { + "name": "refundDestination", + "isMut": true, + "isSigner": false, + "docs": [ + "Account to receive rent refund" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "authorizerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Optional signer for Ed25519 authentication" + ] + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 2 + } + }, + { + "name": "TransferOwnership", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Transaction payer" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "currentOwnerAuthority", + "isMut": true, + "isSigner": false, + "docs": [ + "Current owner authority PDA" + ] + }, + { + "name": "newOwnerAuthority", + "isMut": true, + "isSigner": false, + "docs": [ + "New owner authority PDA to be created" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "authorizerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Optional signer for Ed25519 authentication" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + } + ], + "args": [ + { + "name": "newType", + "type": "u8" + }, + { + "name": "payload", + "type": "bytes" + } + ], + "discriminant": { + "type": "u8", + "value": 3 + } + }, + { + "name": "Execute", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Transaction payer" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "authority", + "isMut": false, + "isSigner": false, + "docs": [ + "Authority or Session PDA authorizing execution" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false, + "docs": [ + "Vault PDA" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "sysvarInstructions", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Sysvar Instructions (required for Secp256r1)" + ] + } + ], + "args": [ + { + "name": "instructions", + "type": "bytes" + } + ], + "discriminant": { + "type": "u8", + "value": 4 + } + }, + { + "name": "CreateSession", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Transaction payer and rent contributor" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Wallet PDA" + ] + }, + { + "name": "adminAuthority", + "isMut": false, + "isSigner": false, + "docs": [ + "Admin/Owner authority PDA authorizing logic" + ] + }, + { + "name": "session", + "isMut": true, + "isSigner": false, + "docs": [ + "New session PDA to be created" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Rent Sysvar" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "authorizerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Optional signer for Ed25519 authentication" + ] + } + ], + "args": [ + { + "name": "sessionKey", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "expiresAt", + "type": "i64" + } + ], + "discriminant": { + "type": "u8", + "value": 5 + } + }, + { + "name": "InitializeConfig", + "accounts": [ + { + "name": "admin", + "isMut": true, + "isSigner": true, + "docs": [ + "Initial contract admin" + ] + }, + { + "name": "config", + "isMut": true, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Rent Sysvar" + ] + } + ], + "args": [ + { + "name": "walletFee", + "type": "u64" + }, + { + "name": "actionFee", + "type": "u64" + }, + { + "name": "numShards", + "type": "u8" + } + ], + "discriminant": { + "type": "u8", + "value": 6 + } + }, + { + "name": "UpdateConfig", + "accounts": [ + { + "name": "admin", + "isMut": false, + "isSigner": true, + "docs": [ + "Current contract admin" + ] + }, + { + "name": "config", + "isMut": true, + "isSigner": false, + "docs": [ + "Config PDA" + ] + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 7 + } + }, + { + "name": "CloseSession", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Receives rent refund" + ] + }, + { + "name": "wallet", + "isMut": false, + "isSigner": false, + "docs": [ + "Session's parent wallet" + ] + }, + { + "name": "session", + "isMut": true, + "isSigner": false, + "docs": [ + "Target session" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA for contract admin check" + ] + }, + { + "name": "authorizer", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Wallet authority PDA" + ] + }, + { + "name": "authorizerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Ed25519 signer" + ] + }, + { + "name": "sysvarInstructions", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Secp256r1 sysvar" + ] + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 8 + } + }, + { + "name": "CloseWallet", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Pays tx fee" + ] + }, + { + "name": "wallet", + "isMut": true, + "isSigner": false, + "docs": [ + "Wallet PDA to close" + ] + }, + { + "name": "vault", + "isMut": true, + "isSigner": false, + "docs": [ + "Vault PDA to drain" + ] + }, + { + "name": "ownerAuthority", + "isMut": false, + "isSigner": false, + "docs": [ + "Owner Authority PDA" + ] + }, + { + "name": "destination", + "isMut": true, + "isSigner": false, + "docs": [ + "Receives all drained SOL" + ] + }, + { + "name": "ownerSigner", + "isMut": false, + "isSigner": true, + "isOptional": true, + "docs": [ + "Ed25519 signer" + ] + }, + { + "name": "sysvarInstructions", + "isMut": false, + "isSigner": false, + "isOptional": true, + "docs": [ + "Secp256r1 sysvar" + ] + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 9 + } + }, + { + "name": "SweepTreasury", + "accounts": [ + { + "name": "admin", + "isMut": false, + "isSigner": true, + "docs": [ + "Contract admin" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "destination", + "isMut": true, + "isSigner": false, + "docs": [ + "Receives swept funds" + ] + } + ], + "args": [ + { + "name": "shardId", + "type": "u8" + } + ], + "discriminant": { + "type": "u8", + "value": 10 + } + }, + { + "name": "InitTreasuryShard", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true, + "docs": [ + "Pays for rent exemption" + ] + }, + { + "name": "config", + "isMut": false, + "isSigner": false, + "docs": [ + "Config PDA" + ] + }, + { + "name": "treasuryShard", + "isMut": true, + "isSigner": false, + "docs": [ + "Treasury Shard PDA" + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "System Program" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false, + "docs": [ + "Rent Sysvar" + ] + } + ], + "args": [ + { + "name": "shardId", + "type": "u8" + } + ], + "discriminant": { + "type": "u8", + "value": 11 + } + } + ], + "accounts": [ + { + "name": "WalletAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "discriminator", + "type": "u8" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "version", + "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 5 + ] + } + } + ] + } + }, + { + "name": "AuthorityAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "discriminator", + "type": "u8" + }, + { + "name": "authorityType", + "type": "u8" + }, + { + "name": "role", + "type": "u8" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "version", + "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 3 + ] + } + }, + { + "name": "counter", + "type": "u64" + }, + { + "name": "wallet", + "type": { + "array": [ + "u8", + 32 + ] + } + } + ] + } + }, + { + "name": "SessionAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "discriminator", + "type": "u8" + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "version", + "type": "u8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 5 + ] + } + }, + { + "name": "wallet", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "sessionKey", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "expiresAt", + "type": "u64" + } + ] + } + } + ], + "types": [ + { + "name": "AuthorityType", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Ed25519" + }, + { + "name": "Secp256r1" + } + ] + } + }, + { + "name": "Role", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Owner" + }, + { + "name": "Admin" + }, + { + "name": "Spender" + } + ] + } + } + ], + "errors": [ + { + "code": 3001, + "name": "InvalidAuthorityPayload", + "msg": "Invalid authority payload" + }, + { + "code": 3002, + "name": "PermissionDenied", + "msg": "Permission denied" + }, + { + "code": 3003, + "name": "InvalidInstruction", + "msg": "Invalid instruction" + }, + { + "code": 3004, + "name": "InvalidPubkey", + "msg": "Invalid public key" + }, + { + "code": 3005, + "name": "InvalidMessageHash", + "msg": "Invalid message hash" + }, + { + "code": 3006, + "name": "SignatureReused", + "msg": "Signature has already been used" + }, + { + "code": 3007, + "name": "InvalidSignatureAge", + "msg": "Invalid signature age" + }, + { + "code": 3008, + "name": "InvalidSessionDuration", + "msg": "Invalid session duration" + }, + { + "code": 3009, + "name": "SessionExpired", + "msg": "Session expired" + }, + { + "code": 3010, + "name": "AuthorityDoesNotSupportSession", + "msg": "Authority type does not support sessions" + }, + { + "code": 3011, + "name": "InvalidAuthenticationKind", + "msg": "Invalid authentication kind" + }, + { + "code": 3012, + "name": "InvalidMessage", + "msg": "Invalid message" + }, + { + "code": 3013, + "name": "SelfReentrancyNotAllowed", + "msg": "Self-reentrancy is not allowed" + } + ], + "metadata": { + "origin": "shank", + "address": "11111111111111111111111111111111" + } +} \ No newline at end of file diff --git a/program/src/auth/ed25519.rs b/program/src/auth/ed25519.rs new file mode 100644 index 0000000..80a7569 --- /dev/null +++ b/program/src/auth/ed25519.rs @@ -0,0 +1,36 @@ +use crate::auth::traits::Authenticator; +use crate::state::authority::AuthorityAccountHeader; +use assertions::sol_assert_bytes_eq; +use pinocchio::{account_info::AccountInfo, program_error::ProgramError}; + +pub struct Ed25519Authenticator; + +impl Authenticator for Ed25519Authenticator { + fn authenticate( + &self, + _program_id: &pinocchio::pubkey::Pubkey, + accounts: &[AccountInfo], + authority_data: &mut [u8], + _auth_payload: &[u8], + _signed_payload: &[u8], + _discriminator: &[u8], + ) -> Result<(), ProgramError> { + if authority_data.len() < std::mem::size_of::() + 32 { + return Err(ProgramError::InvalidAccountData); + } + + // Header is at specific offset, but we just need variable data here for key + let header_size = std::mem::size_of::(); + // Ed25519 key is immediately after header + let pubkey_bytes = &authority_data[header_size..header_size + 32]; + + for account in accounts { + if account.is_signer() && sol_assert_bytes_eq(account.key().as_ref(), pubkey_bytes, 32) + { + return Ok(()); + } + } + + Err(ProgramError::MissingRequiredSignature) + } +} diff --git a/program/src/auth/mod.rs b/program/src/auth/mod.rs new file mode 100644 index 0000000..81dcc02 --- /dev/null +++ b/program/src/auth/mod.rs @@ -0,0 +1,3 @@ +pub mod ed25519; +pub mod secp256r1; +pub mod traits; diff --git a/program/src/auth/secp256r1/introspection.rs b/program/src/auth/secp256r1/introspection.rs new file mode 100644 index 0000000..8dfb406 --- /dev/null +++ b/program/src/auth/secp256r1/introspection.rs @@ -0,0 +1,126 @@ +use crate::error::AuthError; +use pinocchio::program_error::ProgramError; + +/// Secp256r1 program ID +pub const SECP256R1_PROGRAM_ID: [u8; 32] = [ + 6, 146, 13, 236, 47, 234, 113, 181, 183, 35, 129, 77, 116, 45, 169, 3, 28, 131, 231, 95, 219, + 121, 93, 86, 142, 117, 71, 128, 32, 0, 0, 0, +]; // "Secp256r1SigVerify1111111111111111111111111" + +/// Constants from the secp256r1 program +pub const COMPRESSED_PUBKEY_SERIALIZED_SIZE: usize = 33; // Stored 33-byte key (0x02/0x03 + X) +pub const PRECOMPILE_PUBKEY_SERIALIZED_SIZE: usize = 33; // Precompile also uses 33-byte Compressed key! +pub const SIGNATURE_SERIALIZED_SIZE: usize = 64; +pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 14; +pub const SIGNATURE_OFFSETS_START: usize = 2; // Matches native precompile [num_sigs(1)][padding(1)] +pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START; + +pub const SIGNATURE_DATA_OFFSET: usize = DATA_START; +pub const PUBKEY_DATA_OFFSET: usize = DATA_START + SIGNATURE_SERIALIZED_SIZE; // 16 + 64 = 80 + // Precompile uses the 64-byte RAW key, so the message offset must account for 64 bytes +pub const MESSAGE_DATA_OFFSET: usize = PUBKEY_DATA_OFFSET + PRECOMPILE_PUBKEY_SERIALIZED_SIZE + 1; // 80 + 33 + 1 = 114 (Padding for alignment) +pub const MESSAGE_DATA_SIZE: usize = 32; + +/// Secp256r1 signature offsets structure (matches solana-secp256r1-program) +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct Secp256r1SignatureOffsets { + /// Offset to compact secp256r1 signature of 64 bytes + pub signature_offset: u16, + /// Instruction index where the signature can be found + pub signature_instruction_index: u16, + /// Offset to compressed public key of 33 bytes + pub public_key_offset: u16, + /// Instruction index where the public key can be found + pub public_key_instruction_index: u16, + /// Offset to the start of message data + pub message_data_offset: u16, + /// Size of message data in bytes + pub message_data_size: u16, + /// Instruction index where the message data can be found + pub message_instruction_index: u16, +} + +impl Secp256r1SignatureOffsets { + /// Deserialize from bytes (14 bytes in little-endian format) + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != SIGNATURE_OFFSETS_SERIALIZED_SIZE { + return Err(AuthError::InvalidInstruction.into()); + } + + Ok(Self { + signature_offset: u16::from_le_bytes([bytes[0], bytes[1]]), + signature_instruction_index: u16::from_le_bytes([bytes[2], bytes[3]]), + public_key_offset: u16::from_le_bytes([bytes[4], bytes[5]]), + public_key_instruction_index: u16::from_le_bytes([bytes[6], bytes[7]]), + message_data_offset: u16::from_le_bytes([bytes[8], bytes[9]]), + message_data_size: u16::from_le_bytes([bytes[10], bytes[11]]), + message_instruction_index: u16::from_le_bytes([bytes[12], bytes[13]]), + }) + } +} + +/// Verify the secp256r1 instruction data contains the expected signature and +/// public key. This also validates that the secp256r1 precompile offsets point +/// to the expected locations, ensuring proper data alignment. +pub fn verify_secp256r1_instruction_data( + instruction_data: &[u8], + expected_pubkey: &[u8; 33], + expected_message: &[u8], +) -> Result<(), ProgramError> { + // Minimum check: must have at least the header and offsets + if instruction_data.len() < DATA_START { + return Err(AuthError::InvalidInstruction.into()); + } + let num_signatures = instruction_data[0] as usize; + if num_signatures == 0 || num_signatures > 1 { + return Err(AuthError::InvalidInstruction.into()); + } + + if instruction_data.len() < MESSAGE_DATA_OFFSET + MESSAGE_DATA_SIZE { + return Err(AuthError::InvalidInstruction.into()); + } + + // Parse the Secp256r1SignatureOffsets structure + let offsets = Secp256r1SignatureOffsets::from_bytes( + &instruction_data + [SIGNATURE_OFFSETS_START..SIGNATURE_OFFSETS_START + SIGNATURE_OFFSETS_SERIALIZED_SIZE], + )?; + + // Validate that all offsets point to the current instruction (0xFFFF) + // This ensures all data references are within the same instruction + if offsets.signature_instruction_index != 0xFFFF && offsets.signature_instruction_index != 0 { + return Err(AuthError::InvalidInstruction.into()); + } + if offsets.public_key_instruction_index != 0xFFFF && offsets.public_key_instruction_index != 0 { + return Err(AuthError::InvalidInstruction.into()); + } + if offsets.message_instruction_index != 0xFFFF && offsets.message_instruction_index != 0 { + return Err(AuthError::InvalidInstruction.into()); + } + + // Validate that the offsets match the expected fixed locations + // This ensures the precompile is verifying the data we're checking + if offsets.public_key_offset as usize != PUBKEY_DATA_OFFSET { + return Err(AuthError::InvalidInstruction.into()); + } + if offsets.message_data_offset as usize != MESSAGE_DATA_OFFSET { + return Err(AuthError::InvalidInstruction.into()); + } + if offsets.message_data_size as usize != expected_message.len() { + return Err(AuthError::InvalidInstruction.into()); + } + + let pubkey_data = &instruction_data + [PUBKEY_DATA_OFFSET..PUBKEY_DATA_OFFSET + COMPRESSED_PUBKEY_SERIALIZED_SIZE]; + let message_data = + &instruction_data[MESSAGE_DATA_OFFSET..MESSAGE_DATA_OFFSET + expected_message.len()]; + + if pubkey_data != expected_pubkey { + return Err(AuthError::InvalidPubkey.into()); + } + if message_data != expected_message { + return Err(AuthError::InvalidMessageHash.into()); + } + Ok(()) +} diff --git a/program/src/auth/secp256r1/mod.rs b/program/src/auth/secp256r1/mod.rs new file mode 100644 index 0000000..c01d732 --- /dev/null +++ b/program/src/auth/secp256r1/mod.rs @@ -0,0 +1,222 @@ +use crate::{error::AuthError, state::authority::AuthorityAccountHeader}; +use pinocchio::{ + account_info::AccountInfo, + program_error::ProgramError, + sysvars::instructions::{Instructions, INSTRUCTIONS_ID}, +}; +use pinocchio_pubkey::pubkey; + +pub mod introspection; +pub mod nonce; +pub mod slothashes; +pub mod webauthn; + +use self::introspection::verify_secp256r1_instruction_data; +use self::nonce::validate_nonce; +use self::webauthn::{ + reconstruct_client_data_json, AuthDataParser, ClientDataJsonReconstructionParams, +}; + +use crate::auth::traits::Authenticator; + +/// Authenticator implementation for Secp256r1 (WebAuthn). +pub struct Secp256r1Authenticator; + +impl Authenticator for Secp256r1Authenticator { + /// Authenticates a Secp256r1 signature (WebAuthn/Passkeys). + /// + /// # Arguments + /// * `accounts`: Slice of accounts, expecting Sysvar Lookups if needed. + /// * `auth_data`: Mutable reference to the Authority account data (to update counter). + /// * `auth_payload`: Auxiliary data (e.g., signature, authenticator data, client JSON parts). + /// * `signed_payload`: The actual message/data that was signed (e.g. instruction args). + fn authenticate( + &self, + program_id: &pinocchio::pubkey::Pubkey, + accounts: &[AccountInfo], + authority_data: &mut [u8], + auth_payload: &[u8], + signed_payload: &[u8], // The message payload (e.g. compact instructions or args) that is signed + discriminator: &[u8], + ) -> Result<(), ProgramError> { + if auth_payload.len() < 12 { + return Err(AuthError::InvalidAuthorityPayload.into()); + } + + let _payer = accounts.get(0).ok_or(ProgramError::NotEnoughAccountKeys)?; + + let slot = u64::from_le_bytes(auth_payload[0..8].try_into().unwrap()); + let sysvar_ix_index = auth_payload[8] as usize; + let sysvar_slothashes_index = auth_payload[9] as usize; + + let reconstruction_params = ClientDataJsonReconstructionParams { + type_and_flags: auth_payload[10], + }; + let rp_id_len = auth_payload[11] as usize; + if auth_payload.len() < 12 + rp_id_len { + return Err(AuthError::InvalidAuthorityPayload.into()); + } + let rp_id = &auth_payload[12..12 + rp_id_len]; + let authenticator_data_raw = &auth_payload[12 + rp_id_len..]; + + // Validate Nonce (SlotHashes) + let slothashes_account = accounts + .get(sysvar_slothashes_index) + .ok_or(AuthError::InvalidAuthorityPayload)?; + // TruncatedSlot removed (Issue #16), passing u64 slot directly + let _slot_hash = validate_nonce(slothashes_account, slot)?; + + let header_size = std::mem::size_of::(); + // Check size + if authority_data.len() < header_size { + return Err(AuthError::InvalidAuthorityPayload.into()); + } + + // Safe read + let mut header = unsafe { + std::ptr::read_unaligned(authority_data.as_ptr() as *const AuthorityAccountHeader) + }; + + // Secp256r1 on-chain data layout: + // [Header] [credential_id_hash(32)] [Pubkey(33)] + // Note: credential_id_hash is stored for off-chain wallet discovery + // via getProgramAccounts + memcmp filter. It is not used in authentication. + let pubkey_offset = header_size + 32; // skip credential_id_hash + + #[allow(unused_assignments)] + let mut computed_rp_id_hash = [0u8; 32]; + #[cfg(target_os = "solana")] + unsafe { + let _res = pinocchio::syscalls::sol_sha256( + [rp_id].as_ptr() as *const u8, + 1, + computed_rp_id_hash.as_mut_ptr(), + ); + } + #[cfg(not(target_os = "solana"))] + { + computed_rp_id_hash = [0u8; 32]; + } + + let payer = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + #[allow(unused_assignments)] + let mut hasher = [0u8; 32]; + let slot_bytes = slot.to_le_bytes(); + let payer_bytes = payer.key(); + let program_id_bytes = program_id.as_ref(); + let slices = [ + discriminator, + auth_payload, + signed_payload, + slot_bytes.as_slice(), + payer_bytes.as_ref(), + program_id_bytes, + ]; + #[cfg(target_os = "solana")] + unsafe { + let _res = pinocchio::syscalls::sol_sha256( + slices.as_ptr() as *const u8, + 6, + hasher.as_mut_ptr(), + ); + } + #[cfg(not(target_os = "solana"))] + { + let _ = signed_payload; // suppress unused warning for non-solana + let _ = discriminator; + let _ = auth_payload; + let _ = payer; + let _ = program_id; + hasher = [0u8; 32]; + } + + let client_data_json = reconstruct_client_data_json(&reconstruction_params, rp_id, &hasher); + #[allow(unused_assignments)] + let mut client_data_hash = [0u8; 32]; + #[cfg(target_os = "solana")] + unsafe { + let _res = pinocchio::syscalls::sol_sha256( + [client_data_json.as_slice()].as_ptr() as *const u8, + 1, + client_data_hash.as_mut_ptr(), + ); + } + #[cfg(not(target_os = "solana"))] + { + let _ = client_data_json; + client_data_hash = [0u8; 32]; + } + + let authority_data_parser = AuthDataParser::new(authenticator_data_raw); + if !authority_data_parser.is_user_present() { + return Err(AuthError::PermissionDenied.into()); + } + + let authenticator_counter = authority_data_parser.counter() as u64; + + // Prevent replay attacks: counter must be strictly increasing + // Counter 0 is always rejected (invalid state) + if authenticator_counter == 0 || authenticator_counter <= header.counter { + return Err(AuthError::SignatureReused.into()); + } + header.counter = authenticator_counter; + unsafe { + std::ptr::write_unaligned( + authority_data.as_mut_ptr() as *mut AuthorityAccountHeader, + header, + ); + } + + // Security Validation (Replaces on-chain storage check): + // Ensure the domain (rp_id_hash) the user provided in the instruction payload actually matches + // the rpIdHash that the authenticator (Hardware/FaceID) signed over inside authenticatorData. + // This validates the origin domain mathematically without wasting 32 bytes on-chain. + if authority_data_parser.rp_id_hash() != computed_rp_id_hash { + return Err(AuthError::InvalidPubkey.into()); + } + + // Unified Model: + // - Precompile Instruction Data: Contains 33-byte COMPRESSED public key (Prefix, X) + // - Contract Storage: Contains 33-byte COMPRESSED public key (Prefix, X) + // The fuzzing test confirmed the precompile supports 33-byte compressed keys. + + // 1. Extract the 33-byte COMPRESSED key from the precompile instruction data + let instruction_pubkey_bytes = &authority_data[pubkey_offset..pubkey_offset + 33]; + let expected_pubkey: &[u8; 33] = instruction_pubkey_bytes.try_into().unwrap(); + + let mut signed_message = Vec::with_capacity(authenticator_data_raw.len() + 32); + signed_message.extend_from_slice(authenticator_data_raw); + signed_message.extend_from_slice(&client_data_hash); + + let sysvar_instructions = accounts + .get(sysvar_ix_index) + .ok_or(AuthError::InvalidAuthorityPayload)?; + if sysvar_instructions.key().as_ref() != INSTRUCTIONS_ID.as_ref() { + return Err(AuthError::InvalidInstruction.into()); + } + + let sysvar_data = unsafe { sysvar_instructions.borrow_data_unchecked() }; + let ixs = unsafe { Instructions::new_unchecked(sysvar_data) }; + let current_index = ixs.load_current_index() as usize; + if current_index == 0 { + return Err(AuthError::InvalidInstruction.into()); + } + + let secp_ix = unsafe { ixs.deserialize_instruction_unchecked(current_index - 1) }; + if secp_ix.get_program_id() != &pubkey!("Secp256r1SigVerify1111111111111111111111111") { + return Err(AuthError::InvalidInstruction.into()); + } + + verify_secp256r1_instruction_data( + secp_ix.get_instruction_data(), + expected_pubkey, // Now passing the 33-byte key, matching helper signature + &signed_message, + )?; + + Ok(()) + } +} diff --git a/program/src/auth/secp256r1/nonce.rs b/program/src/auth/secp256r1/nonce.rs new file mode 100644 index 0000000..aef6427 --- /dev/null +++ b/program/src/auth/secp256r1/nonce.rs @@ -0,0 +1,43 @@ +use pinocchio::{ + account_info::{AccountInfo, Ref}, + program_error::ProgramError, +}; + +use crate::auth::secp256r1::slothashes::SlotHashes; +use crate::error::AuthError; + +// TruncatedSlot removed (Issue #16) + +use crate::utils::get_stack_height; + +pub fn validate_nonce( + slothashes_sysvar: &AccountInfo, + submitted_slot: u64, +) -> Result<[u8; 32], ProgramError> { + // Ensure the program isn't being called via CPI + if get_stack_height() > 1 { + return Err(AuthError::PermissionDenied.into()); // Mapping CPINotAllowed error + } + + let slothashes = SlotHashes::>::try_from(slothashes_sysvar)?; + + // Get current slothash (index 0) + let most_recent_slot_hash = slothashes.get_slot_hash(0)?; + let current_slot = most_recent_slot_hash.height; + + // Check if submitted slot is in the future + if submitted_slot > current_slot { + return Err(AuthError::InvalidSignatureAge.into()); + } + + let index_difference = current_slot - submitted_slot; + + if index_difference >= 150 { + return Err(AuthError::InvalidSignatureAge.into()); + } + + // Assuming SlotHashes stores hashes in descending order of slot height + let slot_hash = slothashes.get_slot_hash(index_difference as usize)?; + + Ok(slot_hash.hash) +} diff --git a/program/src/auth/secp256r1/slothashes.rs b/program/src/auth/secp256r1/slothashes.rs new file mode 100644 index 0000000..8f36ccb --- /dev/null +++ b/program/src/auth/secp256r1/slothashes.rs @@ -0,0 +1,83 @@ +use core::ops::Deref; +use pinocchio::{ + account_info::{AccountInfo, Ref}, + program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::error::AuthError; + +// SysvarS1otHashes111111111111111111111111111 +pub const SLOT_HASHES_ID: Pubkey = [ + 0x06, 0xa7, 0xd5, 0x17, 0x19, 0x2f, 0x0a, 0xaf, 0xc6, 0xf2, 0x65, 0xe3, 0xfb, 0x77, 0xcc, 0x7a, + 0xda, 0x82, 0xc5, 0x29, 0xd0, 0xbe, 0x3b, 0x13, 0x6e, 0x2d, 0x00, 0x55, 0x20, 0x00, 0x00, 0x00, +]; + +#[repr(C)] +pub struct SlotHash { + pub height: u64, + pub hash: [u8; 32], +} + +pub struct SlotHashes +where + T: Deref, +{ + data: T, +} + +impl SlotHashes +where + T: Deref, +{ + /// Creates a new `SlotHashes` struct. + /// `data` is the slot hashes sysvar account data. + #[inline(always)] + /// # Safety + /// Caller must ensure data is valid. + pub unsafe fn new_unchecked(data: T) -> Self { + SlotHashes { data } + } + + /// Returns the number of slot hashes in the SlotHashes sysvar. + #[inline(always)] + pub fn get_slothashes_len(&self) -> u64 { + let raw_ptr = self.data.as_ptr(); + unsafe { u64::from_le(std::ptr::read_unaligned(raw_ptr as *const u64)) } + } + + /// Returns the slot hash at the specified index. + #[inline(always)] + /// # Safety + /// Index must be within bounds. + pub unsafe fn get_slot_hash_unchecked(&self, index: usize) -> SlotHash { + let offset = self + .data + .as_ptr() + .add(8 + index * core::mem::size_of::()); + + std::ptr::read_unaligned(offset as *const SlotHash) + } + + pub fn get_slot_hash(&self, index: usize) -> Result { + if index >= self.get_slothashes_len() as usize { + return Err(AuthError::PermissionDenied.into()); // Mapping generic error for simplicity + } + unsafe { Ok(self.get_slot_hash_unchecked(index)) } + } +} + +impl<'a> TryFrom<&'a AccountInfo> for SlotHashes> { + type Error = ProgramError; + + #[inline(always)] + fn try_from(account_info: &'a AccountInfo) -> Result { + if account_info.key() != &SLOT_HASHES_ID { + return Err(ProgramError::UnsupportedSysvar); + } + + Ok(SlotHashes { + data: account_info.try_borrow_data()?, + }) + } +} diff --git a/program/src/auth/secp256r1/webauthn.rs b/program/src/auth/secp256r1/webauthn.rs new file mode 100644 index 0000000..53c2b31 --- /dev/null +++ b/program/src/auth/secp256r1/webauthn.rs @@ -0,0 +1,140 @@ +#[allow(unused_imports)] +use crate::error::AuthError; +#[allow(unused_imports)] +use pinocchio::program_error::ProgramError; + +/// Packed flags for clientDataJson reconstruction +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct ClientDataJsonReconstructionParams { + pub type_and_flags: u8, +} + +impl ClientDataJsonReconstructionParams { + #[allow(dead_code)] + const TYPE_CREATE: u8 = 0x00; + const TYPE_GET: u8 = 0x10; + const FLAG_CROSS_ORIGIN: u8 = 0x01; + const FLAG_HTTP_ORIGIN: u8 = 0x02; + const FLAG_GOOGLE_EXTRA: u8 = 0x04; + + pub fn auth_type(&self) -> AuthType { + if (self.type_and_flags & 0xF0) == Self::TYPE_GET { + AuthType::Get + } else { + AuthType::Create + } + } + + pub fn is_cross_origin(&self) -> bool { + self.type_and_flags & Self::FLAG_CROSS_ORIGIN != 0 + } + + pub fn is_http(&self) -> bool { + self.type_and_flags & Self::FLAG_HTTP_ORIGIN != 0 + } + + pub fn has_google_extra(&self) -> bool { + self.type_and_flags & Self::FLAG_GOOGLE_EXTRA != 0 + } +} + +#[derive(Clone, Copy, Debug)] +pub enum AuthType { + Create, + Get, +} + +/// Simple Base64URL encoder without padding +pub fn base64url_encode_no_pad(data: &[u8]) -> Vec { + const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + let mut result = Vec::with_capacity(data.len().div_ceil(3) * 4); + + for chunk in data.chunks(3) { + let b = match chunk.len() { + 3 => (chunk[0] as u32) << 16 | (chunk[1] as u32) << 8 | (chunk[2] as u32), + 2 => (chunk[0] as u32) << 16 | (chunk[1] as u32) << 8, + 1 => (chunk[0] as u32) << 16, + _ => unreachable!(), + }; + + result.push(ALPHABET[((b >> 18) & 0x3f) as usize]); + result.push(ALPHABET[((b >> 12) & 0x3f) as usize]); + if chunk.len() > 1 { + result.push(ALPHABET[((b >> 6) & 0x3f) as usize]); + } + if chunk.len() > 2 { + result.push(ALPHABET[(b & 0x3f) as usize]); + } + } + result +} + +/// Reconstructs the clientDataJson +pub fn reconstruct_client_data_json( + params: &ClientDataJsonReconstructionParams, + rp_id: &[u8], + challenge: &[u8], +) -> Vec { + let challenge_b64url = base64url_encode_no_pad(challenge); + let type_str: &[u8] = match params.auth_type() { + AuthType::Create => b"webauthn.create", + AuthType::Get => b"webauthn.get", + }; + + let prefix: &[u8] = if params.is_http() { + b"http://" + } else { + b"https://" + }; + let cross_origin: &[u8] = if params.is_cross_origin() { + b"true" + } else { + b"false" + }; + + let mut json = Vec::with_capacity(256); + json.extend_from_slice(b"{\"type\":\""); + json.extend_from_slice(type_str); + json.extend_from_slice(b"\",\"challenge\":\""); + json.extend_from_slice(&challenge_b64url); + json.extend_from_slice(b"\",\"origin\":\""); + json.extend_from_slice(prefix); + json.extend_from_slice(rp_id); + json.extend_from_slice(b"\",\"crossOrigin\":"); + json.extend_from_slice(cross_origin); + + if params.has_google_extra() { + json.extend_from_slice(b",\"other_keys_can_be_added_here\":\"do not compare clientDataJSON against a template. See https://goo.gl/yabPex\""); + } + + json.extend_from_slice(b"}"); + json +} + +/// Parser for WebAuthn authenticator data +pub struct AuthDataParser<'a> { + data: &'a [u8], +} + +impl<'a> AuthDataParser<'a> { + pub fn new(data: &'a [u8]) -> Self { + Self { data } + } + + pub fn rp_id_hash(&self) -> &'a [u8] { + &self.data[0..32] + } + + pub fn is_user_present(&self) -> bool { + self.data[32] & 0x01 != 0 + } + + pub fn is_user_verified(&self) -> bool { + self.data[32] & 0x04 != 0 + } + + pub fn counter(&self) -> u32 { + u32::from_be_bytes(self.data[33..37].try_into().unwrap()) + } +} diff --git a/program/src/auth/traits.rs b/program/src/auth/traits.rs new file mode 100644 index 0000000..45df617 --- /dev/null +++ b/program/src/auth/traits.rs @@ -0,0 +1,22 @@ +use pinocchio::account_info::AccountInfo; +use pinocchio::program_error::ProgramError; + +/// Trait for defining the authentication logic for different authority types. +pub trait Authenticator { + /// Authenticate the execution request. + /// + /// # Arguments + /// * `accounts` - The full slice of accounts passed to the instruction. + /// * `authority_data` - The mutable data of the authority account. + /// * `auth_payload` - The specific authentication payload (e.g. signature, proof). + /// * `signed_payload` - The message/payload that was signed (e.g. instruction args). + fn authenticate( + &self, + program_id: &pinocchio::pubkey::Pubkey, + accounts: &[AccountInfo], + authority_data: &mut [u8], + auth_payload: &[u8], + signed_payload: &[u8], + discriminator: &[u8], + ) -> Result<(), ProgramError>; +} diff --git a/program/src/compact.rs b/program/src/compact.rs new file mode 100644 index 0000000..fd4db2f --- /dev/null +++ b/program/src/compact.rs @@ -0,0 +1,381 @@ +use pinocchio::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; + +/// Container for a set of compact instructions. +/// +/// This struct holds multiple compact instructions and provides +/// functionality to serialize them into a byte format. +pub struct CompactInstructions { + /// Vector of individual compact instructions + pub inner_instructions: Vec, +} + +/// Represents a single instruction in compact format. +/// +/// Instead of storing full public keys, this format uses indexes +/// into a shared account list to reduce data size. +/// +/// # Fields +/// * `program_id_index` - Index of the program ID in the account list +/// * `accounts` - Indexes of accounts used by this instruction +/// * `data` - Raw instruction data +#[derive(Debug, Clone)] +pub struct CompactInstruction { + pub program_id_index: u8, + pub accounts: Vec, + pub data: Vec, +} + +/// Reference version of CompactInstruction that borrows its data. +/// +/// # Fields +/// * `program_id_index` - Index of the program ID in the account list +/// * `accounts` - Slice of account indexes +/// * `data` - Slice of instruction data +pub struct CompactInstructionRef<'a> { + pub program_id_index: u8, + pub accounts: &'a [u8], + pub data: &'a [u8], +} + +impl CompactInstructions { + /// Serializes the compact instructions into bytes. + /// + /// The byte format is: + /// 1. Number of instructions (u8) + /// 2. For each instruction: + /// - Program ID index (u8) + /// - Number of accounts (u8) + /// - Account indexes (u8 array) + /// - Data length (u16 LE) + /// - Instruction data (bytes) + /// + /// # Returns + /// * `Vec` - Serialized instruction data + pub fn into_bytes(&self) -> Vec { + let mut bytes = vec![self.inner_instructions.len() as u8]; + for ix in self.inner_instructions.iter() { + bytes.push(ix.program_id_index); + bytes.push(ix.accounts.len() as u8); + bytes.extend(ix.accounts.iter()); + bytes.extend((ix.data.len() as u16).to_le_bytes()); + bytes.extend(ix.data.iter()); + } + bytes + } +} + +impl CompactInstruction { + /// Deserialize a CompactInstruction from bytes + /// Format: [program_id_index: u8][num_accounts: u8][accounts...][data_len: u16][data...] + pub fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), ProgramError> { + if bytes.len() < 4 { + // Minimum: program_id(1) + num_accounts(1) + data_len(2) + return Err(ProgramError::InvalidInstructionData); + } + + let program_id_index = bytes[0]; + let num_accounts = bytes[1] as usize; + + if bytes.len() < 2 + num_accounts + 2 { + return Err(ProgramError::InvalidInstructionData); + } + + let accounts = bytes[2..2 + num_accounts].to_vec(); + let data_len_offset = 2 + num_accounts; + let data_len = + u16::from_le_bytes([bytes[data_len_offset], bytes[data_len_offset + 1]]) as usize; + + let data_start = data_len_offset + 2; + if bytes.len() < data_start + data_len { + return Err(ProgramError::InvalidInstructionData); + } + + let data = bytes[data_start..data_start + data_len].to_vec(); + let rest = &bytes[data_start + data_len..]; + + Ok(( + CompactInstruction { + program_id_index, + accounts, + data, + }, + rest, + )) + } + + /// Serialize this CompactInstruction to bytes + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(4 + self.accounts.len() + self.data.len()); + bytes.push(self.program_id_index); + bytes.push(self.accounts.len() as u8); + bytes.extend_from_slice(&self.accounts); + bytes.extend_from_slice(&(self.data.len() as u16).to_le_bytes()); + bytes.extend_from_slice(&self.data); + bytes + } + + /// Decompress this compact instruction into a full Instruction using the provided accounts + pub fn decompress<'a>( + &self, + account_infos: &'a [AccountInfo], + ) -> Result, ProgramError> { + // Validate program_id_index + if (self.program_id_index as usize) >= account_infos.len() { + return Err(ProgramError::InvalidInstructionData); + } + + let program_id = account_infos[self.program_id_index as usize].key(); + + // Validate all account indexes + let mut accounts = Vec::with_capacity(self.accounts.len()); + for &index in &self.accounts { + if (index as usize) >= account_infos.len() { + return Err(ProgramError::InvalidInstructionData); + } + accounts.push(&account_infos[index as usize]); + } + + Ok(DecompressedInstruction { + program_id, + accounts, + data: self.data.clone(), // Clone data to avoid lifetime issues + }) + } +} + +/// Decompressed instruction ready for execution +pub struct DecompressedInstruction<'a> { + pub program_id: &'a Pubkey, + pub accounts: Vec<&'a AccountInfo>, + pub data: Vec, // Owned data to avoid lifetime issues +} + +/// Parse multiple CompactInstructions from bytes +/// Format: [num_instructions: u8][instruction_0][instruction_1]... +pub fn parse_compact_instructions(bytes: &[u8]) -> Result, ProgramError> { + if bytes.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + + let num_instructions = bytes[0] as usize; + let mut instructions = Vec::with_capacity(num_instructions); + let mut remaining = &bytes[1..]; + + for _ in 0..num_instructions { + let (instruction, rest) = CompactInstruction::from_bytes(remaining)?; + instructions.push(instruction); + remaining = rest; + } + + Ok(instructions) +} + +/// Serialize multiple CompactInstructions to bytes +pub fn serialize_compact_instructions(instructions: &[CompactInstruction]) -> Vec { + let compact_instructions = CompactInstructions { + inner_instructions: instructions.to_vec(), + }; + compact_instructions.into_bytes() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compact_instruction_serialization() { + let ix = CompactInstruction { + program_id_index: 0, + accounts: vec![1, 2, 3], + data: vec![0xDE, 0xAD, 0xBE, 0xEF], + }; + + let bytes = ix.to_bytes(); + let (deserialized, rest) = CompactInstruction::from_bytes(&bytes).unwrap(); + + assert_eq!(rest.len(), 0); + assert_eq!(deserialized.program_id_index, ix.program_id_index); + assert_eq!(deserialized.accounts, ix.accounts); + assert_eq!(deserialized.data, ix.data); + } + + #[test] + fn test_multiple_instructions() { + let instructions = vec![ + CompactInstruction { + program_id_index: 0, + accounts: vec![1, 2], + data: vec![1, 2, 3], + }, + CompactInstruction { + program_id_index: 3, + accounts: vec![4, 5, 6], + data: vec![7, 8, 9, 10], + }, + ]; + + let bytes = serialize_compact_instructions(&instructions); + let parsed = parse_compact_instructions(&bytes).unwrap(); + + assert_eq!(parsed.len(), instructions.len()); + for (original, parsed) in instructions.iter().zip(parsed.iter()) { + assert_eq!(original.program_id_index, parsed.program_id_index); + assert_eq!(original.accounts, parsed.accounts); + assert_eq!(original.data, parsed.data); + } + } + + #[test] + fn test_empty_instruction_data() { + // Instruction with no data + let ix = CompactInstruction { + program_id_index: 0, + accounts: vec![1], + data: vec![], + }; + + let bytes = ix.to_bytes(); + let (deserialized, _) = CompactInstruction::from_bytes(&bytes).unwrap(); + + assert_eq!(deserialized.data.len(), 0); + } + + #[test] + fn test_empty_accounts() { + // Instruction with no accounts + let ix = CompactInstruction { + program_id_index: 0, + accounts: vec![], + data: vec![1, 2, 3], + }; + + let bytes = ix.to_bytes(); + let (deserialized, _) = CompactInstruction::from_bytes(&bytes).unwrap(); + + assert_eq!(deserialized.accounts.len(), 0); + } + + #[test] + fn test_max_accounts() { + // Test with maximum number of accounts (u8::MAX = 255) + let accounts: Vec = (0..=255).collect(); + let ix = CompactInstruction { + program_id_index: 0, + accounts, + data: vec![1], + }; + + let bytes = ix.to_bytes(); + let (deserialized, _) = CompactInstruction::from_bytes(&bytes).unwrap(); + + // Note: accounts.len() wraps to 0 when cast to u8! + // This is a known limitation - can't have exactly 256 accounts + assert_eq!(deserialized.accounts.len(), 0); // Wraps around! + } + + #[test] + fn test_large_data() { + // Test with large instruction data (close to u16::MAX) + let data = vec![0x42; 1000]; + let ix = CompactInstruction { + program_id_index: 0, + accounts: vec![1], + data: data.clone(), + }; + + let bytes = ix.to_bytes(); + let (deserialized, _) = CompactInstruction::from_bytes(&bytes).unwrap(); + + assert_eq!(deserialized.data.len(), 1000); + assert_eq!(deserialized.data, data); + } + + #[test] + fn test_invalid_truncated_data() { + // Truncated instruction data + let bytes = vec![0, 2, 1, 2]; // program_id, num_accounts, accounts... but missing data_len + + let result = CompactInstruction::from_bytes(&bytes); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_short_buffer() { + // Buffer too short (less than minimum 4 bytes) + let bytes = vec![0, 1, 2]; + + let result = CompactInstruction::from_bytes(&bytes); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_data_length_mismatch() { + // Data length field says 10 bytes but only 5 provided + let mut bytes = vec![0, 1, 1]; // program_id, num_accounts=1, account=1 + bytes.extend(&10u16.to_le_bytes()); // data_len = 10 + bytes.extend(&[1, 2, 3, 4, 5]); // only 5 bytes of data + + let result = CompactInstruction::from_bytes(&bytes); + assert!(result.is_err()); + } + + #[test] + fn test_empty_instructions_list() { + // Empty list of instructions + let instructions: Vec = vec![]; + + let bytes = serialize_compact_instructions(&instructions); + let parsed = parse_compact_instructions(&bytes).unwrap(); + + assert_eq!(parsed.len(), 0); + } + + #[test] + fn test_compact_instructions_wrapper() { + // Test CompactInstructions wrapper struct + let compact = CompactInstructions { + inner_instructions: vec![CompactInstruction { + program_id_index: 0, + accounts: vec![1, 2], + data: vec![0xAB, 0xCD], + }], + }; + + let bytes = compact.into_bytes(); + let parsed = parse_compact_instructions(&bytes).unwrap(); + + assert_eq!(parsed.len(), 1); + assert_eq!(parsed[0].program_id_index, 0); + assert_eq!(parsed[0].accounts, vec![1, 2]); + assert_eq!(parsed[0].data, vec![0xAB, 0xCD]); + } + + /// Test demonstrating Issue #11 fix concept: + /// Same indices with different account orderings should produce different extended payloads + #[test] + fn test_account_ordering_affects_signature() { + // This test verifies the conceptual fix for Issue #11 + // The actual hash computation happens in execute.rs with real AccountInfo + // Here we demonstrate that the account indices are preserved in serialization + + let ix = CompactInstruction { + program_id_index: 0, + accounts: vec![1, 2], // Transfer from accounts[1] to accounts[2] + data: vec![0x01], // Transfer instruction + }; + + let bytes = ix.to_bytes(); + + // The serialized format preserves exact indices + // [program_id: 0] [num_accounts: 2] [acc_idx: 1] [acc_idx: 2] [data_len: 1] [data: 0x01] + assert_eq!(bytes[0], 0); // program_id_index + assert_eq!(bytes[1], 2); // num_accounts + assert_eq!(bytes[2], 1); // first account index + assert_eq!(bytes[3], 2); // second account index + + // If accounts are reordered in transaction (Issue #11 attack): + // accounts[1] and accounts[2] would point to different pubkeys + // causing hash(pubkey[1], pubkey[2]) != hash(pubkey[2], pubkey[1]) + // This is verified at runtime in execute.rs::compute_accounts_hash + } +} diff --git a/program/src/entrypoint.rs b/program/src/entrypoint.rs new file mode 100644 index 0000000..890040c --- /dev/null +++ b/program/src/entrypoint.rs @@ -0,0 +1,39 @@ +use pinocchio::{ + account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey, + ProgramResult, +}; + +use crate::processor::{ + close_session, close_wallet, create_session, create_wallet, execute, init_treasury_shard, + initialize_config, manage_authority, sweep_treasury, transfer_ownership, update_config, +}; + +entrypoint!(process_instruction); + +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + if instruction_data.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + + let (discriminator, data) = instruction_data.split_first().unwrap(); + + match discriminator { + 0 => create_wallet::process(program_id, accounts, data), + 1 => manage_authority::process_add_authority(program_id, accounts, data), + 2 => manage_authority::process_remove_authority(program_id, accounts, data), + 3 => transfer_ownership::process(program_id, accounts, data), + 4 => execute::process(program_id, accounts, data), + 5 => create_session::process(program_id, accounts, data), + 6 => initialize_config::process(program_id, accounts, data), + 7 => update_config::process(program_id, accounts, data), + 8 => close_session::process(program_id, accounts, data), + 9 => close_wallet::process(program_id, accounts, data), + 10 => sweep_treasury::process(program_id, accounts, data), + 11 => init_treasury_shard::process(program_id, accounts, data), + _ => Err(ProgramError::InvalidInstructionData), + } +} diff --git a/program/src/error.rs b/program/src/error.rs new file mode 100644 index 0000000..dd3e6ff --- /dev/null +++ b/program/src/error.rs @@ -0,0 +1,25 @@ +use pinocchio::program_error::ProgramError; + +#[derive(Debug, Clone, Copy)] +#[repr(u32)] +pub enum AuthError { + InvalidAuthorityPayload = 3001, + PermissionDenied = 3002, + InvalidInstruction = 3003, + InvalidPubkey = 3004, + InvalidMessageHash = 3005, + SignatureReused = 3006, + InvalidSignatureAge = 3007, + InvalidSessionDuration = 3008, + SessionExpired = 3009, + AuthorityDoesNotSupportSession = 3010, + InvalidAuthenticationKind = 3011, + InvalidMessage = 3012, + SelfReentrancyNotAllowed = 3013, +} + +impl From for ProgramError { + fn from(e: AuthError) -> Self { + ProgramError::Custom(e as u32) + } +} diff --git a/program/src/idl_facade.rs b/program/src/idl_facade.rs new file mode 100644 index 0000000..4b5c0fd --- /dev/null +++ b/program/src/idl_facade.rs @@ -0,0 +1,316 @@ +// ========================================== +// Shank IDL Facade for Accounts, Types, Errors +// ========================================== +use borsh::{BorshSerialize, BorshDeserialize}; +use shank::{ShankAccount, ShankType, ShankInstruction}; + +/// Shank IDL facade enum describing all program instructions and their required accounts. +/// This is used only for IDL generation and does not affect runtime behavior. +#[derive(ShankInstruction)] +pub enum ProgramIx { + /// Create a new wallet + #[account( + 0, + signer, + writable, + name = "payer", + desc = "Payer and rent contributor" + )] + #[account(1, writable, name = "wallet", desc = "Wallet PDA")] + #[account(2, writable, name = "vault", desc = "Vault PDA")] + #[account(3, writable, name = "authority", desc = "Initial owner authority PDA")] + #[account(4, name = "system_program", desc = "System Program")] + #[account(5, name = "rent", desc = "Rent Sysvar")] + #[account(6, name = "config", desc = "Config PDA")] + #[account(7, writable, name = "treasury_shard", desc = "Treasury Shard PDA")] + CreateWallet { + user_seed: [u8; 32], + auth_type: u8, + auth_bump: u8, + padding: [u8; 6], + payload: Vec, + }, + + /// Add a new authority to the wallet + #[account(0, signer, writable, name = "payer", desc = "Transaction payer")] + #[account(1, name = "wallet", desc = "Wallet PDA")] + #[account( + 2, + name = "admin_authority", + desc = "Admin authority PDA authorizing this action" + )] + #[account( + 3, + writable, + name = "new_authority", + desc = "New authority PDA to be created" + )] + #[account(4, name = "system_program", desc = "System Program")] + #[account(5, name = "config", desc = "Config PDA")] + #[account(6, writable, name = "treasury_shard", desc = "Treasury Shard PDA")] + #[account( + 7, + signer, + optional, + name = "authorizer_signer", + desc = "Optional signer for Ed25519 authentication" + )] + AddAuthority { + new_type: u8, + new_role: u8, + padding: [u8; 6], + payload: Vec, + }, + + /// Remove an authority from the wallet + #[account(0, signer, writable, name = "payer", desc = "Transaction payer")] + #[account(1, name = "wallet", desc = "Wallet PDA")] + #[account( + 2, + writable, + name = "admin_authority", + desc = "Admin authority PDA authorizing this action" + )] + #[account( + 3, + writable, + name = "target_authority", + desc = "Authority PDA to be removed" + )] + #[account( + 4, + writable, + name = "refund_destination", + desc = "Account to receive rent refund" + )] + #[account(5, name = "system_program", desc = "System Program")] + #[account(6, name = "config", desc = "Config PDA")] + #[account(7, writable, name = "treasury_shard", desc = "Treasury Shard PDA")] + #[account( + 8, + signer, + optional, + name = "authorizer_signer", + desc = "Optional signer for Ed25519 authentication" + )] + RemoveAuthority, + + /// Transfer ownership (atomic swap of Owner role) + #[account(0, signer, writable, name = "payer", desc = "Transaction payer")] + #[account(1, name = "wallet", desc = "Wallet PDA")] + #[account( + 2, + writable, + name = "current_owner_authority", + desc = "Current owner authority PDA" + )] + #[account( + 3, + writable, + name = "new_owner_authority", + desc = "New owner authority PDA to be created" + )] + #[account(4, name = "system_program", desc = "System Program")] + #[account( + 5, + signer, + optional, + name = "authorizer_signer", + desc = "Optional signer for Ed25519 authentication" + )] + #[account(6, name = "config", desc = "Config PDA")] + #[account(7, writable, name = "treasury_shard", desc = "Treasury Shard PDA")] + TransferOwnership { + new_type: u8, + payload: Vec, + }, + + /// Execute transactions + #[account(0, signer, writable, name = "payer", desc = "Transaction payer")] + #[account(1, name = "wallet", desc = "Wallet PDA")] + #[account( + 2, + name = "authority", + desc = "Authority or Session PDA authorizing execution" + )] + #[account(3, writable, name = "vault", desc = "Vault PDA")] + #[account(4, name = "config", desc = "Config PDA")] + #[account(5, writable, name = "treasury_shard", desc = "Treasury Shard PDA")] + #[account(6, name = "system_program", desc = "System Program")] + #[account( + 7, + optional, + name = "sysvar_instructions", + desc = "Sysvar Instructions (required for Secp256r1)" + )] + Execute { instructions: Vec }, + + /// Create a new session key + #[account( + 0, + signer, + writable, + name = "payer", + desc = "Transaction payer and rent contributor" + )] + #[account(1, name = "wallet", desc = "Wallet PDA")] + #[account( + 2, + name = "admin_authority", + desc = "Admin/Owner authority PDA authorizing logic" + )] + #[account(3, writable, name = "session", desc = "New session PDA to be created")] + #[account(4, name = "system_program", desc = "System Program")] + #[account(5, name = "rent", desc = "Rent Sysvar")] + #[account(6, name = "config", desc = "Config PDA")] + #[account(7, writable, name = "treasury_shard", desc = "Treasury Shard PDA")] + #[account( + 8, + signer, + optional, + name = "authorizer_signer", + desc = "Optional signer for Ed25519 authentication" + )] + CreateSession { + session_key: [u8; 32], + expires_at: i64, + }, + + /// Initialize global Config PDA + #[account(0, signer, writable, name = "admin", desc = "Initial contract admin")] + #[account(1, writable, name = "config", desc = "Config PDA")] + #[account(2, name = "system_program", desc = "System Program")] + #[account(3, name = "rent", desc = "Rent Sysvar")] + InitializeConfig { + wallet_fee: u64, + action_fee: u64, + num_shards: u8, + }, + + /// Update global Config PDA + #[account(0, signer, name = "admin", desc = "Current contract admin")] + #[account(1, writable, name = "config", desc = "Config PDA")] + UpdateConfig, // args parsed raw + + /// Close an expired or active Session + #[account(0, signer, writable, name = "payer", desc = "Receives rent refund")] + #[account(1, name = "wallet", desc = "Session's parent wallet")] + #[account(2, writable, name = "session", desc = "Target session")] + #[account(3, name = "config", desc = "Config PDA for contract admin check")] + #[account(4, optional, name = "authorizer", desc = "Wallet authority PDA")] + #[account( + 5, + signer, + optional, + name = "authorizer_signer", + desc = "Ed25519 signer" + )] + #[account(6, optional, name = "sysvar_instructions", desc = "Secp256r1 sysvar")] + CloseSession, + + /// Drain and close a Wallet PDA (Owner-only) + #[account(0, signer, writable, name = "payer", desc = "Pays tx fee")] + #[account(1, writable, name = "wallet", desc = "Wallet PDA to close")] + #[account(2, writable, name = "vault", desc = "Vault PDA to drain")] + #[account(3, name = "owner_authority", desc = "Owner Authority PDA")] + #[account(4, writable, name = "destination", desc = "Receives all drained SOL")] + #[account(5, signer, optional, name = "owner_signer", desc = "Ed25519 signer")] + #[account(6, optional, name = "sysvar_instructions", desc = "Secp256r1 sysvar")] + CloseWallet, + + /// Sweep funds from a treasury shard + #[account(0, signer, name = "admin", desc = "Contract admin")] + #[account(1, name = "config", desc = "Config PDA")] + #[account(2, writable, name = "treasury_shard", desc = "Treasury Shard PDA")] + #[account(3, writable, name = "destination", desc = "Receives swept funds")] + SweepTreasury { shard_id: u8 }, + + /// Initialize a new treasury shard + #[account(0, signer, writable, name = "payer", desc = "Pays for rent exemption")] + #[account(1, name = "config", desc = "Config PDA")] + #[account(2, writable, name = "treasury_shard", desc = "Treasury Shard PDA")] + #[account(3, name = "system_program", desc = "System Program")] + #[account(4, name = "rent", desc = "Rent Sysvar")] + InitTreasuryShard { shard_id: u8 }, +} + +// --- 1. Account Structs --- + +#[derive(BorshSerialize, BorshDeserialize, ShankAccount)] +pub struct WalletAccount { + pub discriminator: u8, + pub bump: u8, + pub version: u8, + pub padding: [u8; 5], +} + +#[derive(BorshSerialize, BorshDeserialize, ShankAccount)] +pub struct AuthorityAccount { + pub discriminator: u8, + pub authority_type: u8, + pub role: u8, + pub bump: u8, + pub version: u8, + pub padding: [u8; 3], + pub counter: u64, + pub wallet: [u8; 32], +} + +#[derive(BorshSerialize, BorshDeserialize, ShankAccount)] +pub struct SessionAccount { + pub discriminator: u8, + pub bump: u8, + pub version: u8, + pub padding: [u8; 5], + pub wallet: [u8; 32], + pub session_key: [u8; 32], + pub expires_at: u64, +} + +// --- 2. Custom Types / Enums --- + +#[derive(BorshSerialize, BorshDeserialize, ShankType)] +pub enum AuthorityType { + Ed25519, + Secp256r1, +} + +#[derive(BorshSerialize, BorshDeserialize, ShankType)] +pub enum Role { + Owner, + Admin, + Spender, +} + +// --- 3. Custom Errors --- +use shank::ShankError; + +#[derive(thiserror::Error, Debug, Copy, Clone, ShankError)] +pub enum LazorKitError { + #[error("Invalid authority payload")] + InvalidAuthorityPayload = 3001, + #[error("Permission denied")] + PermissionDenied = 3002, + #[error("Invalid instruction")] + InvalidInstruction = 3003, + #[error("Invalid public key")] + InvalidPubkey = 3004, + #[error("Invalid message hash")] + InvalidMessageHash = 3005, + #[error("Signature has already been used")] + SignatureReused = 3006, + #[error("Invalid signature age")] + InvalidSignatureAge = 3007, + #[error("Invalid session duration")] + InvalidSessionDuration = 3008, + #[error("Session expired")] + SessionExpired = 3009, + #[error("Authority type does not support sessions")] + AuthorityDoesNotSupportSession = 3010, + #[error("Invalid authentication kind")] + InvalidAuthenticationKind = 3011, + #[error("Invalid message")] + InvalidMessage = 3012, + #[error("Self-reentrancy is not allowed")] + SelfReentrancyNotAllowed = 3013, +} diff --git a/program/src/instruction.rs b/program/src/instruction.rs new file mode 100644 index 0000000..d10ce67 --- /dev/null +++ b/program/src/instruction.rs @@ -0,0 +1,210 @@ +use pinocchio::program_error::ProgramError; + +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +pub enum LazorKitInstruction { + /// Create a new wallet + /// + /// Accounts: + /// 1. `[signer, writable]` Payer + /// 2. `[writable]` Wallet PDA + /// 3. `[writable]` Vault PDA + /// 4. `[writable]` Authority PDA + /// 5. `[]` System Program + CreateWallet { + user_seed: Vec, + auth_type: u8, + auth_pubkey: [u8; 33], + credential_hash: [u8; 32], + }, + + /// Add a new authority to the wallet + /// + /// Accounts: + /// 1. `[signer]` Payer + /// 2. `[]` Wallet PDA + /// 3. `[signer]` Admin Authority PDA (The one authorizing this action) + /// 4. `[writable]` New Authority PDA + /// 5. `[]` System Program + AddAuthority { + new_type: u8, + new_pubkey: [u8; 33], + new_hash: [u8; 32], + new_role: u8, + }, + + /// Remove an authority from the wallet + /// + /// Accounts: + /// 1. `[signer]` Payer + /// 2. `[]` Wallet PDA + /// 3. `[signer]` Admin Authority PDA + /// 4. `[writable]` Target Authority PDA + /// 5. `[writable]` Refund Destination + /// 6. `[]` System Program + RemoveAuthority, + + /// Transfer ownership (atomic swap of Owner role) + /// + /// Accounts: + /// 1. `[signer]` Payer + /// 2. `[]` Wallet PDA + /// 3. `[writable]` Current Owner Authority PDA + /// 4. `[writable]` New Owner Authority PDA + /// 5. `[]` System Program + TransferOwnership { + new_type: u8, + new_pubkey: [u8; 33], + new_hash: [u8; 32], + }, + + /// Execute transactions + /// + /// Accounts: + /// 1. `[signer]` Payer + /// 2. `[]` Wallet PDA + /// 3. `[]` Authority PDA + /// 4. `[signer]` Vault PDA + /// 5. `[]` Sysvar Instructions (if Secp256r1) + /// ... Inner accounts + Execute { + instructions: Vec, // CompactInstructions bytes, we'll parse later + }, + + /// Create a new session key + /// + /// Accounts: + /// 1. `[signer]` Payer + /// 2. `[]` Wallet PDA + /// 3. `[signer]` Authority PDA (Authorizer) + /// 4. `[writable]` Session PDA + /// 5. `[]` System Program + CreateSession { + session_key: [u8; 32], + expires_at: u64, + }, + + InitializeConfig { + wallet_fee: u64, + action_fee: u64, + num_shards: u8, + }, + UpdateConfig, + CloseSession, + CloseWallet, + SweepTreasury { + shard_id: u8, + }, + InitTreasuryShard { + shard_id: u8, + }, +} + +impl LazorKitInstruction { + pub fn unpack(input: &[u8]) -> Result { + let (&tag, rest) = input + .split_first() + .ok_or(ProgramError::InvalidInstructionData)?; + + match tag { + 0 => { + // CreateWallet + // Format: [user_seed_len(4)][user_seed][auth_type(1)][auth_pubkey(33)][credential_hash(32)] + + if rest.len() < 4 { + return Err(ProgramError::InvalidInstructionData); + } + let (len_bytes, rest) = rest.split_at(4); + let seed_len = u32::from_le_bytes(len_bytes.try_into().unwrap()) as usize; + + if rest.len() < seed_len + 1 + 33 + 32 { + return Err(ProgramError::InvalidInstructionData); + } + let (user_seed, rest) = rest.split_at(seed_len); + let (&auth_type, rest) = rest.split_first().unwrap(); + let (auth_pubkey, rest) = rest.split_at(33); + let (credential_hash, _) = rest.split_at(32); + + Ok(Self::CreateWallet { + user_seed: user_seed.to_vec(), + auth_type, + auth_pubkey: auth_pubkey.try_into().unwrap(), + credential_hash: credential_hash.try_into().unwrap(), + }) + }, + 1 => { + // AddAuthority + // Format: [new_type(1)][new_pubkey(33)][new_hash(32)][new_role(1)] + if rest.len() < 1 + 33 + 32 + 1 { + return Err(ProgramError::InvalidInstructionData); + } + let (&new_type, rest) = rest.split_first().unwrap(); + let (new_pubkey, rest) = rest.split_at(33); + let (new_hash, rest) = rest.split_at(32); + let (&new_role, _) = rest.split_first().unwrap(); + + Ok(Self::AddAuthority { + new_type, + new_pubkey: new_pubkey.try_into().unwrap(), + new_hash: new_hash.try_into().unwrap(), + new_role, + }) + }, + 2 => Ok(Self::RemoveAuthority), + 3 => { + // Format: [new_type(1)][new_pubkey(33)][new_hash(32)] + if rest.len() < 1 + 33 + 32 { + return Err(ProgramError::InvalidInstructionData); + } + let (&new_type, rest) = rest.split_first().unwrap(); + let (new_pubkey, rest) = rest.split_at(33); + let (new_hash, _) = rest.split_at(32); + + Ok(Self::TransferOwnership { + new_type, + new_pubkey: new_pubkey.try_into().unwrap(), + new_hash: new_hash.try_into().unwrap(), + }) + }, + 4 => { + // Execute + // Remaining bytes are compact instructions + Ok(Self::Execute { + instructions: rest.to_vec(), + }) + }, + 5 => { + // CreateSession + // Format: [session_key(32)][expires_at(8)] + if rest.len() < 32 + 8 { + return Err(ProgramError::InvalidInstructionData); + } + let (session_key, rest) = rest.split_at(32); + let (expires_at_bytes, _) = rest.split_at(8); + let expires_at = u64::from_le_bytes(expires_at_bytes.try_into().unwrap()); + + Ok(Self::CreateSession { + session_key: session_key.try_into().unwrap(), + expires_at, + }) + }, + 6 => Ok(Self::InitializeConfig { + wallet_fee: 0, + action_fee: 0, + num_shards: 16, + }), // Dummy unpack, actual args parsed in processor + 7 => Ok(Self::UpdateConfig), + 8 => Ok(Self::CloseSession), + 9 => Ok(Self::CloseWallet), + 10 => { + let (&shard_id, _) = rest.split_first().unwrap(); + Ok(Self::SweepTreasury { shard_id }) + }, + 11 => { + let (&shard_id, _) = rest.split_first().unwrap(); + Ok(Self::InitTreasuryShard { shard_id }) + }, + _ => Err(ProgramError::InvalidInstructionData), + } + } +} diff --git a/program/src/lib.rs b/program/src/lib.rs new file mode 100644 index 0000000..ed56f7e --- /dev/null +++ b/program/src/lib.rs @@ -0,0 +1,11 @@ +#![allow(unexpected_cfgs)] + +pub mod auth; +pub mod compact; +pub mod entrypoint; +pub mod error; +// pub mod idl_facade; +pub mod instruction; +pub mod processor; +pub mod state; +pub mod utils; diff --git a/program/src/processor/close_session.rs b/program/src/processor/close_session.rs new file mode 100644 index 0000000..386aa7b --- /dev/null +++ b/program/src/processor/close_session.rs @@ -0,0 +1,194 @@ +use assertions::sol_assert_bytes_eq; +use pinocchio::{ + account_info::AccountInfo, + program_error::ProgramError, + pubkey::{find_program_address, Pubkey}, + sysvars::{clock::Clock, Sysvar}, + ProgramResult, +}; + +use crate::{ + auth::{ + ed25519::Ed25519Authenticator, secp256r1::Secp256r1Authenticator, traits::Authenticator, + }, + error::AuthError, + state::{ + authority::AuthorityAccountHeader, config::ConfigAccount, session::SessionAccount, + AccountDiscriminator, + }, +}; + +/// Closes a session account and refunds the rent to the caller. +/// +/// Authentication rules: +/// - Contract Admin: Can close ONLY expired sessions. +/// - Wallet Admin/Owner: Can close both active AND expired sessions. +/// - Anyone else: Rejected. +/// +/// Accounts: +/// 0. `[signer, writable]` Payer (receives refund) +/// 1. `[]` Wallet PDA +/// 2. `[writable]` Session PDA +/// 3. `[]` Config PDA +/// 4. `[optional]` Authorizer PDA (if wallet admin/owner) +/// 5. `[optional, signer]` Authorizer Signer (Ed25519) +/// 6. `[optional]` Sysvar Instructions (Secp256r1) +pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + // Issue: Passkey signatures require authority_payload. + let authority_payload = instruction_data; + + let account_info_iter = &mut accounts.iter(); + let payer = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let wallet_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let session_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + let (config_key, _config_bump) = find_program_address(&[b"config"], program_id); + if !sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + + let config_data = unsafe { config_pda.borrow_data_unchecked() }; + if config_data.len() < std::mem::size_of::() { + return Err(ProgramError::UninitializedAccount); + } + let config = unsafe { std::ptr::read_unaligned(config_data.as_ptr() as *const ConfigAccount) }; + + // 1. Validate Session PDA + if session_pda.owner() != program_id { + return Err(ProgramError::IllegalOwner); + } + let session_data = unsafe { session_pda.borrow_mut_data_unchecked() }; + if session_data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + // Safe alignment check isn't strictly necessary with byte copy or if we assume layout + let session = + unsafe { std::ptr::read_unaligned(session_data.as_ptr() as *const SessionAccount) }; + + if session.discriminator != AccountDiscriminator::Session as u8 { + return Err(ProgramError::InvalidAccountData); + } + if session.wallet != *wallet_pda.key() { + return Err(ProgramError::InvalidArgument); + } + // Re-derive to be absolutely sure + let (derived_session_key, _bump) = find_program_address( + &[ + b"session", + wallet_pda.key().as_ref(), + session.session_key.as_ref(), + ], + program_id, + ); + if !sol_assert_bytes_eq(session_pda.key().as_ref(), derived_session_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + + // 3. Check expiration + let current_slot = Clock::get()?.slot; + let is_expired = current_slot > session.expires_at; + + // 4. Authorization + let mut is_authorized = false; + + // Is the caller the contract admin? + if *payer.key() == config.admin && is_expired { + is_authorized = true; + } + + // Is there an authorizer PDA provided? + if !is_authorized { + let auth_pda = account_info_iter.next(); + if let Some(auth_pda) = auth_pda { + // Verify authority belongs to the wallet + if auth_pda.owner() != program_id { + return Err(ProgramError::IllegalOwner); + } + let auth_data = unsafe { auth_pda.borrow_mut_data_unchecked() }; + if auth_data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + let auth_header = unsafe { + std::ptr::read_unaligned(auth_data.as_ptr() as *const AuthorityAccountHeader) + }; + if auth_header.discriminator != AccountDiscriminator::Authority as u8 { + return Err(ProgramError::InvalidAccountData); + } + if auth_header.wallet != *wallet_pda.key() { + return Err(ProgramError::InvalidArgument); + } + if auth_header.role > 1 { + // Must be Owner (0) or Admin (1) + return Err(AuthError::PermissionDenied.into()); + } + + // Authenticate the authority via signatures + // Binding payload to the session PDA to prevent replay swap attacks + let mut payload = Vec::with_capacity(32); + payload.extend_from_slice(session_pda.key().as_ref()); + + if auth_header.authority_type == 0 { + // Ed25519 + Ed25519Authenticator.authenticate( + program_id, + accounts, + auth_data, + &[], + &payload, + &[8], + )?; + } else if auth_header.authority_type == 1 { + // Secp256r1 + Secp256r1Authenticator.authenticate( + program_id, + accounts, + auth_data, + authority_payload, + &payload, + &[8], + )?; + } else { + return Err(AuthError::InvalidAuthenticationKind.into()); + } + + is_authorized = true; + } + } + + if !is_authorized { + return Err(AuthError::PermissionDenied.into()); + } + + // 5. Transfer session lamports to the payer + let session_lamports = session_pda.lamports(); + let payer_lamports = payer.lamports(); + + unsafe { + *payer.borrow_mut_lamports_unchecked() = payer_lamports + .checked_add(session_lamports) + .ok_or(ProgramError::ArithmeticOverflow)?; + *session_pda.borrow_mut_lamports_unchecked() = 0; + } + + // 6. Zero out the session data + session_data.fill(0); + + Ok(()) +} diff --git a/program/src/processor/close_wallet.rs b/program/src/processor/close_wallet.rs new file mode 100644 index 0000000..4beece9 --- /dev/null +++ b/program/src/processor/close_wallet.rs @@ -0,0 +1,197 @@ +use assertions::sol_assert_bytes_eq; +use pinocchio::{ + account_info::AccountInfo, + program_error::ProgramError, + pubkey::{find_program_address, Pubkey}, + ProgramResult, +}; + +use crate::{ + auth::{ + ed25519::Ed25519Authenticator, secp256r1::Secp256r1Authenticator, traits::Authenticator, + }, + error::AuthError, + state::{authority::AuthorityAccountHeader, wallet::WalletAccount, AccountDiscriminator}, +}; + +/// Closes the Wallet and Vault PDAs, sending all remaining lamports to a designated destination. +/// +/// This is a highly destructive action and can ONLY be performed by the Owner (Role 0). +/// Note: Any remaining Authority/Session PDAs will be orphaned on-chain. +/// +/// Accounts: +/// 0. `[signer]` Payer (pays transaction fee) +/// 1. `[writable]` Wallet PDA (to close) +/// 2. `[writable]` Vault PDA (to drain) +/// 3. `[]` Owner Authority PDA (must be role == 0) +/// 4. `[writable]` Destination account (receives all drained lamports) +/// 5. `[optional, signer]` Owner Signer (Ed25519) +/// 6. `[optional]` Sysvar Instructions (Secp256r1) +pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + // instruction_data only used for Secp256r1 auth_payload (when owner is Secp256r1) + let account_info_iter = &mut accounts.iter(); + let payer = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let wallet_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let vault_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let owner_auth_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let destination = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + if !payer.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + if destination.key() == vault_pda.key() || destination.key() == wallet_pda.key() { + return Err(ProgramError::InvalidArgument); + } + + if wallet_pda.owner() != program_id { + return Err(ProgramError::IllegalOwner); + } + let wallet_data = unsafe { wallet_pda.borrow_mut_data_unchecked() }; + if wallet_data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + let wallet_info = + unsafe { std::ptr::read_unaligned(wallet_data.as_ptr() as *const WalletAccount) }; + if wallet_info.discriminator != AccountDiscriminator::Wallet as u8 { + return Err(ProgramError::InvalidAccountData); + } + + // 2. Validate Vault PDA + let (derived_vault_key, _vault_bump) = + find_program_address(&[b"vault", wallet_pda.key().as_ref()], program_id); + if !sol_assert_bytes_eq(vault_pda.key().as_ref(), derived_vault_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + + // 3. Validate Owner Authority PDA + if owner_auth_pda.owner() != program_id { + return Err(ProgramError::IllegalOwner); + } + let auth_data = unsafe { owner_auth_pda.borrow_mut_data_unchecked() }; + if auth_data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + let auth_header = + unsafe { std::ptr::read_unaligned(auth_data.as_ptr() as *const AuthorityAccountHeader) }; + if auth_header.discriminator != AccountDiscriminator::Authority as u8 { + return Err(ProgramError::InvalidAccountData); + } + if auth_header.wallet != *wallet_pda.key() { + return Err(ProgramError::InvalidArgument); + } + if auth_header.role != 0 { + return Err(AuthError::PermissionDenied.into()); // MUST be Owner + } + + // 4. Authenticate the Owner + // Bind payload to the Destination address to prevent attackers from swapping the destination + let mut payload = Vec::with_capacity(32); + payload.extend_from_slice(destination.key().as_ref()); + + if auth_header.authority_type == 0 { + // Ed25519 + Ed25519Authenticator.authenticate(program_id, accounts, auth_data, &[], &payload, &[9])?; + } else if auth_header.authority_type == 1 { + // Secp256r1: instruction_data contains auth_payload with slot, sysvar indices, rp_id, authenticator_data + if instruction_data.is_empty() { + return Err(AuthError::InvalidAuthorityPayload.into()); + } + Secp256r1Authenticator.authenticate( + program_id, + accounts, + auth_data, + instruction_data, + &payload, + &[9], + )?; + } else { + return Err(AuthError::InvalidAuthenticationKind.into()); + } + + // 5. Drain Vault PDA to Destination + let vault_lamports = vault_pda.lamports(); + if vault_lamports > 0 { + // Must use CPI to transfer because the Vault is owned by SystemProgram + let mut system_program = None; + for acc in accounts { + if acc.key().as_ref() == &[0; 32] { + system_program = Some(acc); + break; + } + } + let sys_prog = system_program.ok_or(ProgramError::NotEnoughAccountKeys)?; + + // Create instruction + let mut ix_data = [0u8; 12]; + ix_data[0..4].copy_from_slice(&2u32.to_le_bytes()); // Transfer instruction index + ix_data[4..12].copy_from_slice(&vault_lamports.to_le_bytes()); + + let account_metas = [ + pinocchio::instruction::AccountMeta { + pubkey: vault_pda.key(), + is_signer: true, + is_writable: true, + }, + pinocchio::instruction::AccountMeta { + pubkey: destination.key(), + is_signer: false, + is_writable: true, + }, + ]; + + let ix = pinocchio::instruction::Instruction { + program_id: sys_prog.key(), + accounts: &account_metas, + data: &ix_data, + }; + + let vault_bump_pda = + find_program_address(&[b"vault", wallet_pda.key().as_ref()], program_id).1; + let vault_bump_arr = [vault_bump_pda]; + let seeds = [ + pinocchio::instruction::Seed::from(b"vault"), + pinocchio::instruction::Seed::from(wallet_pda.key().as_ref()), + pinocchio::instruction::Seed::from(&vault_bump_arr), + ]; + let signer: pinocchio::instruction::Signer = (&seeds).into(); + + let cpi_accounts = [ + pinocchio::instruction::Account::from(vault_pda), + pinocchio::instruction::Account::from(destination), + ]; + + unsafe { + pinocchio::program::invoke_signed_unchecked(&ix, &cpi_accounts, &[signer]); + } + } + + // 6. Drain Wallet PDA to Destination + let wallet_lamports = wallet_pda.lamports(); + // Re-read dest lamports since we just updated it + let current_dest_lamports = destination.lamports(); + + unsafe { + *destination.borrow_mut_lamports_unchecked() = current_dest_lamports + .checked_add(wallet_lamports) + .ok_or(ProgramError::ArithmeticOverflow)?; + *wallet_pda.borrow_mut_lamports_unchecked() = 0; + } + wallet_data.fill(0); + + Ok(()) +} diff --git a/program/src/processor/create_session.rs b/program/src/processor/create_session.rs new file mode 100644 index 0000000..c1c9d69 --- /dev/null +++ b/program/src/processor/create_session.rs @@ -0,0 +1,307 @@ +use assertions::{check_zero_data, sol_assert_bytes_eq}; +use no_padding::NoPadding; +use pinocchio::{ + account_info::AccountInfo, + instruction::Seed, + program_error::ProgramError, + pubkey::{find_program_address, Pubkey}, + sysvars::rent::Rent, + ProgramResult, +}; + +use crate::{ + auth::{ + ed25519::Ed25519Authenticator, secp256r1::Secp256r1Authenticator, traits::Authenticator, + }, + error::AuthError, + state::{authority::AuthorityAccountHeader, session::SessionAccount, AccountDiscriminator}, +}; + +/// Arguments for the `CreateSession` instruction. +/// +/// Layout: +/// - `session_key`: The public key of the ephemeral session signer. +/// - `expires_at`: The absolute slot height when this session expires. +#[repr(C, align(8))] +#[derive(NoPadding)] +pub struct CreateSessionArgs { + pub session_key: [u8; 32], + pub expires_at: u64, +} + +impl CreateSessionArgs { + pub fn from_bytes(data: &[u8]) -> Result { + if data.len() < 40 { + return Err(ProgramError::InvalidInstructionData); + } + // args are: [session_key(32)][expires_at(8)] + let (key_bytes, rest) = data.split_at(32); + let (alloc_bytes, _) = rest.split_at(8); + + let mut session_key = [0u8; 32]; + session_key.copy_from_slice(key_bytes); + + let expires_at = u64::from_le_bytes(alloc_bytes.try_into().unwrap()); + + Ok(Self { + session_key, + expires_at, + }) + } +} + +/// Processes the `CreateSession` instruction. +/// +/// Creates a temporary `Session` account that facilitates limited-scope execution (Spender role). +/// +/// # Logic: +/// 1. Verifies the authorizing authority (must be Owner or Admin). +/// 2. Derives a fresh Session PDA from `["session", wallet, session_key]`. +/// 3. Allocates and initializes the Session account with expiry. +/// +/// # Accounts: +/// 1. `[signer, writable]` Payer: Pays for rent. +/// 2. `[]` Wallet PDA. +/// 3. `[signer, writable]` Authorizer: Authority approving this session creation. +/// 4. `[writable]` Session PDA: The new session account. +/// 5. `[]` System Program. +/// 6. `[]` Rent Sysvar. +pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let args = CreateSessionArgs::from_bytes(instruction_data)?; + + let account_info_iter = &mut accounts.iter(); + let payer = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let wallet_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let authorizer_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let session_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let system_program = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let rent_sysvar = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + // Get rent from sysvar (fixes audit issue #5 - hardcoded rent calculations) + let rent = Rent::from_account_info(rent_sysvar)?; + + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let treasury_shard = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + // Parse Config and Charge Fee + let (config_key, _config_bump) = find_program_address(&[b"config"], program_id); + if !assertions::sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + let config_data = unsafe { config_pda.borrow_data_unchecked() }; + if config_data.len() < std::mem::size_of::() { + return Err(ProgramError::UninitializedAccount); + } + let config = unsafe { + std::ptr::read_unaligned(config_data.as_ptr() as *const crate::state::config::ConfigAccount) + }; + + crate::utils::collect_protocol_fee( + program_id, + payer, + &config, + treasury_shard, + system_program, + false, // not a wallet creation + )?; + + // Validate system_program is the correct System Program (audit N2) + if !assertions::sol_assert_bytes_eq( + system_program.key().as_ref(), + &crate::utils::SYSTEM_PROGRAM_ID, + 32, + ) { + return Err(ProgramError::IncorrectProgramId); + } + + if wallet_pda.owner() != program_id || authorizer_pda.owner() != program_id { + return Err(ProgramError::IllegalOwner); + } + + // Validate Wallet Discriminator (Issue #7) + let wallet_data = unsafe { wallet_pda.borrow_data_unchecked() }; + if wallet_data.is_empty() || wallet_data[0] != AccountDiscriminator::Wallet as u8 { + return Err(ProgramError::InvalidAccountData); + } + + // Verify Authorizer + // Check removed: conditional writable check inside match + + let auth_data = unsafe { authorizer_pda.borrow_mut_data_unchecked() }; + + // Safe Copy of Header using read_unaligned + if auth_data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + let auth_header = + unsafe { std::ptr::read_unaligned(auth_data.as_ptr() as *const AuthorityAccountHeader) }; + + if auth_header.discriminator != AccountDiscriminator::Authority as u8 { + return Err(ProgramError::InvalidAccountData); + } + if auth_header.wallet != *wallet_pda.key() { + return Err(ProgramError::InvalidAccountData); + } + // Only Admin (1) or Owner (0) can create sessions. + // Spender (2) cannot create sessions. + if auth_header.role != 0 && auth_header.role != 1 { + return Err(AuthError::PermissionDenied.into()); + } + + // Authenticate Authorizer + + // We assume CreateSession instruction data AFTER the args is payload for Secp256r1 if any + let payload_offset = std::mem::size_of::(); + let authority_payload = if instruction_data.len() > payload_offset { + &instruction_data[payload_offset..] + } else { + &[] + }; + + // But wait, `CreateSessionArgs` consumes 40 bytes. + // `instruction_data` passed here is whatever follows the discriminator. + // `Execute` passes compact instructions. + // Here we pass args. + + // For Secp256r1, we need to distinguish args from auth payload. + // The instruction format is [discriminator][args][payload]. + // `instruction_data` here is [args][payload]. + let data_payload = &instruction_data[..payload_offset]; + + // Include payer + wallet to prevent payer swap and cross-wallet replay + let mut ed25519_payload = Vec::with_capacity(96); + ed25519_payload.extend_from_slice(payer.key().as_ref()); + ed25519_payload.extend_from_slice(&args.session_key); + ed25519_payload.extend_from_slice(wallet_pda.key().as_ref()); + + match auth_header.authority_type { + 0 => { + // Ed25519: Include payer + session_key in signed payload + Ed25519Authenticator.authenticate( + program_id, + accounts, + auth_data, + &[], + &ed25519_payload, + &[5], + )?; + }, + 1 => { + // Secp256r1: Include payer + wallet in data_payload + let mut extended_data_payload = Vec::with_capacity(data_payload.len() + 64); + extended_data_payload.extend_from_slice(data_payload); + extended_data_payload.extend_from_slice(payer.key().as_ref()); + extended_data_payload.extend_from_slice(wallet_pda.key().as_ref()); + + Secp256r1Authenticator.authenticate( + program_id, + accounts, + auth_data, + authority_payload, + &extended_data_payload, + &[5], + )?; + }, + _ => return Err(AuthError::InvalidAuthenticationKind.into()), + } + + // Derive Session PDA + let (session_key, bump) = find_program_address( + &[b"session", wallet_pda.key().as_ref(), &args.session_key], + program_id, + ); + if !sol_assert_bytes_eq(session_pda.key().as_ref(), session_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + check_zero_data(session_pda, ProgramError::AccountAlreadyInitialized)?; + + // Create Session Account + let space = std::mem::size_of::(); + let session_rent = rent.minimum_balance(space); + + let bump_arr = [bump]; + let seeds = [ + Seed::from(b"session"), + Seed::from(wallet_pda.key().as_ref()), + Seed::from(&args.session_key), + Seed::from(&bump_arr), + ]; + + crate::utils::initialize_pda_account( + payer, + session_pda, + system_program, + space, + session_rent, + program_id, + &seeds, + )?; + + // Initialize Session State + let data = unsafe { session_pda.borrow_mut_data_unchecked() }; + let session = SessionAccount { + discriminator: AccountDiscriminator::Session as u8, + bump, + version: crate::state::CURRENT_ACCOUNT_VERSION, + _padding: [0; 5], + wallet: *wallet_pda.key(), + session_key: Pubkey::from(args.session_key), + expires_at: args.expires_at, + }; + + // Safe write + let session_bytes = unsafe { + std::slice::from_raw_parts( + &session as *const SessionAccount as *const u8, + std::mem::size_of::(), + ) + }; + data[0..std::mem::size_of::()].copy_from_slice(session_bytes); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_session_args_from_bytes() { + let mut data = Vec::new(); + // session_key(32) + expires_at(8) + let session_key = [7u8; 32]; + let expires_at = 12345678u64; + data.extend_from_slice(&session_key); + data.extend_from_slice(&expires_at.to_le_bytes()); + + let args = CreateSessionArgs::from_bytes(&data).unwrap(); + assert_eq!(args.session_key, session_key); + assert_eq!(args.expires_at, expires_at); + } + + #[test] + fn test_create_session_args_too_short() { + let data = vec![0u8; 39]; // Need 40 + assert!(CreateSessionArgs::from_bytes(&data).is_err()); + } +} diff --git a/program/src/processor/create_wallet.rs b/program/src/processor/create_wallet.rs new file mode 100644 index 0000000..91839ef --- /dev/null +++ b/program/src/processor/create_wallet.rs @@ -0,0 +1,368 @@ +use assertions::{check_zero_data, sol_assert_bytes_eq}; +use no_padding::NoPadding; +use pinocchio::{ + account_info::AccountInfo, + instruction::Seed, + program_error::ProgramError, + pubkey::{find_program_address, Pubkey}, + sysvars::rent::Rent, + ProgramResult, +}; + +use crate::{ + error::AuthError, + state::{authority::AuthorityAccountHeader, wallet::WalletAccount, AccountDiscriminator}, +}; + +/// Arguments for the `CreateWallet` instruction. +/// +/// Layout: +/// - `user_seed`: 32-byte seed for deterministic wallet derivation. +/// - `authority_type`: 0 for Ed25519, 1 for Secp256r1. +/// - `auth_bump`: Bump seed for the authority PDA (optional/informational). +/// - `_padding`: Reserved for alignment (ensure total size is multiple of 8). +#[repr(C, align(8))] +#[derive(NoPadding)] +pub struct CreateWalletArgs { + pub user_seed: [u8; 32], + pub authority_type: u8, + pub auth_bump: u8, + pub _padding: [u8; 6], // 32+1+1+6 = 40 bytes +} + +impl CreateWalletArgs { + pub fn from_bytes(data: &[u8]) -> Result<(Self, &[u8]), ProgramError> { + if data.len() < 40 { + return Err(ProgramError::InvalidInstructionData); + } + let (fixed, rest) = data.split_at(40); + + // Safe copy to ensure alignment + let mut user_seed = [0u8; 32]; + user_seed.copy_from_slice(&fixed[0..32]); + + let authority_type = fixed[32]; + let auth_bump = fixed[33]; + // skip 6 padding bytes + + let args = Self { + user_seed, + authority_type, + auth_bump, + _padding: [0; 6], + }; + + Ok((args, rest)) + } +} + +/// Processes the `CreateWallet` instruction. +/// +/// This instruction initializes: +/// 1. A `Wallet` PDA: The central identity. +/// 2. A `Vault` PDA: To hold assets (signer). +/// 3. An `Authority` PDA: The initial owner (Admin/Owner role). +/// +/// # Accounts: +/// 1. `[signer, writable]` Payer: Pays for account creation. +/// 2. `[writable]` Wallet PDA: Derived from `["wallet", user_seed]`. +/// 3. `[writable]` Vault PDA: Derived from `["vault", wallet_pubkey]`. +/// 4. `[writable]` Authority PDA: Derived from `["authority", wallet_pubkey, id_seed]`. +/// 5. `[]` System Program. +/// 6. `[]` Rent Sysvar. +pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let (args, rest) = CreateWalletArgs::from_bytes(instruction_data)?; + + let (id_seed, full_auth_data) = match args.authority_type { + 0 => { + if rest.len() != 32 { + return Err(ProgramError::InvalidInstructionData); + } + (rest, rest) + }, + 1 => { + // [credential_id_hash(32)] [pubkey(33)] = 65 bytes total + if rest.len() < 65 { + return Err(ProgramError::InvalidInstructionData); + } + let (credential_id_hash, _rest_after_cred) = rest.split_at(32); + // We store credential_id_hash + pubkey for on-chain wallet discovery + let full_auth_data = &rest[..65]; + (credential_id_hash, full_auth_data) + }, + _ => return Err(AuthError::InvalidAuthenticationKind.into()), + }; + + let account_info_iter = &mut accounts.iter(); + let payer = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let wallet_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let vault_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let auth_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let system_program = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let rent_sysvar = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let treasury_shard = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + // Get rent from sysvar (fixes audit issue #5 - hardcoded rent calculations) + let rent = Rent::from_account_info(rent_sysvar)?; + + // Parse and validate Config PDA + let (config_key, _config_bump) = find_program_address(&[b"config"], program_id); + if !sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + let config_data = unsafe { config_pda.borrow_data_unchecked() }; + if config_data.len() < std::mem::size_of::() { + return Err(ProgramError::UninitializedAccount); + } + let config_account = unsafe { + std::ptr::read_unaligned(config_data.as_ptr() as *const crate::state::config::ConfigAccount) + }; + + // Collect protocol fee + crate::utils::collect_protocol_fee( + program_id, + payer, + &config_account, + treasury_shard, + system_program, + true, // is_wallet_creation = true + )?; + + let (wallet_key, wallet_bump) = find_program_address(&[b"wallet", &args.user_seed], program_id); + if !sol_assert_bytes_eq(wallet_pda.key().as_ref(), wallet_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + check_zero_data(wallet_pda, ProgramError::AccountAlreadyInitialized)?; + + // Derive Vault PDA + let (vault_key, vault_bump) = + find_program_address(&[b"vault", wallet_key.as_ref()], program_id); + if !sol_assert_bytes_eq(vault_pda.key().as_ref(), vault_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + check_zero_data(vault_pda, ProgramError::AccountAlreadyInitialized)?; + + // Derive canonical authority PDA and verify user-provided bump matches (audit N1) + // Must use find_program_address to ensure canonical bump - user-supplied bump + // could create a valid but non-canonical PDA + let (auth_key, auth_bump) = + find_program_address(&[b"authority", wallet_key.as_ref(), id_seed], program_id); + if !sol_assert_bytes_eq(auth_pda.key().as_ref(), auth_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + check_zero_data(auth_pda, ProgramError::AccountAlreadyInitialized)?; + + // --- 1. Initialize Wallet Account --- + let wallet_space = 8; + let wallet_rent = rent.minimum_balance(wallet_space); + let wallet_bump_arr = [wallet_bump]; + let wallet_seeds = [ + Seed::from(b"wallet"), + Seed::from(&args.user_seed), + Seed::from(&wallet_bump_arr), + ]; + + crate::utils::initialize_pda_account( + payer, + wallet_pda, + system_program, + wallet_space, + wallet_rent, + program_id, + &wallet_seeds, + )?; + + // --- 2. Initialize Authority Account --- + let header_size = std::mem::size_of::(); + let variable_size = full_auth_data.len(); + let auth_space = header_size + variable_size; + let auth_rent = rent.minimum_balance(auth_space); + let auth_bump_arr = [auth_bump]; + let auth_seeds = [ + Seed::from(b"authority"), + Seed::from(wallet_key.as_ref()), + Seed::from(id_seed), + Seed::from(&auth_bump_arr), + ]; + + crate::utils::initialize_pda_account( + payer, + auth_pda, + system_program, + auth_space, + auth_rent, + program_id, + &auth_seeds, + )?; + + // --- 3. Prep Vault PDA --- + // Vault accounts are owned by SystemProgram (to support standard transfers). + // We fund it to rent-exemption for 0 bytes and allocate(0) to mark it as initialized. + let vault_rent = rent.minimum_balance(0); + let current_vault_balance = vault_pda.lamports(); + if current_vault_balance < vault_rent { + let transfer_amount = vault_rent + .checked_sub(current_vault_balance) + .ok_or(ProgramError::ArithmeticOverflow)?; + let mut transfer_data = [0u8; 12]; + transfer_data[0..4].copy_from_slice(&2u32.to_le_bytes()); + transfer_data[4..12].copy_from_slice(&transfer_amount.to_le_bytes()); + let transfer_accounts = [ + pinocchio::instruction::AccountMeta { + pubkey: payer.key(), + is_signer: true, + is_writable: true, + }, + pinocchio::instruction::AccountMeta { + pubkey: vault_pda.key(), + is_signer: false, + is_writable: true, + }, + ]; + let transfer_ix = pinocchio::instruction::Instruction { + program_id: &Pubkey::from(crate::utils::SYSTEM_PROGRAM_ID), + accounts: &transfer_accounts, + data: &transfer_data, + }; + pinocchio::program::invoke(&transfer_ix, &[&payer, &vault_pda, &system_program])?; + } + + // Allocate 0 bytes to mark as initialized (owned by system program) + let mut allocate_data = [0u8; 12]; + allocate_data[0..4].copy_from_slice(&8u32.to_le_bytes()); + allocate_data[4..12].copy_from_slice(&0u64.to_le_bytes()); + let allocate_accounts = [pinocchio::instruction::AccountMeta { + pubkey: vault_pda.key(), + is_signer: true, + is_writable: true, + }]; + let allocate_ix = pinocchio::instruction::Instruction { + program_id: &Pubkey::from(crate::utils::SYSTEM_PROGRAM_ID), + accounts: &allocate_accounts, + data: &allocate_data, + }; + let vault_bump_arr = [vault_bump]; + let vault_seeds = [ + Seed::from(b"vault"), + Seed::from(wallet_key.as_ref()), + Seed::from(&vault_bump_arr), + ]; + let signer: pinocchio::instruction::Signer = (&vault_seeds).into(); + pinocchio::program::invoke_signed(&allocate_ix, &[&vault_pda, &system_program], &[signer])?; + + // --- 4. Write Data --- + let wallet_data = unsafe { wallet_pda.borrow_mut_data_unchecked() }; + let wallet_account = WalletAccount { + discriminator: AccountDiscriminator::Wallet as u8, + bump: wallet_bump, + version: crate::state::CURRENT_ACCOUNT_VERSION, + _padding: [0; 5], + }; + unsafe { + std::ptr::write_unaligned( + wallet_data.as_mut_ptr() as *mut WalletAccount, + wallet_account, + ); + } + + let auth_account_data = unsafe { auth_pda.borrow_mut_data_unchecked() }; + let header = AuthorityAccountHeader { + discriminator: AccountDiscriminator::Authority as u8, + authority_type: args.authority_type, + role: 0, + bump: auth_bump, + version: crate::state::CURRENT_ACCOUNT_VERSION, + _padding: [0; 3], + counter: 0, + wallet: *wallet_pda.key(), + }; + + let header_bytes = unsafe { + std::slice::from_raw_parts( + &header as *const AuthorityAccountHeader as *const u8, + std::mem::size_of::(), + ) + }; + auth_account_data[0..header_size].copy_from_slice(header_bytes); + auth_account_data[header_size..header_size + variable_size].copy_from_slice(full_auth_data); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_wallet_args_from_bytes_ed25519() { + let mut data = Vec::new(); + // Args: user_seed(32) + type(1) + bump(1) + padding(6) = 40 + let user_seed = [1u8; 32]; + data.extend_from_slice(&user_seed); + data.push(0); // Ed25519 + data.push(255); // bump + data.extend_from_slice(&[0; 6]); // padding + + // Payload for Ed25519: pubkey(32) + let pubkey = [2u8; 32]; + data.extend_from_slice(&pubkey); + + let (args, rest) = CreateWalletArgs::from_bytes(&data).unwrap(); + assert_eq!(args.user_seed, user_seed); + assert_eq!(args.authority_type, 0); + assert_eq!(args.auth_bump, 255); + assert_eq!(rest, &pubkey); + } + + #[test] + fn test_create_wallet_args_from_bytes_secp256r1() { + let mut data = Vec::new(); + let user_seed = [3u8; 32]; + data.extend_from_slice(&user_seed); + data.push(1); // authority_type = Secp256r1 + data.push(123); // bump + data.extend_from_slice(&[0; 6]); // padding + + // Payload for Secp256r1: credential_id_hash(32) + pubkey(33) + let cred_id_hash = [4u8; 32]; + let pubkey = [6u8; 33]; + data.extend_from_slice(&cred_id_hash); + data.extend_from_slice(&pubkey); + + let (args, rest) = CreateWalletArgs::from_bytes(&data).unwrap(); + assert_eq!(args.user_seed, user_seed); + assert_eq!(args.authority_type, 1); + assert_eq!(args.auth_bump, 123); + assert_eq!(rest.len(), 65); // from_bytes returns the raw remaining data + assert_eq!(&rest[0..32], &cred_id_hash); + assert_eq!(&rest[32..65], &pubkey); + } + + #[test] + fn test_create_wallet_args_too_short() { + let data = vec![0u8; 39]; // Need 40 + assert!(CreateWalletArgs::from_bytes(&data).is_err()); + } +} diff --git a/program/src/processor/execute.rs b/program/src/processor/execute.rs new file mode 100644 index 0000000..5469305 --- /dev/null +++ b/program/src/processor/execute.rs @@ -0,0 +1,342 @@ +use crate::{ + auth::{ + ed25519::Ed25519Authenticator, secp256r1::Secp256r1Authenticator, traits::Authenticator, + }, + compact::parse_compact_instructions, + error::AuthError, + state::{authority::AuthorityAccountHeader, AccountDiscriminator}, +}; +use pinocchio::{ + account_info::AccountInfo, + instruction::{Account, AccountMeta, Instruction, Seed, Signer}, + program::invoke_signed_unchecked, + program_error::ProgramError, + pubkey::{find_program_address, Pubkey}, + sysvars::{clock::Clock, Sysvar}, + ProgramResult, +}; + +/// Process the Execute instruction +/// Processes the `Execute` instruction. +/// +/// Executes a batch of condensed "Compact Instructions" on behalf of the wallet. +/// +/// # Logic: +/// 1. **Authentication**: Verifies that the signer is a valid `Authority` or `Session` for this wallet. +/// 2. **Session Checks**: If authenticated via Session, enforces slot expiry. +/// 3. **Decompression**: Expands `CompactInstructions` (index-based references) into full Solana instructions. +/// 4. **Execution**: Invokes the Instructions via CPI, signing with the Vault PDA. +/// +/// # Accounts: +/// 1. `[signer]` Payer. +/// 2. `[]` Wallet PDA. +/// 3. `[signer]` Authority or Session PDA. +/// 4. `[signer]` Vault PDA (Signer for CPI). +/// 5. `...` Inner accounts referenced by instructions. +pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + // Parse accounts + let account_info_iter = &mut accounts.iter(); + let payer = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let wallet_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let authority_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let vault_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let treasury_shard = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let system_program = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + + // Parse Config and Charge Fee early + let (config_key, _config_bump) = find_program_address(&[b"config"], program_id); + if !assertions::sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + let config_data = unsafe { config_pda.borrow_data_unchecked() }; + if config_data.len() < std::mem::size_of::() { + return Err(ProgramError::UninitializedAccount); + } + let config_account = unsafe { + std::ptr::read_unaligned(config_data.as_ptr() as *const crate::state::config::ConfigAccount) + }; + + crate::utils::collect_protocol_fee( + program_id, + payer, + &config_account, + treasury_shard, + system_program, + false, // is_wallet_creation = false + )?; + + // Verify ownership + if wallet_pda.owner() != program_id || authority_pda.owner() != program_id { + return Err(ProgramError::IllegalOwner); + } + // Validate Wallet Discriminator (Issue #7) + let wallet_data = unsafe { wallet_pda.borrow_data_unchecked() }; + if wallet_data.is_empty() || wallet_data[0] != AccountDiscriminator::Wallet as u8 { + return Err(ProgramError::InvalidAccountData); + } + + if !authority_pda.is_writable() { + return Err(ProgramError::InvalidAccountData); + } + + // Read authority header + // Safe copy header + // Read authority data + let authority_data = unsafe { authority_pda.borrow_mut_data_unchecked() }; + + // Authenticate based on discriminator + let discriminator = if !authority_data.is_empty() { + authority_data[0] + } else { + return Err(ProgramError::InvalidAccountData); + }; + + // Parse compact instructions + let compact_instructions = parse_compact_instructions(instruction_data)?; + + // Serialize compact instructions to get their byte length + let compact_bytes = crate::compact::serialize_compact_instructions(&compact_instructions); + let compact_len = compact_bytes.len(); + + match discriminator { + 2 => { + // Authority + if authority_data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + // Use read_unaligned to safely copy potentially unaligned data into a local struct + let authority_header = unsafe { + std::ptr::read_unaligned(authority_data.as_ptr() as *const AuthorityAccountHeader) + }; + + if authority_header.discriminator != AccountDiscriminator::Authority as u8 { + return Err(ProgramError::InvalidAccountData); + } + + if authority_header.wallet != *wallet_pda.key() { + return Err(ProgramError::InvalidAccountData); + } + match authority_header.authority_type { + 0 => { + // Ed25519: Verify signer (authority_payload ignored) + Ed25519Authenticator.authenticate( + program_id, + accounts, + authority_data, + &[], + &[], + &[4], + )?; + }, + 1 => { + // Secp256r1 (WebAuthn) + // Issue #11: Include accounts hash to prevent account reordering attacks + // signed_payload is compact_instructions bytes + accounts hash for Execute + let data_payload = &instruction_data[..compact_len]; + let authority_payload = &instruction_data[compact_len..]; + + // Compute hash of all account pubkeys referenced by compact instructions + // This binds the signature to the exact accounts, preventing reordering + let accounts_hash = compute_accounts_hash(accounts, &compact_instructions)?; + + // Extended payload: compact_instructions + accounts_hash + let mut extended_payload = Vec::with_capacity(compact_len + 32); + extended_payload.extend_from_slice(data_payload); + extended_payload.extend_from_slice(&accounts_hash); + + Secp256r1Authenticator.authenticate( + program_id, + accounts, + authority_data, + authority_payload, + &extended_payload, + &[4], // Execute instruction discriminator + )?; + }, + _ => return Err(AuthError::InvalidAuthenticationKind.into()), + } + }, + 3 => { + // Session + let session_data = unsafe { authority_pda.borrow_mut_data_unchecked() }; + if session_data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + + // Use read_unaligned to safely load SessionAccount + let session = unsafe { + std::ptr::read_unaligned( + session_data.as_ptr() as *const crate::state::session::SessionAccount + ) + }; + + let clock = Clock::get()?; + let current_slot = clock.slot; + + // Verify Wallet + if session.wallet != *wallet_pda.key() { + return Err(ProgramError::InvalidAccountData); + } + + // Verify Expiry + if current_slot > session.expires_at { + return Err(AuthError::SessionExpired.into()); + } + + // Verify Signer matches Session Key + let mut signer_matched = false; + for acc in accounts { + if acc.is_signer() && *acc.key() == session.session_key { + signer_matched = true; + break; + } + } + if !signer_matched { + return Err(ProgramError::MissingRequiredSignature); + } + }, + _ => return Err(ProgramError::InvalidAccountData), + } + + // Get vault bump for signing + let (vault_key, vault_bump) = + find_program_address(&[b"vault", wallet_pda.key().as_ref()], program_id); + + // Verify vault PDA. + // CRITICAL: Ensure we are signing with the correct Vault derived from this Wallet. + if vault_pda.key() != &vault_key { + return Err(ProgramError::InvalidSeeds); + } + + // Execute each compact instruction + for compact_ix in &compact_instructions { + let decompressed = compact_ix.decompress(accounts)?; + + // Build AccountMeta array for instruction + let account_metas: Vec = decompressed + .accounts + .iter() + .map(|acc| AccountMeta { + pubkey: acc.key(), + is_signer: acc.is_signer() || acc.key() == vault_pda.key(), + is_writable: acc.is_writable(), + }) + .collect(); + + // Prevent self-reentrancy (Issue #10) + // Reject CPI calls back into this program to avoid unexpected state mutations + if decompressed.program_id.as_ref() == program_id.as_ref() { + return Err(AuthError::SelfReentrancyNotAllowed.into()); + } + + // Create instruction + let ix = Instruction { + program_id: decompressed.program_id, + accounts: &account_metas, + data: &decompressed.data, + }; + + // Create seeds for vault signing (pinocchio style) + let vault_bump_arr = [vault_bump]; + let seeds = [ + Seed::from(b"vault"), + Seed::from(wallet_pda.key().as_ref()), + Seed::from(&vault_bump_arr), + ]; + let signer: Signer = (&seeds).into(); + + // Convert AccountInfo to Account for invoke_signed_unchecked + let cpi_accounts: Vec = decompressed + .accounts + .iter() + .map(|acc| Account::from(*acc)) + .collect(); + + // Invoke with vault as signer + // Use unchecked invocation to support dynamic account list (slice) + unsafe { + invoke_signed_unchecked(&ix, &cpi_accounts, &[signer]); + } + } + + Ok(()) +} + +/// Compute SHA256 hash of all account pubkeys referenced by compact instructions (Issue #11) +/// +/// This binds the signature to the exact accounts in their exact order, +/// preventing account reordering attacks where an attacker could swap +/// recipient addresses while keeping the signature valid. +/// +/// # Arguments +/// * `accounts` - Slice of all account infos in the transaction +/// * `compact_instructions` - Parsed compact instructions containing account indices +/// +/// # Returns +/// * 32-byte SHA256 hash of all referenced pubkeys +fn compute_accounts_hash( + accounts: &[AccountInfo], + compact_instructions: &[crate::compact::CompactInstruction], +) -> Result<[u8; 32], ProgramError> { + // Collect all account pubkeys in order of reference + let mut pubkeys_data = Vec::new(); + + for ix in compact_instructions { + // Include program_id + let program_idx = ix.program_id_index as usize; + if program_idx >= accounts.len() { + return Err(ProgramError::InvalidInstructionData); + } + pubkeys_data.extend_from_slice(accounts[program_idx].key().as_ref()); + + // Include all account pubkeys + for &acc_idx in &ix.accounts { + let idx = acc_idx as usize; + if idx >= accounts.len() { + return Err(ProgramError::InvalidInstructionData); + } + pubkeys_data.extend_from_slice(accounts[idx].key().as_ref()); + } + } + + // Compute SHA256 hash + #[allow(unused_assignments)] + let mut hash = [0u8; 32]; + #[cfg(target_os = "solana")] + unsafe { + pinocchio::syscalls::sol_sha256( + [pubkeys_data.as_slice()].as_ptr() as *const u8, + 1, + hash.as_mut_ptr(), + ); + } + #[cfg(not(target_os = "solana"))] + { + // For tests, use a dummy hash + hash = [0xAA; 32]; + let _ = pubkeys_data; // suppress warning + } + + Ok(hash) +} diff --git a/program/src/processor/init_treasury_shard.rs b/program/src/processor/init_treasury_shard.rs new file mode 100644 index 0000000..a74e96d --- /dev/null +++ b/program/src/processor/init_treasury_shard.rs @@ -0,0 +1,103 @@ +use assertions::sol_assert_bytes_eq; +use pinocchio::{ + account_info::AccountInfo, + instruction::Seed, + program_error::ProgramError, + pubkey::{find_program_address, Pubkey}, + sysvars::rent::Rent, + ProgramResult, +}; + +use crate::state::{config::ConfigAccount, AccountDiscriminator}; + +/// Arguments: +/// - `shard_id`: u8 +pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + if instruction_data.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + let shard_id = instruction_data[0]; + + let account_info_iter = &mut accounts.iter(); + let payer_info = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let treasury_shard_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let system_program = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let rent_sysvar = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + if !payer_info.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Verify Config PDA + let (config_key, _config_bump) = find_program_address(&[b"config"], program_id); + if !sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + + // Read config to verify shard_id is within bounds + let config_data = unsafe { config_pda.borrow_data_unchecked() }; + if config_data.len() < std::mem::size_of::() { + return Err(ProgramError::UninitializedAccount); + } + + let config_account = + unsafe { std::ptr::read_unaligned(config_data.as_ptr() as *const ConfigAccount) }; + + if config_account.discriminator != AccountDiscriminator::Config as u8 { + return Err(ProgramError::InvalidAccountData); + } + + if shard_id >= config_account.num_shards { + return Err(ProgramError::InvalidArgument); + } + + // Verify Treasury Shard PDA + let shard_id_bytes = [shard_id]; + let (shard_key, shard_bump) = find_program_address(&[b"treasury", &shard_id_bytes], program_id); + if !sol_assert_bytes_eq(treasury_shard_pda.key().as_ref(), shard_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + + // A treasury shard account has NO structure/data. It's just a raw system account + // owned by the system program (or our program) with a balance to be rent-exempt. + // If it has 0 bytes data, minimum balance is 890,880 lamports. + let rent = Rent::from_account_info(rent_sysvar)?; + let rent_lamports = rent.minimum_balance(0); + + let shard_bump_arr = [shard_bump]; + let pda_seeds = [ + Seed::from(b"treasury"), + Seed::from(&shard_id_bytes), + Seed::from(&shard_bump_arr), + ]; + + // Initialize as a 0-byte PDA owned by system program (or program_id, but our init func assigns to program_id) + // Actually, it doesn't matter much if it's owned by System Program or our Program, + // as long as the PDA signs to transfer funds out later. Let's make it owned by program_id + crate::utils::initialize_pda_account( + payer_info, + treasury_shard_pda, + system_program, + 0, // 0 space + rent_lamports, + program_id, + &pda_seeds, + )?; + + Ok(()) +} diff --git a/program/src/processor/initialize_config.rs b/program/src/processor/initialize_config.rs new file mode 100644 index 0000000..eae6fa4 --- /dev/null +++ b/program/src/processor/initialize_config.rs @@ -0,0 +1,131 @@ +use assertions::{check_zero_data, sol_assert_bytes_eq}; +use no_padding::NoPadding; +use pinocchio::{ + account_info::AccountInfo, instruction::Seed, program_error::ProgramError, + pubkey::find_program_address, pubkey::Pubkey, sysvars::rent::Rent, ProgramResult, +}; + +use crate::state::{config::ConfigAccount, AccountDiscriminator, CURRENT_ACCOUNT_VERSION}; + +/// Arguments for `InitializeConfig`. +/// Layout: +/// - `wallet_fee`: u64 (8 bytes) +/// - `action_fee`: u64 (8 bytes) +/// - `num_shards`: u8 (1 byte) +#[repr(C, align(8))] +#[derive(Debug, NoPadding)] +pub struct InitializeConfigArgs { + pub wallet_fee: u64, + pub action_fee: u64, + pub num_shards: u8, + pub _padding: [u8; 7], +} + +impl InitializeConfigArgs { + pub fn from_bytes(data: &[u8]) -> Result { + if data.len() < 17 { + return Err(ProgramError::InvalidInstructionData); + } + let mut wallet_fee_bytes = [0u8; 8]; + wallet_fee_bytes.copy_from_slice(&data[0..8]); + let wallet_fee = u64::from_le_bytes(wallet_fee_bytes); + + let mut action_fee_bytes = [0u8; 8]; + action_fee_bytes.copy_from_slice(&data[8..16]); + let action_fee = u64::from_le_bytes(action_fee_bytes); + + let num_shards = data[16]; + if num_shards == 0 { + return Err(ProgramError::InvalidInstructionData); // Must have at least 1 shard + } + + Ok(Self { + wallet_fee, + action_fee, + num_shards, + _padding: [0; 7], + }) + } +} + +/// Initializes the global Config PDA. +/// +/// Accounts: +/// 0. `[signer, writable]` Admin +/// 1. `[writable]` Config PDA +/// 2. `[]` System Program +/// 3. `[]` Rent Sysvar +pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let args = InitializeConfigArgs::from_bytes(instruction_data)?; + + let account_info_iter = &mut accounts.iter(); + let admin_info = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let system_program = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let rent_sysvar = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + if !admin_info.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + let rent = Rent::from_account_info(rent_sysvar)?; + + // Validate Config PDA seeds + let (config_key, config_bump) = find_program_address(&[b"config"], program_id); + if !sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + check_zero_data(config_pda, ProgramError::AccountAlreadyInitialized)?; + + // Initialize the Config PDA space + let config_space = std::mem::size_of::(); + let config_rent = rent.minimum_balance(config_space); + + let config_bump_arr = [config_bump]; + let config_seeds = [Seed::from(b"config"), Seed::from(&config_bump_arr)]; + + crate::utils::initialize_pda_account( + admin_info, + config_pda, + system_program, + config_space, + config_rent, + program_id, + &config_seeds, + )?; + + // Write the data + let config_data = unsafe { config_pda.borrow_mut_data_unchecked() }; + + let config_account = ConfigAccount { + discriminator: AccountDiscriminator::Config as u8, + bump: config_bump, + version: CURRENT_ACCOUNT_VERSION, + num_shards: args.num_shards, + _padding: [0; 4], + admin: *admin_info.key(), + wallet_fee: args.wallet_fee, + action_fee: args.action_fee, + }; + + unsafe { + std::ptr::write_unaligned( + config_data.as_mut_ptr() as *mut ConfigAccount, + config_account, + ); + } + + Ok(()) +} diff --git a/program/src/processor/manage_authority.rs b/program/src/processor/manage_authority.rs new file mode 100644 index 0000000..b853467 --- /dev/null +++ b/program/src/processor/manage_authority.rs @@ -0,0 +1,498 @@ +use assertions::{check_zero_data, sol_assert_bytes_eq}; +use no_padding::NoPadding; +use pinocchio::{ + account_info::AccountInfo, + instruction::Seed, + program_error::ProgramError, + pubkey::{find_program_address, Pubkey}, + sysvars::{rent::Rent, Sysvar}, + ProgramResult, +}; + +use crate::{ + auth::{ + ed25519::Ed25519Authenticator, secp256r1::Secp256r1Authenticator, traits::Authenticator, + }, + error::AuthError, + state::{authority::AuthorityAccountHeader, AccountDiscriminator}, +}; + +/// Arguments for the `AddAuthority` instruction. +/// +/// Layout: +/// - `authority_type`: 0 for Ed25519, 1 for Secp256r1. +/// - `new_role`: Role to assign (0=Owner, 1=Admin, 2=Spender). +/// - `_padding`: Reserved to align to 8-byte boundary. +#[repr(C, align(8))] +#[derive(NoPadding)] +pub struct AddAuthorityArgs { + pub authority_type: u8, + pub new_role: u8, + pub _padding: [u8; 6], +} + +impl AddAuthorityArgs { + pub fn from_bytes(data: &[u8]) -> Result<(Self, &[u8]), ProgramError> { + if data.len() < 8 { + return Err(ProgramError::InvalidInstructionData); + } + let (fixed, rest) = data.split_at(8); + + // Manual deserialization for safety + let authority_type = fixed[0]; + let new_role = fixed[1]; + + let args = Self { + authority_type, + new_role, + _padding: [0; 6], + }; + + Ok((args, rest)) + } +} + +/// Processes the `AddAuthority` instruction. +/// +/// Adds a new authority to the wallet. +/// +/// # Logic: +/// 1. **Authentication**: Verifies the `admin_authority` (must be Admin or Owner). +/// 2. **Authorization**: Checks permission levels: +/// - `Owner` (0) can add any role. +/// - `Admin` (1) can only add `Spender` (2). +/// 3. **Execution**: Creates a new PDA `["authority", wallet, id_hash]` and initializes it. +/// +/// # Accounts: +/// 1. `[signer, writable]` Payer. +/// 2. `[]` Wallet PDA. +/// 3. `[signer]` Admin Authority: Existing authority authorizing this action. +/// 4. `[writable]` New Authority: The PDA to create. +/// 5. `[]` System Program. +pub fn process_add_authority( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let (args, rest) = AddAuthorityArgs::from_bytes(instruction_data)?; + + let (id_seed, full_auth_data) = match args.authority_type { + 0 => { + if rest.len() < 32 { + return Err(ProgramError::InvalidInstructionData); + } + let (pubkey, _) = rest.split_at(32); + (pubkey, pubkey) + }, + 1 => { + // [credential_id_hash(32)] [pubkey(33)] = 65 bytes total + if rest.len() < 65 { + return Err(ProgramError::InvalidInstructionData); + } + let (credential_id_hash, _rest_after_cred) = rest.split_at(32); + // We store credential_id_hash + pubkey for on-chain wallet discovery + let full_auth_data = &rest[..65]; + (credential_id_hash, full_auth_data) + }, + _ => return Err(AuthError::InvalidAuthenticationKind.into()), + }; + + // Split data_payload and authority_payload + // data_payload = everything up to and including the new authority data + let data_payload_len = 8 + full_auth_data.len(); // args + full_auth_data + if instruction_data.len() < data_payload_len { + return Err(ProgramError::InvalidInstructionData); + } + let (data_payload, authority_payload) = instruction_data.split_at(data_payload_len); + + let account_info_iter = &mut accounts.iter(); + let payer = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let wallet_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let admin_auth_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let new_auth_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let system_program = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let treasury_shard = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + let (config_key, _config_bump) = find_program_address(&[b"config"], program_id); + if !sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + let config_data = unsafe { config_pda.borrow_data_unchecked() }; + if config_data.len() < std::mem::size_of::() { + return Err(ProgramError::UninitializedAccount); + } + let config = unsafe { + std::ptr::read_unaligned(config_data.as_ptr() as *const crate::state::config::ConfigAccount) + }; + + crate::utils::collect_protocol_fee( + program_id, + payer, + &config, + treasury_shard, + system_program, + false, + )?; + + if wallet_pda.owner() != program_id { + return Err(ProgramError::IllegalOwner); + } + if admin_auth_pda.owner() != program_id { + return Err(ProgramError::IllegalOwner); + } + // Validate Wallet Discriminator (Issue #7) + let wallet_data = unsafe { wallet_pda.borrow_data_unchecked() }; + if wallet_data.is_empty() || wallet_data[0] != AccountDiscriminator::Wallet as u8 { + return Err(ProgramError::InvalidAccountData); + } + + let rent = Rent::get()?; + + // Check removed here, moved to type-specific logic + // if !admin_auth_pda.is_writable() { + // return Err(ProgramError::InvalidAccountData); + // } + + let admin_data = unsafe { admin_auth_pda.borrow_mut_data_unchecked() }; + if admin_data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + + // Safe Copy of Header using read_unaligned + let admin_header = + unsafe { std::ptr::read_unaligned(admin_data.as_ptr() as *const AuthorityAccountHeader) }; + + if admin_header.discriminator != AccountDiscriminator::Authority as u8 { + return Err(ProgramError::InvalidAccountData); + } + if admin_header.wallet != *wallet_pda.key() { + return Err(ProgramError::InvalidAccountData); + } + + // Unified Authentication + // Include payer + target + wallet in signed payload to prevent account swap replay attacks + let mut ed25519_payload = Vec::with_capacity(96); + ed25519_payload.extend_from_slice(payer.key().as_ref()); + ed25519_payload.extend_from_slice(new_auth_pda.key().as_ref()); + ed25519_payload.extend_from_slice(wallet_pda.key().as_ref()); + + match admin_header.authority_type { + 0 => { + // Ed25519: Include payer + new_auth_pda in signed payload + Ed25519Authenticator.authenticate( + program_id, + accounts, + admin_data, + &[], + &ed25519_payload, + &[1], + )?; + }, + 1 => { + // Secp256r1 (WebAuthn) - Must be Writable + if !admin_auth_pda.is_writable() { + return Err(ProgramError::InvalidAccountData); + } + // Secp256r1: Include payer + wallet in signed payload + let mut extended_data_payload = Vec::with_capacity(data_payload.len() + 64); + extended_data_payload.extend_from_slice(data_payload); + extended_data_payload.extend_from_slice(payer.key().as_ref()); + extended_data_payload.extend_from_slice(wallet_pda.key().as_ref()); + + Secp256r1Authenticator.authenticate( + program_id, + accounts, + admin_data, + authority_payload, + &extended_data_payload, + &[1], + )?; + }, + _ => return Err(AuthError::InvalidAuthenticationKind.into()), + } + + // Authorization + if admin_header.role != 0 && (admin_header.role != 1 || args.new_role != 2) { + return Err(AuthError::PermissionDenied.into()); + } + + // Logic + let (new_auth_key, bump) = find_program_address( + &[b"authority", wallet_pda.key().as_ref(), id_seed], + program_id, + ); + if !sol_assert_bytes_eq(new_auth_pda.key().as_ref(), new_auth_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + check_zero_data(new_auth_pda, ProgramError::AccountAlreadyInitialized)?; + + let header_size = std::mem::size_of::(); + let variable_size = full_auth_data.len(); + let space = header_size + variable_size; + let rent_lamports = rent.minimum_balance(space); + + // Use secure transfer-allocate-assign pattern to prevent DoS (Issue #4) + let bump_arr = [bump]; + let seeds = [ + Seed::from(b"authority"), + Seed::from(wallet_pda.key().as_ref()), + Seed::from(id_seed), + Seed::from(&bump_arr), + ]; + + crate::utils::initialize_pda_account( + payer, + new_auth_pda, + system_program, + space, + rent_lamports, + program_id, + &seeds, + )?; + + let data = unsafe { new_auth_pda.borrow_mut_data_unchecked() }; + let header = AuthorityAccountHeader { + discriminator: AccountDiscriminator::Authority as u8, + authority_type: args.authority_type, + role: args.new_role, + bump, + version: crate::state::CURRENT_ACCOUNT_VERSION, + _padding: [0; 3], + counter: 0, + wallet: *wallet_pda.key(), + }; + unsafe { + std::ptr::write_unaligned(data.as_mut_ptr() as *mut AuthorityAccountHeader, header); + } + + let variable_target = &mut data[header_size..]; + variable_target.copy_from_slice(full_auth_data); + + Ok(()) +} + +/// Processes the `RemoveAuthority` instruction. +/// +/// Removes an existing authority and refunds rent to the destination. +/// +/// # Logic: +/// 1. **Authentication**: Verifies the `admin_authority`. +/// 2. **Authorization**: +/// - `Owner` can remove anyone (except potentially the last owner, though not explicitly enforced here). +/// - `Admin` can only remove `Spender`. +/// 3. **Execution**: Securely closes the account by zeroing data and transferring lamports. +/// +/// # Accounts: +/// 1. `[signer]` Payer. +/// 2. `[]` Wallet PDA. +/// 3. `[signer]` Admin Authority. +/// 4. `[writable]` Target Authority: PDA to verify and close. +/// 5. `[writable]` Refund Destination. +pub fn process_remove_authority( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + // For RemoveAuthority, all instruction_data is authority_payload + // Issue #13: Bind signature to specific target accounts to prevent reuse + let authority_payload = instruction_data; + + // Build data_payload with target pubkeys (computed after parsing accounts) + + let account_info_iter = &mut accounts.iter(); + let payer = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let wallet_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let admin_auth_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let target_auth_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let refund_dest = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let system_program = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let treasury_shard = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + // Read config + let (config_key, _config_bump) = find_program_address(&[b"config"], program_id); + if !assertions::sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + let config_data = unsafe { config_pda.borrow_data_unchecked() }; + if config_data.len() < std::mem::size_of::() { + return Err(ProgramError::UninitializedAccount); + } + let config = unsafe { + std::ptr::read_unaligned(config_data.as_ptr() as *const crate::state::config::ConfigAccount) + }; + + crate::utils::collect_protocol_fee( + program_id, + payer, + &config, + treasury_shard, + system_program, + false, // not a wallet creation + )?; + + if wallet_pda.owner() != program_id + || admin_auth_pda.owner() != program_id + || target_auth_pda.owner() != program_id + { + return Err(ProgramError::IllegalOwner); + } + + // Validate Wallet Discriminator (Issue #7) + let wallet_data = unsafe { wallet_pda.borrow_data_unchecked() }; + if wallet_data.is_empty() || wallet_data[0] != AccountDiscriminator::Wallet as u8 { + return Err(ProgramError::InvalidAccountData); + } + + if !admin_auth_pda.is_writable() { + return Err(ProgramError::InvalidAccountData); + } + + // Safe copy header using read_unaligned + let admin_data = unsafe { admin_auth_pda.borrow_mut_data_unchecked() }; + if admin_data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + let admin_header = + unsafe { std::ptr::read_unaligned(admin_data.as_ptr() as *const AuthorityAccountHeader) }; + + if admin_header.discriminator != AccountDiscriminator::Authority as u8 { + return Err(ProgramError::InvalidAccountData); + } + if admin_header.wallet != *wallet_pda.key() { + return Err(ProgramError::InvalidAccountData); + } + + // Issue #13: Build data_payload with target pubkeys to prevent signature reuse + // Signature is now bound to specific target_auth_pda and refund_dest + let mut data_payload = Vec::with_capacity(64); + data_payload.extend_from_slice(target_auth_pda.key().as_ref()); + data_payload.extend_from_slice(refund_dest.key().as_ref()); + + // Authentication + match admin_header.authority_type { + 0 => { + // Ed25519: Include data_payload in signature verification + Ed25519Authenticator.authenticate( + program_id, + accounts, + admin_data, + &[], + &data_payload, + &[2], + )?; + }, + 1 => { + Secp256r1Authenticator.authenticate( + program_id, + accounts, + admin_data, + authority_payload, + &data_payload, + &[2], + )?; + }, + _ => return Err(AuthError::InvalidAuthenticationKind.into()), + } + + // Authorization - ALWAYS validate target authority + let target_data = unsafe { target_auth_pda.borrow_data_unchecked() }; + if target_data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + // Safe copy target header using read_unaligned + let target_header = + unsafe { std::ptr::read_unaligned(target_data.as_ptr() as *const AuthorityAccountHeader) }; + + // ALWAYS verify discriminator + if target_header.discriminator != AccountDiscriminator::Authority as u8 { + return Err(ProgramError::InvalidAccountData); + } + + // ALWAYS verify target belongs to THIS wallet (CRITICAL SECURITY CHECK) + if target_header.wallet != *wallet_pda.key() { + return Err(ProgramError::InvalidAccountData); + } + + // Role-based permission check + if admin_header.role != 0 { + // Admin can only remove Spender + if admin_header.role != 1 || target_header.role != 2 { + return Err(AuthError::PermissionDenied.into()); + } + } + + let target_lamports = unsafe { *target_auth_pda.borrow_mut_lamports_unchecked() }; + let refund_lamports = unsafe { *refund_dest.borrow_mut_lamports_unchecked() }; + unsafe { + *refund_dest.borrow_mut_lamports_unchecked() = refund_lamports + .checked_add(target_lamports) + .ok_or(ProgramError::ArithmeticOverflow)?; + *target_auth_pda.borrow_mut_lamports_unchecked() = 0; + } + let target_data = unsafe { target_auth_pda.borrow_mut_data_unchecked() }; + target_data.fill(0); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_authority_args_from_bytes() { + // [type(1)][role(1)][padding(6)] + let mut data = Vec::new(); + data.push(0); // Ed25519 + data.push(2); // Spender + data.extend_from_slice(&[0; 6]); // padding + + let extra_data = [1u8; 32]; + data.extend_from_slice(&extra_data); + + let (args, rest) = AddAuthorityArgs::from_bytes(&data).unwrap(); + assert_eq!(args.authority_type, 0); + assert_eq!(args.new_role, 2); + assert_eq!(rest, &extra_data); + } + + #[test] + fn test_add_authority_args_too_short() { + let data = vec![0u8; 7]; // Need 8 + assert!(AddAuthorityArgs::from_bytes(&data).is_err()); + } +} diff --git a/program/src/processor/mod.rs b/program/src/processor/mod.rs new file mode 100644 index 0000000..759737d --- /dev/null +++ b/program/src/processor/mod.rs @@ -0,0 +1,15 @@ +//! Processor modules for handling program instructions. +//! +//! Each module corresponds to a specific instruction in the IDL. + +pub mod close_session; +pub mod close_wallet; +pub mod create_session; +pub mod create_wallet; +pub mod execute; +pub mod init_treasury_shard; +pub mod initialize_config; +pub mod manage_authority; +pub mod sweep_treasury; +pub mod transfer_ownership; +pub mod update_config; diff --git a/program/src/processor/sweep_treasury.rs b/program/src/processor/sweep_treasury.rs new file mode 100644 index 0000000..416fd1d --- /dev/null +++ b/program/src/processor/sweep_treasury.rs @@ -0,0 +1,104 @@ +use assertions::sol_assert_bytes_eq; +use pinocchio::{ + account_info::AccountInfo, + program_error::ProgramError, + pubkey::{find_program_address, Pubkey}, + sysvars::Sysvar, + ProgramResult, +}; + +use crate::{error::AuthError, state::{config::ConfigAccount, AccountDiscriminator}}; + +/// Arguments: +/// - `shard_id`: u8 +pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + if instruction_data.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + let shard_id = instruction_data[0]; + + let account_info_iter = &mut accounts.iter(); + let admin_info = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let treasury_shard_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let destination_wallet = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + if !admin_info.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Verify Config PDA + let (config_key, _config_bump) = find_program_address(&[b"config"], program_id); + if !sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + + // Read config to verify admin + let config_data = unsafe { config_pda.borrow_data_unchecked() }; + if config_data.len() < std::mem::size_of::() { + return Err(ProgramError::UninitializedAccount); + } + + let config_account = + unsafe { std::ptr::read_unaligned(config_data.as_ptr() as *const ConfigAccount) }; + + if config_account.discriminator != AccountDiscriminator::Config as u8 { + return Err(ProgramError::InvalidAccountData); + } + + if config_account.admin != *admin_info.key() { + return Err(AuthError::PermissionDenied.into()); // Only admin can sweep + } + + if shard_id >= config_account.num_shards { + return Err(ProgramError::InvalidArgument); // Trying to sweep non-existent shard range + } + + // Verify Treasury Shard PDA + let shard_id_bytes = [shard_id]; + let (shard_key, _shard_bump) = + find_program_address(&[b"treasury", &shard_id_bytes], program_id); + if !sol_assert_bytes_eq(treasury_shard_pda.key().as_ref(), shard_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + + // Transfer lamports from treasury shard to destination wallet, leaving rent exemption behind + let rent = pinocchio::sysvars::rent::Rent::get()?; + let shard_space = unsafe { treasury_shard_pda.borrow_data_unchecked() }.len(); + let rent_lamports = rent.minimum_balance(shard_space); + + let shard_lamports = treasury_shard_pda.lamports(); + if shard_lamports <= rent_lamports { + return Err(ProgramError::InsufficientFunds); + } + let withdrawable = shard_lamports - rent_lamports; + + let dest_lamports = destination_wallet.lamports(); + + unsafe { + *destination_wallet.borrow_mut_lamports_unchecked() = dest_lamports + .checked_add(withdrawable) + .ok_or(ProgramError::ArithmeticOverflow)?; + *treasury_shard_pda.borrow_mut_lamports_unchecked() = rent_lamports; + } + + // Erase any data if there was any (zero data) + let shard_data = unsafe { treasury_shard_pda.borrow_mut_data_unchecked() }; + if !shard_data.is_empty() { + shard_data.fill(0); + } + + Ok(()) +} diff --git a/program/src/processor/transfer_ownership.rs b/program/src/processor/transfer_ownership.rs new file mode 100644 index 0000000..5e593d3 --- /dev/null +++ b/program/src/processor/transfer_ownership.rs @@ -0,0 +1,315 @@ +use assertions::{check_zero_data, sol_assert_bytes_eq}; +use pinocchio::{ + account_info::AccountInfo, + instruction::Seed, + program_error::ProgramError, + pubkey::{find_program_address, Pubkey}, + sysvars::rent::Rent, + ProgramResult, +}; + +use crate::{ + // Unified authentication helpers. + auth::{ + ed25519::Ed25519Authenticator, secp256r1::Secp256r1Authenticator, traits::Authenticator, + }, + error::AuthError, + state::{authority::AuthorityAccountHeader, AccountDiscriminator}, +}; + +/// Processes the `TransferOwnership` instruction. +/// +/// atomically transfers the "Owner" role from the current authority to a new one. +/// The old owner is closed/removed, and the new one is created with `Role::Owner`. +/// +/// # Logic: +/// 1. **Authentication**: Verifies the `current_owner` matches the request logic. +/// 2. **Authorization**: strictly enforced to only work if `current_owner` has `Role::Owner` (0). +/// 3. **Atomic Swap**: +/// - Creates the `new_owner` account. +/// - Closes the `current_owner` account and refunds rent to payer. +/// +/// # Accounts: +/// 1. `[signer, writable]` Payer. +/// 2. `[]` Wallet PDA. +/// 3. `[signer, writable]` Current Owner Authority. +/// 4. `[writable]` New Owner Authority. +/// 5. `[]` System Program. +/// +/// Arguments for the `TransferOwnership` instruction. +/// +/// Layout: +/// - `new_type`: Authority Type (0=Ed25519, 1=Secp256r1). +/// - `pubkey`/`hash`: The identifier for the new authority. +#[derive(Debug)] +pub struct TransferOwnershipArgs { + pub auth_type: u8, +} + +impl TransferOwnershipArgs { + pub fn from_bytes(data: &[u8]) -> Result<(Self, &[u8]), ProgramError> { + if data.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + let auth_type = data[0]; + let rest = &data[1..]; + + Ok((Self { auth_type }, rest)) + } +} + +pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let (args, rest) = TransferOwnershipArgs::from_bytes(instruction_data)?; + + let (id_seed, full_auth_data) = match args.auth_type { + 0 => { + if rest.len() < 32 { + return Err(ProgramError::InvalidInstructionData); + } + let (pubkey, _) = rest.split_at(32); + (pubkey, pubkey) + }, + 1 => { + if rest.len() < 65 { + // 32 (hash) + 33 (pubkey) minimum + return Err(ProgramError::InvalidInstructionData); + } + let (hash, _rest_after_hash) = rest.split_at(32); + let full_data = &rest[..65]; // hash + pubkey (33 bytes) + (hash, full_data) + }, + _ => return Err(AuthError::InvalidAuthenticationKind.into()), + }; + + // Issue #15: Prevent transferring ownership to zero address / SystemProgram + if id_seed.iter().all(|&x| x == 0) { + return Err(ProgramError::InvalidAccountData); + } + + // Split data_payload and authority_payload + let data_payload_len = 1 + full_auth_data.len(); // auth_type + full_auth_data + if instruction_data.len() < data_payload_len { + return Err(ProgramError::InvalidInstructionData); + } + let (data_payload, authority_payload) = instruction_data.split_at(data_payload_len); + + let account_info_iter = &mut accounts.iter(); + let payer = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let wallet_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let current_owner = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let new_owner = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let system_program = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let rent_sysvar = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let rent_obj = Rent::from_account_info(rent_sysvar)?; + + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let treasury_shard = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + // Parse Config and Charge Fee + let (config_key, _config_bump) = find_program_address(&[b"config"], program_id); + if !assertions::sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + let config_data = unsafe { config_pda.borrow_data_unchecked() }; + if config_data.len() < std::mem::size_of::() { + return Err(ProgramError::UninitializedAccount); + } + let config = unsafe { + std::ptr::read_unaligned(config_data.as_ptr() as *const crate::state::config::ConfigAccount) + }; + + crate::utils::collect_protocol_fee( + program_id, + payer, + &config, + treasury_shard, + system_program, + false, // not a wallet creation + )?; + + if wallet_pda.owner() != program_id || current_owner.owner() != program_id { + return Err(ProgramError::IllegalOwner); + } + // Validate Wallet Discriminator (Issue #7) + let wallet_data = unsafe { wallet_pda.borrow_data_unchecked() }; + if wallet_data.is_empty() || wallet_data[0] != AccountDiscriminator::Wallet as u8 { + return Err(ProgramError::InvalidAccountData); + } + + if !current_owner.is_writable() { + return Err(ProgramError::InvalidAccountData); + } + + // Scope to borrow current owner data + { + let data = unsafe { current_owner.borrow_mut_data_unchecked() }; + if (data.as_ptr() as usize) % 8 != 0 { + return Err(ProgramError::InvalidAccountData); + } + // SAFETY: Use read_unaligned for safety + let auth = + unsafe { std::ptr::read_unaligned(data.as_ptr() as *const AuthorityAccountHeader) }; + if auth.discriminator != AccountDiscriminator::Authority as u8 { + return Err(ProgramError::InvalidAccountData); + } + if auth.wallet != *wallet_pda.key() { + return Err(ProgramError::InvalidAccountData); + } + if auth.role != 0 { + return Err(AuthError::PermissionDenied.into()); + } + + // Authenticate Current Owner + // Issue: Include payer + new_owner + wallet_pda to prevent rent theft and replay + let mut ed25519_payload = Vec::with_capacity(96); + ed25519_payload.extend_from_slice(payer.key().as_ref()); + ed25519_payload.extend_from_slice(new_owner.key().as_ref()); + ed25519_payload.extend_from_slice(wallet_pda.key().as_ref()); + + match auth.authority_type { + 0 => { + // Ed25519: Include payer + new_owner in signed payload + Ed25519Authenticator.authenticate( + program_id, + accounts, + data, + &[], + &ed25519_payload, + &[3], + )?; + }, + 1 => { + // Secp256r1 (WebAuthn) - Must be Writable + if !current_owner.is_writable() { + return Err(ProgramError::InvalidAccountData); + } + // Secp256r1: Include payer + wallet to prevent rent theft and replay + let mut extended_data_payload = Vec::with_capacity(data_payload.len() + 64); + extended_data_payload.extend_from_slice(data_payload); + extended_data_payload.extend_from_slice(payer.key().as_ref()); + extended_data_payload.extend_from_slice(wallet_pda.key().as_ref()); + + Secp256r1Authenticator.authenticate( + program_id, + accounts, + data, + authority_payload, + &extended_data_payload, + &[3], + )?; + }, + _ => return Err(AuthError::InvalidAuthenticationKind.into()), + } + } + + let (new_key, bump) = find_program_address( + &[b"authority", wallet_pda.key().as_ref(), id_seed], + program_id, + ); + if !sol_assert_bytes_eq(new_owner.key().as_ref(), new_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + check_zero_data(new_owner, ProgramError::AccountAlreadyInitialized)?; + + let header_size = std::mem::size_of::(); + let variable_size = full_auth_data.len(); + let space = header_size + variable_size; + let rent = rent_obj.minimum_balance(space); + + // Use secure transfer-allocate-assign pattern to prevent DoS (Issue #4) + let bump_arr = [bump]; + let seeds = [ + Seed::from(b"authority"), + Seed::from(wallet_pda.key().as_ref()), + Seed::from(id_seed), + Seed::from(&bump_arr), + ]; + + crate::utils::initialize_pda_account( + payer, + new_owner, + system_program, + space, + rent, + program_id, + &seeds, + )?; + + let data = unsafe { new_owner.borrow_mut_data_unchecked() }; + if data.len() < std::mem::size_of::() { + return Err(ProgramError::InvalidAccountData); + } + let header = AuthorityAccountHeader { + discriminator: AccountDiscriminator::Authority as u8, + authority_type: args.auth_type, + role: 0, + bump, + version: crate::state::CURRENT_ACCOUNT_VERSION, + _padding: [0; 3], + counter: 0, + wallet: *wallet_pda.key(), + }; + unsafe { + std::ptr::write_unaligned(data.as_mut_ptr() as *mut AuthorityAccountHeader, header); + } + + let variable_target = &mut data[header_size..]; + variable_target.copy_from_slice(full_auth_data); + + let current_lamports = unsafe { *current_owner.borrow_mut_lamports_unchecked() }; + let payer_lamports = unsafe { *payer.borrow_mut_lamports_unchecked() }; + unsafe { + *payer.borrow_mut_lamports_unchecked() = payer_lamports + .checked_add(current_lamports) + .ok_or(ProgramError::ArithmeticOverflow)?; + *current_owner.borrow_mut_lamports_unchecked() = 0; + } + let current_data = unsafe { current_owner.borrow_mut_data_unchecked() }; + current_data.fill(0); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_transfer_ownership_args_from_bytes() { + // [type(1)][rest...] + let mut data = Vec::new(); + data.push(1); // Secp256r1 + let payload = [5u8; 65]; + data.extend_from_slice(&payload); + + let (args, rest) = TransferOwnershipArgs::from_bytes(&data).unwrap(); + assert_eq!(args.auth_type, 1); + assert_eq!(rest, &payload); + } + + #[test] + fn test_transfer_ownership_args_too_short() { + let data = vec![]; + assert!(TransferOwnershipArgs::from_bytes(&data).is_err()); + } +} diff --git a/program/src/processor/update_config.rs b/program/src/processor/update_config.rs new file mode 100644 index 0000000..9df824a --- /dev/null +++ b/program/src/processor/update_config.rs @@ -0,0 +1,145 @@ +use assertions::sol_assert_bytes_eq; +use no_padding::NoPadding; +use pinocchio::{ + account_info::AccountInfo, program_error::ProgramError, pubkey::find_program_address, + pubkey::Pubkey, ProgramResult, +}; + +use crate::{error::AuthError, state::{config::ConfigAccount, AccountDiscriminator}}; + +/// Arguments for `UpdateConfig`. +/// Fixed length format: 53 bytes total. +/// - `update_wallet_fee`, `update_action_fee`, `update_num_shards`, `update_admin`, `num_shards` (5 bytes) +/// - `_padding` (3 bytes) +/// - `wallet_fee` (8 bytes) +/// - `action_fee` (8 bytes) +/// - `admin` (32 bytes) +#[repr(C, align(8))] +#[derive(Debug, NoPadding)] +pub struct UpdateConfigArgs { + pub update_wallet_fee: u8, + pub update_action_fee: u8, + pub update_num_shards: u8, + pub update_admin: u8, + pub num_shards: u8, + pub _padding: [u8; 3], + pub wallet_fee: u64, + pub action_fee: u64, + pub admin: [u8; 32], +} + +impl UpdateConfigArgs { + pub fn from_bytes(data: &[u8]) -> Result { + if data.len() < 56 { + return Err(ProgramError::InvalidInstructionData); + } + + let update_wallet_fee = data[0]; + let update_action_fee = data[1]; + let update_num_shards = data[2]; + let update_admin = data[3]; + let num_shards = data[4]; + + let mut wallet_fee_bytes = [0u8; 8]; + wallet_fee_bytes.copy_from_slice(&data[8..16]); + let wallet_fee = u64::from_le_bytes(wallet_fee_bytes); + + let mut action_fee_bytes = [0u8; 8]; + action_fee_bytes.copy_from_slice(&data[16..24]); + let action_fee = u64::from_le_bytes(action_fee_bytes); + + let mut admin = [0u8; 32]; + admin.copy_from_slice(&data[24..56]); + + Ok(Self { + update_wallet_fee, + update_action_fee, + update_num_shards, + update_admin, + num_shards, + _padding: [0; 3], + wallet_fee, + action_fee, + admin, + }) + } +} + +/// Updates the global Config PDA settings. +/// +/// Accounts: +/// 0. `[signer]` Admin (must match config.admin) +/// 1. `[writable]` Config PDA +pub fn process( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + let args = UpdateConfigArgs::from_bytes(instruction_data)?; + + let account_info_iter = &mut accounts.iter(); + let admin_info = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + let config_pda = account_info_iter + .next() + .ok_or(ProgramError::NotEnoughAccountKeys)?; + + if !admin_info.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Validate Config PDA seeds + let (config_key, _config_bump) = find_program_address(&[b"config"], program_id); + if !sol_assert_bytes_eq(config_pda.key().as_ref(), config_key.as_ref(), 32) { + return Err(ProgramError::InvalidSeeds); + } + + let config_data = unsafe { config_pda.borrow_mut_data_unchecked() }; + if config_data.len() < std::mem::size_of::() { + return Err(ProgramError::UninitializedAccount); + } + + // We can't use mutable reference to unaligned data easily without read_unaligned/write_unaligned + let mut config_account = + unsafe { std::ptr::read_unaligned(config_data.as_ptr() as *const ConfigAccount) }; + + // Verify Admin + if config_account.discriminator != AccountDiscriminator::Config as u8 { + return Err(ProgramError::InvalidAccountData); + } + if config_account.admin != *admin_info.key() { + return Err(AuthError::PermissionDenied.into()); // Only current admin can update + } + + // Apply updates + if args.update_wallet_fee != 0 { + config_account.wallet_fee = args.wallet_fee; + } + + if args.update_action_fee != 0 { + config_account.action_fee = args.action_fee; + } + + if args.update_num_shards != 0 { + if args.num_shards < config_account.num_shards { + // Cannot decrease num_shards to avoid stranding funds + return Err(ProgramError::InvalidArgument); + } + config_account.num_shards = args.num_shards; + } + + if args.update_admin != 0 { + config_account.admin = Pubkey::from(args.admin); + } + + // Write back + unsafe { + std::ptr::write_unaligned( + config_data.as_mut_ptr() as *mut ConfigAccount, + config_account, + ); + } + + Ok(()) +} diff --git a/program/src/state/authority.rs b/program/src/state/authority.rs new file mode 100644 index 0000000..cdd8a1b --- /dev/null +++ b/program/src/state/authority.rs @@ -0,0 +1,27 @@ +use no_padding::NoPadding; +use pinocchio::pubkey::Pubkey; + +/// Header for all Authority accounts. +/// +/// This header is followed by variable-length data depending on the `authority_type`. +#[repr(C, align(8))] +#[derive(NoPadding, Debug, Clone, Copy)] +pub struct AuthorityAccountHeader { + /// Account discriminator (must be `2` for Authority). + pub discriminator: u8, + /// Type of authority: `0` = Ed25519, `1` = Secp256r1 (WebAuthn). + pub authority_type: u8, + /// Permission role: `0` = Owner, `1` = Admin, `2` = Spender. + pub role: u8, + /// Bump seed used to derive this PDA. + pub bump: u8, + /// Account Version (for future upgrades). + pub version: u8, + /// Padding for 8-byte alignment. + pub _padding: [u8; 3], + /// Monotonically increasing counter to prevent replay attacks (Secp256r1 only). + pub counter: u64, + /// The wallet this authority belongs to. + pub wallet: Pubkey, +} +// 4 + 4 + 8 + 32 = 48. 48 is divisible by 8. diff --git a/program/src/state/config.rs b/program/src/state/config.rs new file mode 100644 index 0000000..6c94f20 --- /dev/null +++ b/program/src/state/config.rs @@ -0,0 +1,28 @@ +use no_padding::NoPadding; +use pinocchio::pubkey::Pubkey; + +#[repr(C, align(8))] +#[derive(NoPadding, Debug, Clone, Copy)] +/// Global Configuration Account for LazorKit. +/// +/// Stores protocol-wide settings such as the admin key, and fee structures. +/// There is only expected to be a single PDA of this type at seeds `["config"]`. +pub struct ConfigAccount { + /// Account discriminator (must be `4` for Config). + pub discriminator: u8, + /// Bump seed used to derive this PDA. + pub bump: u8, + /// Account Version (for future upgrades). + pub version: u8, + /// Number of treasury shards to distribute fees across. Max 255. + pub num_shards: u8, + /// Padding for 8-byte alignment. + pub _padding: [u8; 4], + /// The public key of the contract administrator. + pub admin: Pubkey, + /// The fixed fee (in lamports) charged for creating a new wallet. + pub wallet_fee: u64, + /// The fee (in lamports) charged for all other protocol actions. + pub action_fee: u64, +} +// Size: 1 + 1 + 1 + 1 + 4 + 32 + 8 + 8 = 56 bytes. diff --git a/program/src/state/mod.rs b/program/src/state/mod.rs new file mode 100644 index 0000000..6ffe255 --- /dev/null +++ b/program/src/state/mod.rs @@ -0,0 +1,22 @@ +pub mod authority; +pub mod config; +pub mod session; +pub mod wallet; + +/// Discriminators for account types to ensure type safety. +#[repr(u8)] +pub enum AccountDiscriminator { + /// The main Wallet account (Trust Anchor). + Wallet = 1, + /// An Authority account (Owner/Admin/Spender). + Authority = 2, + /// A Session account (Ephemeral Spender). + Session = 3, + /// The global Config account. + Config = 4, +} + +/// Helper constant for versioning. +/// +/// Current account logic version. +pub const CURRENT_ACCOUNT_VERSION: u8 = 1; diff --git a/program/src/state/session.rs b/program/src/state/session.rs new file mode 100644 index 0000000..197efd8 --- /dev/null +++ b/program/src/state/session.rs @@ -0,0 +1,24 @@ +use no_padding::NoPadding; +use pinocchio::pubkey::Pubkey; + +#[repr(C, align(8))] +#[derive(NoPadding)] +/// Ephemeral Session Account. +/// +/// Represents a temporary delegated authority with an expiration time. +pub struct SessionAccount { + /// Account discriminator (must be `3` for Session). + pub discriminator: u8, // 1 + /// Bump seed for this PDA. + pub bump: u8, // 1 + /// Account Version. + pub version: u8, // 1 + /// Padding for alignment. + pub _padding: [u8; 5], // 5 + /// The wallet this session belongs to. + pub wallet: Pubkey, // 32 + /// The ephemeral public key authorized to sign. + pub session_key: Pubkey, // 32 + /// Absolute slot height when this session expires. + pub expires_at: u64, // 8 +} diff --git a/program/src/state/wallet.rs b/program/src/state/wallet.rs new file mode 100644 index 0000000..c9f5e4d --- /dev/null +++ b/program/src/state/wallet.rs @@ -0,0 +1,16 @@ +use no_padding::NoPadding; + +// Main Wallet Account. +// Acts as the trust anchor. Assets are stored in the separate Vault PDA. +#[repr(C, align(8))] +#[derive(NoPadding)] +pub struct WalletAccount { + /// Account discriminator (must be `1` for Wallet). + pub discriminator: u8, + /// Bump seed for this PDA. + pub bump: u8, + /// Account Version. + pub version: u8, + /// Padding for alignment. + pub _padding: [u8; 5], +} diff --git a/program/src/utils.rs b/program/src/utils.rs new file mode 100644 index 0000000..b799f7e --- /dev/null +++ b/program/src/utils.rs @@ -0,0 +1,228 @@ +use pinocchio::{ + account_info::AccountInfo, + instruction::{AccountMeta, Instruction, Seed, Signer}, + program::invoke_signed, + program_error::ProgramError, + pubkey::Pubkey, + ProgramResult, +}; + +/// System Program ID (11111111111111111111111111111111) +pub const SYSTEM_PROGRAM_ID: [u8; 32] = [0u8; 32]; + +/// Wrapper around the `sol_get_stack_height` syscall +pub fn get_stack_height() -> u64 { + #[cfg(target_os = "solana")] + unsafe { + pinocchio::syscalls::sol_get_stack_height() + } + #[cfg(not(target_os = "solana"))] + 0 +} + +/// Safely initializes a PDA account using transfer-allocate-assign pattern. +/// +/// This prevents DoS attacks where malicious actors pre-fund target accounts +/// with small amounts of lamports, causing the System Program's `create_account` +/// instruction to fail (since it rejects accounts with non-zero balances). +/// +/// The transfer-allocate-assign pattern works in three steps: +/// 1. **Transfer**: Add lamports to reach rent-exemption (if needed) +/// 2. **Allocate**: Set the account's data size +/// 3. **Assign**: Transfer ownership to the target program +/// +/// # Security +/// - Prevents Issue #4: Create Account DoS vulnerability +/// - Still enforces rent-exemption requirements +/// - Properly assigns ownership to prevent unauthorized access +/// - Works even if account is pre-funded by attacker +/// +/// # Arguments +/// * `payer` - Account paying for initialization (must be signer & writable) +/// * `target_pda` - PDA being initialized (will be writable) +/// * `system_program` - System Program account +/// * `space` - Number of bytes to allocate for account data +/// * `rent_lamports` - Minimum lamports for rent-exemption +/// * `owner` - Program that will own this account +/// * `pda_seeds` - Seeds for PDA signing (for allocate & assign) +/// +/// # Errors +/// Returns ProgramError if: +/// - Payer has insufficient funds +/// - Any CPI call fails +/// - Account is already owned by another program +pub fn initialize_pda_account( + payer: &AccountInfo, + target_pda: &AccountInfo, + system_program: &AccountInfo, + space: usize, + rent_lamports: u64, + owner: &Pubkey, + pda_seeds: &[Seed], +) -> ProgramResult { + // Validate System Program ID + if system_program.key() != &SYSTEM_PROGRAM_ID { + return Err(ProgramError::IncorrectProgramId); + } + + let current_balance = target_pda.lamports(); + + // Step 1: Transfer lamports if needed to reach rent-exemption + if current_balance < rent_lamports { + let transfer_amount = rent_lamports + .checked_sub(current_balance) + .ok_or(ProgramError::ArithmeticOverflow)?; + + // System Program Transfer instruction (discriminator: 2) + let mut transfer_data = Vec::with_capacity(12); + transfer_data.extend_from_slice(&2u32.to_le_bytes()); + transfer_data.extend_from_slice(&transfer_amount.to_le_bytes()); + + let transfer_accounts = [ + AccountMeta { + pubkey: payer.key(), + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: target_pda.key(), + is_signer: false, + is_writable: true, + }, + ]; + + let transfer_ix = Instruction { + program_id: &Pubkey::from(SYSTEM_PROGRAM_ID), + accounts: &transfer_accounts, + data: &transfer_data, + }; + + pinocchio::program::invoke(&transfer_ix, &[&payer, &target_pda, &system_program])?; + } + + // Step 2: Allocate space + // System Program Allocate instruction (discriminator: 8) + let mut allocate_data = Vec::with_capacity(12); + allocate_data.extend_from_slice(&8u32.to_le_bytes()); + allocate_data.extend_from_slice(&(space as u64).to_le_bytes()); + + let allocate_accounts = [AccountMeta { + pubkey: target_pda.key(), + is_signer: true, + is_writable: true, + }]; + + let allocate_ix = Instruction { + program_id: &Pubkey::from(SYSTEM_PROGRAM_ID), + accounts: &allocate_accounts, + data: &allocate_data, + }; + + let signer: Signer = pda_seeds.into(); + invoke_signed(&allocate_ix, &[&target_pda, &system_program], &[signer])?; + + // Step 3: Assign ownership to target program + // System Program Assign instruction (discriminator: 1) + let mut assign_data = Vec::with_capacity(36); + assign_data.extend_from_slice(&1u32.to_le_bytes()); + assign_data.extend_from_slice(owner.as_ref()); + + let assign_accounts = [AccountMeta { + pubkey: target_pda.key(), + is_signer: true, + is_writable: true, + }]; + + let assign_ix = Instruction { + program_id: &Pubkey::from(SYSTEM_PROGRAM_ID), + accounts: &assign_accounts, + data: &assign_data, + }; + + let signer: Signer = pda_seeds.into(); + invoke_signed(&assign_ix, &[&target_pda, &system_program], &[signer])?; + + Ok(()) +} + +/// Maps a pubkey to a shard ID deterministically and stably across platforms. +pub fn hash_pubkey_to_shard(pubkey: &Pubkey, num_shards: u8) -> u8 { + if num_shards == 0 { + return 0; // Fallback, though config should ensure num_shards >= 1 + } + let mut sum: u32 = 0; + for &b in pubkey.as_ref() { + sum = sum.wrapping_add(b as u32); + } + (sum % (num_shards as u32)) as u8 +} + +/// Collects the protocol fee from the payer and transfers it to the assigned treasury shard. +/// +/// # Arguments +/// * `payer` - Account paying for the fee (must be signer & writable) +/// * `config_account` - The global config account data to read fees/shards from +/// * `treasury_shard` - The pre-initialized treasury shard PDA receiving the fee (must be writable) +/// * `system_program` - System Program account +/// * `is_wallet_creation` - If true, applies `wallet_fee`, otherwise `action_fee` +pub fn collect_protocol_fee( + program_id: &Pubkey, + payer: &AccountInfo, + config_account: &crate::state::config::ConfigAccount, + treasury_shard: &AccountInfo, + system_program: &AccountInfo, + is_wallet_creation: bool, +) -> ProgramResult { + let fee = if is_wallet_creation { + config_account.wallet_fee + } else { + config_account.action_fee + }; + + if fee == 0 { + return Ok(()); // Free action + } + + // Verify system program + if system_program.key() != &SYSTEM_PROGRAM_ID { + return Err(ProgramError::IncorrectProgramId); + } + + // Verify Treasury Shard is the correct one for this payer + let shard_id = hash_pubkey_to_shard(payer.key(), config_account.num_shards); + let shard_id_bytes = [shard_id]; + let (expected_shard_key, _bump) = + pinocchio::pubkey::find_program_address(&[b"treasury", &shard_id_bytes], program_id); + + if treasury_shard.key() != &expected_shard_key { + return Err(ProgramError::InvalidSeeds); + } + + // System Program Transfer instruction (discriminator: 2) + let mut transfer_data = Vec::with_capacity(12); + transfer_data.extend_from_slice(&2u32.to_le_bytes()); + transfer_data.extend_from_slice(&fee.to_le_bytes()); + + let transfer_accounts = [ + AccountMeta { + pubkey: payer.key(), + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: treasury_shard.key(), + is_signer: false, + is_writable: true, // Shards must be writable + }, + ]; + + let transfer_ix = Instruction { + program_id: &Pubkey::from(SYSTEM_PROGRAM_ID), + accounts: &transfer_accounts, + data: &transfer_data, + }; + + pinocchio::program::invoke(&transfer_ix, &[&payer, &treasury_shard, &system_program])?; + + Ok(()) +} diff --git a/program/tests/account_binding_tests.rs b/program/tests/account_binding_tests.rs new file mode 100644 index 0000000..565d038 --- /dev/null +++ b/program/tests/account_binding_tests.rs @@ -0,0 +1,151 @@ +//! Tests for Issue #11: Account Binding in Signed Payload +//! +//! These tests verify that reordering accounts in a transaction produces +//! a different hash, which will invalidate the signature. + +use sha2::{Digest, Sha256}; + +/// Simulates the compute_accounts_hash logic for testing purposes +/// This mirrors the on-chain implementation in execute.rs +fn compute_accounts_hash_test( + account_pubkeys: &[[u8; 32]], + compact_instructions: &[(u8, Vec)], // (program_id_index, account_indices) +) -> [u8; 32] { + let mut pubkeys_data = Vec::new(); + + for (program_idx, accounts) in compact_instructions { + // Include program_id + pubkeys_data.extend_from_slice(&account_pubkeys[*program_idx as usize]); + + // Include all account pubkeys + for &acc_idx in accounts { + pubkeys_data.extend_from_slice(&account_pubkeys[acc_idx as usize]); + } + } + + // Compute SHA256 hash + let mut hasher = Sha256::new(); + hasher.update(&pubkeys_data); + hasher.finalize().into() +} + +#[test] +fn test_same_indices_same_accounts_same_hash() { + // Setup: 3 accounts + let accounts = [ + [1u8; 32], // Program + [2u8; 32], // UserA + [3u8; 32], // UserB + ]; + + let instructions = vec![(0u8, vec![1u8, 2u8])]; // Transfer from 1 to 2 + + let hash1 = compute_accounts_hash_test(&accounts, &instructions); + let hash2 = compute_accounts_hash_test(&accounts, &instructions); + + assert_eq!(hash1, hash2, "Same accounts should produce same hash"); +} + +#[test] +fn test_reordered_accounts_different_hash() { + // Original order: [Program, UserA, UserB] + let accounts_original = [ + [1u8; 32], // Program at index 0 + [2u8; 32], // UserA at index 1 + [3u8; 32], // UserB at index 2 + ]; + + // Attacker reorders: [Program, UserB, UserA] + let accounts_reordered = [ + [1u8; 32], // Program at index 0 (unchanged) + [3u8; 32], // UserB at index 1 (was UserA!) + [2u8; 32], // UserA at index 2 (was UserB!) + ]; + + // Same compact instruction indices + let instructions = vec![(0u8, vec![1u8, 2u8])]; // Transfer from index 1 to index 2 + + let hash_original = compute_accounts_hash_test(&accounts_original, &instructions); + let hash_reordered = compute_accounts_hash_test(&accounts_reordered, &instructions); + + // Issue #11 Fix: Reordered accounts MUST produce different hash + assert_ne!( + hash_original, hash_reordered, + "Reordered accounts MUST produce different hash (Issue #11 fix)" + ); + + println!("Original hash: {:?}", &hash_original[..8]); + println!("Reordered hash: {:?}", &hash_reordered[..8]); +} + +#[test] +fn test_attack_scenario_transfer_recipient_swap() { + // Scenario: User intends to transfer 1 SOL to UserA and 100 SOL to UserB + // Attacker swaps UserA and UserB positions + + let program = [0u8; 32]; + let user_a = [0xAA; 32]; + let user_b = [0xBB; 32]; + let payer = [0xFF; 32]; + + // User's intended account order + let user_intended = [program, payer, user_a, user_b]; + // Instructions: Transfer to accounts[2] (UserA), Transfer to accounts[3] (UserB) + let instructions = vec![ + (0u8, vec![1u8, 2u8]), // Transfer from payer to UserA + (0u8, vec![1u8, 3u8]), // Transfer from payer to UserB + ]; + + let user_hash = compute_accounts_hash_test(&user_intended, &instructions); + + // Attacker's reordered accounts (swap UserA and UserB) + let attacker_reordered = [program, payer, user_b, user_a]; // Swapped! + + let attacker_hash = compute_accounts_hash_test(&attacker_reordered, &instructions); + + // The hashes MUST be different, invalidating the signature + assert_ne!( + user_hash, attacker_hash, + "Attack detected: Account swap must invalidate signature" + ); + + println!("✅ Issue #11 Attack Prevention Verified"); + println!( + " User intended hash: {:02x}{:02x}{:02x}{:02x}...", + user_hash[0], user_hash[1], user_hash[2], user_hash[3] + ); + println!( + " Attacker reorder hash: {:02x}{:02x}{:02x}{:02x}...", + attacker_hash[0], attacker_hash[1], attacker_hash[2], attacker_hash[3] + ); +} + +#[test] +fn test_multiple_instructions_hash_binding() { + let accounts = [ + [0u8; 32], // Program 0 + [1u8; 32], // Account 1 + [2u8; 32], // Account 2 + [3u8; 32], // Account 3 + ]; + + // Multiple instructions using different accounts + let instructions = vec![ + (0u8, vec![1u8, 2u8]), + (0u8, vec![2u8, 3u8]), + (0u8, vec![1u8, 3u8]), + ]; + + let hash = compute_accounts_hash_test(&accounts, &instructions); + + // Verify hash is deterministic + let hash2 = compute_accounts_hash_test(&accounts, &instructions); + assert_eq!(hash, hash2); + + // Verify different account produces different hash + let mut modified_accounts = accounts; + modified_accounts[2] = [0xFF; 32]; // Change account 2 + + let hash_modified = compute_accounts_hash_test(&modified_accounts, &instructions); + assert_ne!(hash, hash_modified, "Modified account must change hash"); +} diff --git a/program/tests/closing_tests.rs b/program/tests/closing_tests.rs new file mode 100644 index 0000000..34bccfd --- /dev/null +++ b/program/tests/closing_tests.rs @@ -0,0 +1,220 @@ +mod common; + +use common::*; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + message::{v0, VersionedMessage}, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + transaction::VersionedTransaction, +}; + +#[test] +fn test_close_wallet_lifecycle() { + let mut context = setup_test(); + + // 1. Create Wallet + let user_seed = rand::random::<[u8; 32]>(); + let owner_keypair = Keypair::new(); + let (wallet_pda, _) = Pubkey::find_program_address(&[b"wallet", &user_seed], &context.program_id); + let (vault_pda, _) = Pubkey::find_program_address(&[b"vault", wallet_pda.as_ref()], &context.program_id); + let (owner_auth_pda, owner_bump) = Pubkey::find_program_address( + &[b"authority", wallet_pda.as_ref(), owner_keypair.pubkey().as_ref()], + &context.program_id, + ); + + let (config_pda, _) = Pubkey::find_program_address(&[b"config"], &context.program_id); + let (treasury_pda, _) = Pubkey::find_program_address(&[b"treasury", &[0]], &context.program_id); + + { + let mut create_data = vec![0]; // CreateWallet discriminator + create_data.extend_from_slice(&user_seed); // 32 bytes + create_data.push(0); // auth_type = Ed25519 (byte 33) + create_data.push(owner_bump); // auth_bump (byte 34) + create_data.extend_from_slice(&[0; 6]); // padding (bytes 35-40) + create_data.extend_from_slice(owner_keypair.pubkey().as_ref()); // id_seed (32 bytes) + + let create_wallet_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(owner_auth_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: create_data, + }; + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &context.payer.pubkey(), + &[create_wallet_ix], + &[], + context.svm.latest_blockhash(), + ).unwrap()), + &[&context.payer], + ).unwrap(); + context.svm.send_transaction(tx).expect("CreateWallet failed"); + } + + // 2. Close Wallet + let destination = Keypair::new(); + + let close_wallet_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new_readonly(owner_auth_pda, false), + AccountMeta::new(destination.pubkey(), false), + AccountMeta::new_readonly(owner_keypair.pubkey(), true), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + ], + data: vec![9], // CloseWallet + }; + + let tx_close = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &context.payer.pubkey(), + &[close_wallet_ix], + &[], + context.svm.latest_blockhash(), + ).unwrap()), + &[&context.payer, &owner_keypair], + ).unwrap(); + + context.svm.send_transaction(tx_close).expect("CloseWallet failed"); + + // Verify accounts closed + assert!(context.svm.get_account(&wallet_pda).is_none() || context.svm.get_account(&wallet_pda).unwrap().lamports == 0); + assert!(context.svm.get_account(&vault_pda).is_none() || context.svm.get_account(&vault_pda).unwrap().lamports == 0); + println!("✅ Wallet and Vault closed successfully"); +} + +#[test] +fn test_close_session_lifecycle() { + let mut context = setup_test(); + + // 1. Create Wallet + let user_seed = rand::random::<[u8; 32]>(); + let owner_keypair = Keypair::new(); + let (wallet_pda, _) = Pubkey::find_program_address(&[b"wallet", &user_seed], &context.program_id); + let (vault_pda, _) = Pubkey::find_program_address(&[b"vault", wallet_pda.as_ref()], &context.program_id); + let (owner_auth_pda, owner_bump) = Pubkey::find_program_address( + &[b"authority", wallet_pda.as_ref(), owner_keypair.pubkey().as_ref()], + &context.program_id, + ); + let (config_pda, _) = Pubkey::find_program_address(&[b"config"], &context.program_id); + let (treasury_pda, _) = Pubkey::find_program_address(&[b"treasury", &[0]], &context.program_id); + + // Create Wallet + { + let mut create_data = vec![0]; + create_data.extend_from_slice(&user_seed); + create_data.push(0); // Ed25519 + create_data.push(owner_bump); + create_data.extend_from_slice(&[0; 6]); + create_data.extend_from_slice(owner_keypair.pubkey().as_ref()); + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &context.payer.pubkey(), + &[Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(owner_auth_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: create_data, + }], + &[], + context.svm.latest_blockhash(), + ).unwrap()), + &[&context.payer], + ).unwrap(); + context.svm.send_transaction(tx).expect("CreateWallet failed"); + } + + // 2. Create Session + let session_key = Keypair::new(); + let (session_pda, _) = Pubkey::find_program_address( + &[b"session", wallet_pda.as_ref(), session_key.pubkey().as_ref()], + &context.program_id, + ); + + { + let mut session_data = vec![5]; // CreateSession + session_data.extend_from_slice(session_key.pubkey().as_ref()); + session_data.extend_from_slice(&2000000000u64.to_le_bytes()); // far future expiration + + let create_session_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new_readonly(wallet_pda, false), + AccountMeta::new_readonly(owner_auth_pda, false), + AccountMeta::new(session_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(treasury_pda, false), + AccountMeta::new_readonly(owner_keypair.pubkey(), true), + ], + data: session_data, + }; + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &context.payer.pubkey(), + &[create_session_ix], + &[], + context.svm.latest_blockhash(), + ).unwrap()), + &[&context.payer, &owner_keypair], + ).unwrap(); + context.svm.send_transaction(tx).expect("CreateSession failed"); + } + + // 3. Close Session + { + let close_session_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), // receives refund + AccountMeta::new_readonly(wallet_pda, false), + AccountMeta::new(session_pda, false), + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new_readonly(owner_auth_pda, false), // optional but used for owner check + AccountMeta::new_readonly(owner_keypair.pubkey(), true), + ], + data: vec![8], // CloseSession + }; + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &context.payer.pubkey(), + &[close_session_ix], + &[], + context.svm.latest_blockhash(), + ).unwrap()), + &[&context.payer, &owner_keypair], + ).unwrap(); + context.svm.send_transaction(tx).expect("CloseSession failed"); + } + + // Verify session closed + assert!(context.svm.get_account(&session_pda).is_none() || context.svm.get_account(&session_pda).unwrap().lamports == 0); + println!("✅ Session closed successfully"); +} diff --git a/program/tests/common/mod.rs b/program/tests/common/mod.rs new file mode 100644 index 0000000..51185dd --- /dev/null +++ b/program/tests/common/mod.rs @@ -0,0 +1,89 @@ +use litesvm::LiteSVM; +use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; + +pub struct TestContext { + pub svm: LiteSVM, + pub payer: Keypair, + pub program_id: Pubkey, +} + +pub fn setup_test() -> TestContext { + let payer = Keypair::new(); + let mut svm = LiteSVM::new(); + + // Airdrop to payer + svm.airdrop(&payer.pubkey(), 10_000_000_000) + .expect("Failed to airdrop"); + + // Load program + let program_id = load_program(&mut svm); + + // Initialize a zero-fee Config PDA and a single Treasury shard (id 0) + // so that protocol fee logic in tests has valid accounts to read from. + { + use lazorkit_program::state::{config::ConfigAccount, AccountDiscriminator, CURRENT_ACCOUNT_VERSION}; + use solana_sdk::account::Account; + + let (config_pda, _) = + Pubkey::find_program_address(&[b"config"], &program_id); + let shard_id: u8 = 0; + let shard_id_bytes = [shard_id]; + let (treasury_pda, _) = + Pubkey::find_program_address(&[b"treasury", &shard_id_bytes], &program_id); + + let config_data = ConfigAccount { + discriminator: AccountDiscriminator::Config as u8, + bump: 0, + version: CURRENT_ACCOUNT_VERSION, + num_shards: 1, + _padding: [0; 4], + admin: payer.pubkey().to_bytes().into(), + wallet_fee: 0, + action_fee: 0, + }; + + let mut config_bytes = vec![0u8; std::mem::size_of::()]; + unsafe { + std::ptr::write_unaligned( + config_bytes.as_mut_ptr() as *mut ConfigAccount, + config_data, + ); + } + + let config_account = Account { + lamports: 100_000_000, // Enough for rent + data: config_bytes, + owner: program_id, + executable: false, + rent_epoch: 0, + }; + let _ = svm.set_account(config_pda, config_account); + + let treasury_account = Account { + lamports: 100_000_000, + data: vec![], + owner: solana_sdk::system_program::id(), + executable: false, + rent_epoch: 0, + }; + let _ = svm.set_account(treasury_pda, treasury_account); + } + + TestContext { + svm, + payer, + program_id, + } +} + +fn load_program(svm: &mut LiteSVM) -> Pubkey { + // LazorKit program ID (deterministic for tests) + let program_id = Pubkey::new_unique(); + + // Load the compiled program + let path = "../target/deploy/lazorkit_program.so"; + svm.add_program_from_file(program_id, path) + .expect("Failed to load program"); + + program_id +} diff --git a/program/tests/config_tests.rs b/program/tests/config_tests.rs new file mode 100644 index 0000000..fb359e1 --- /dev/null +++ b/program/tests/config_tests.rs @@ -0,0 +1,121 @@ +mod common; + +use common::*; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + message::{v0, VersionedMessage}, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + transaction::VersionedTransaction, +}; + +#[test] +fn test_config_lifecycle() { + let mut context = setup_test(); + + // 1. Verify Config PDA was initialized in setup_test + let (config_pda, _) = Pubkey::find_program_address(&[b"config"], &context.program_id); + let config_account = context.svm.get_account(&config_pda); + assert!(config_account.is_some(), "Config should be initialized"); + + // 2. Update Config (Normal case) + let new_admin = Keypair::new(); + let new_wallet_fee = 50000u64; + let new_action_fee = 5000u64; + + // args: update_wallet_fee(1), update_action_fee(1), update_num_shards(1), update_admin(1), num_shards(16) + // padding(3), wallet_fee(8), action_fee(8), admin(32) + let mut update_data = vec![7]; // discriminator + update_data.extend_from_slice(&[1, 1, 1, 1, 16]); // updates + num_shards + update_data.extend_from_slice(&[0, 0, 0]); // padding + update_data.extend_from_slice(&new_wallet_fee.to_le_bytes()); + update_data.extend_from_slice(&new_action_fee.to_le_bytes()); + update_data.extend_from_slice(new_admin.pubkey().as_ref()); + + let update_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(config_pda, false), + ], + data: update_data, + }; + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &context.payer.pubkey(), + &[update_ix], + &[], + context.svm.latest_blockhash(), + ).unwrap()), + &[&context.payer], + ).unwrap(); + + context.svm.send_transaction(tx).expect("UpdateConfig failed"); + println!("✅ Config updated successfully"); + + // 3. Reject update from unauthorized payer (non-admin) + let hacker = Keypair::new(); + context.svm.airdrop(&hacker.pubkey(), 1_000_000_000).unwrap(); + + let mut update_data_hacker = vec![7]; + update_data_hacker.extend_from_slice(&[1, 0, 0, 0, 16, 0, 0, 0]); + update_data_hacker.extend_from_slice(&999999u64.to_le_bytes()); + update_data_hacker.extend_from_slice(&0u64.to_le_bytes()); + update_data_hacker.extend_from_slice(&[0; 32]); + + let update_ix_hacker = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(hacker.pubkey(), true), + AccountMeta::new(config_pda, false), + ], + data: update_data_hacker, + }; + + let tx_hacker = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &hacker.pubkey(), + &[update_ix_hacker], + &[], + context.svm.latest_blockhash(), + ).unwrap()), + &[&hacker], + ).unwrap(); + + let res = context.svm.send_transaction(tx_hacker); + assert!(res.is_err(), "Hacker should not be able to update config"); + println!("✅ Unauthorized update rejected"); + + // 4. Test InitTreasuryShard + let shard_id = 5; + let shard_id_bytes = [shard_id]; + let (shard_pda, _) = Pubkey::find_program_address(&[b"treasury", &shard_id_bytes], &context.program_id); + + let init_shard_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(shard_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + ], + data: vec![11, shard_id], + }; + + let tx_shard = VersionedTransaction::try_new( + VersionedMessage::V0(v0::Message::try_compile( + &context.payer.pubkey(), + &[init_shard_ix], + &[], + context.svm.latest_blockhash(), + ).unwrap()), + &[&context.payer], + ).unwrap(); + + context.svm.send_transaction(tx_shard).expect("InitTreasuryShard failed"); + assert!(context.svm.get_account(&shard_pda).is_some(), "Shard should exist"); + println!("✅ Treasury shard initialized"); +} diff --git a/program/tests/cross_wallet_and_fee_attacks.rs b/program/tests/cross_wallet_and_fee_attacks.rs new file mode 100644 index 0000000..e560e28 --- /dev/null +++ b/program/tests/cross_wallet_and_fee_attacks.rs @@ -0,0 +1,193 @@ +mod common; + +use common::*; +use lazorkit_program::state::config::ConfigAccount; +use lazorkit_program::state::AccountDiscriminator; +use lazorkit_program::state::CURRENT_ACCOUNT_VERSION; +use solana_sdk::account::Account; +use solana_sdk::instruction::{AccountMeta, Instruction}; +use solana_sdk::message::{v0, VersionedMessage}; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::transaction::VersionedTransaction; + +fn init_zero_fee_config_and_shard(context: &mut TestContext) -> (Pubkey, Pubkey) { + let (config_pda, _) = Pubkey::find_program_address(&[b"config"], &context.program_id); + let shard_id: u8 = 0; + let shard_id_bytes = [shard_id]; + let (treasury_pda, _) = + Pubkey::find_program_address(&[b"treasury", &shard_id_bytes], &context.program_id); + + // Minimal config with zero fees and 1 shard. + let config_data = ConfigAccount { + discriminator: AccountDiscriminator::Config as u8, + bump: 0, + version: CURRENT_ACCOUNT_VERSION, + num_shards: 1, + _padding: [0; 4], + admin: context.payer.pubkey().to_bytes(), + wallet_fee: 0, + action_fee: 0, + }; + let mut config_bytes = vec![0u8; std::mem::size_of::()]; + unsafe { + std::ptr::write_unaligned(config_bytes.as_mut_ptr() as *mut ConfigAccount, config_data); + } + let config_account = Account { + lamports: 1, + data: config_bytes, + owner: context.program_id, + executable: false, + rent_epoch: 0, + }; + let _ = context.svm.set_account(config_pda, config_account); + + let treasury_account = Account { + lamports: 1_000_000, + data: vec![], + owner: solana_sdk::system_program::id(), + executable: false, + rent_epoch: 0, + }; + let _ = context.svm.set_account(treasury_pda, treasury_account); + + (config_pda, treasury_pda) +} + +/// Attack 1: Cross-wallet authority abuse +/// Try to use an authority PDA from Wallet A to execute on Wallet B. +#[test] +fn test_execute_rejects_cross_wallet_authority() { + let mut context = setup_test(); + let (config_pda, treasury_pda) = init_zero_fee_config_and_shard(&mut context); + + // Wallet A + authority + let user_seed_a = rand::random::<[u8; 32]>(); + let owner_a = Keypair::new(); + let (wallet_a, _) = + Pubkey::find_program_address(&[b"wallet", &user_seed_a], &context.program_id); + let (vault_a, _) = + Pubkey::find_program_address(&[b"vault", wallet_a.as_ref()], &context.program_id); + let (auth_a, auth_a_bump) = Pubkey::find_program_address( + &[b"authority", wallet_a.as_ref(), owner_a.pubkey().as_ref()], + &context.program_id, + ); + + // Wallet B (no authority yet) + let user_seed_b = rand::random::<[u8; 32]>(); + let (wallet_b, _) = + Pubkey::find_program_address(&[b"wallet", &user_seed_b], &context.program_id); + let (vault_b, _) = + Pubkey::find_program_address(&[b"vault", wallet_b.as_ref()], &context.program_id); + + // Create wallet A (owner_a) + { + let mut data = Vec::new(); + data.extend_from_slice(&user_seed_a); + data.push(0); // Ed25519 + data.push(auth_a_bump); + data.extend_from_slice(&[0; 6]); + data.extend_from_slice(owner_a.pubkey().as_ref()); + + let ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_a, false), + AccountMeta::new(vault_a, false), + AccountMeta::new(auth_a, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: { + let mut full = vec![0]; + full.extend_from_slice(&data); + full + }, + }; + let msg = v0::Message::try_compile( + &context.payer.pubkey(), + &[ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = + VersionedTransaction::try_new(VersionedMessage::V0(msg), &[&context.payer]).unwrap(); + context.svm.send_transaction(tx).expect("create A"); + } + + // Create wallet B (no authorities) + { + let mut data = Vec::new(); + data.extend_from_slice(&user_seed_b); + data.push(0); // Ed25519 + data.push(0); // dummy bump + data.extend_from_slice(&[0; 6]); + data.extend_from_slice(owner_a.pubkey().as_ref()); // reuse pubkey just for seed + + let (_auth_b_pda, _) = Pubkey::find_program_address( + &[b"authority", wallet_b.as_ref(), owner_a.pubkey().as_ref()], + &context.program_id, + ); + + let ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_b, false), + AccountMeta::new(vault_b, false), + AccountMeta::new(_auth_b_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: { + let mut full = vec![0]; + full.extend_from_slice(&data); + full + }, + }; + let msg = v0::Message::try_compile( + &context.payer.pubkey(), + &[ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = + VersionedTransaction::try_new(VersionedMessage::V0(msg), &[&context.payer]).unwrap(); + // This may succeed or fail depending on seeds; not critical for this test. + let _ = context.svm.send_transaction(tx); + } + + // Attempt to Execute on wallet B using authority from wallet A. + let execute_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), // payer + AccountMeta::new(wallet_b, false), // wallet B + AccountMeta::new(auth_a, false), // authority from wallet A + AccountMeta::new(vault_b, false), // vault B + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + ], + data: vec![4, 0], // Execute discriminator + 0 compact instructions + }; + + let msg = v0::Message::try_compile( + &context.payer.pubkey(), + &[execute_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = VersionedTransaction::try_new(VersionedMessage::V0(msg), &[&context.payer]).unwrap(); + let res = context.svm.send_transaction(tx); + assert!(res.is_err(), "Cross-wallet authority should be rejected"); +} diff --git a/program/tests/nonce_integration_tests.rs b/program/tests/nonce_integration_tests.rs new file mode 100644 index 0000000..dd78293 --- /dev/null +++ b/program/tests/nonce_integration_tests.rs @@ -0,0 +1,236 @@ +mod common; +use common::*; +use p256::ecdsa::{SigningKey, VerifyingKey}; +use sha2::Digest; +use solana_sdk::{ + account::Account, + instruction::{AccountMeta, Instruction}, + message::{v0, VersionedMessage}, + pubkey::Pubkey, + signature::Signer as SolanaSigner, + transaction::VersionedTransaction, +}; + +#[test] +fn test_nonce_slot_truncation_fix() { + let mut context = setup_test(); + + // 1. Setup Wallet with Secp256r1 Authority + let mut rng = rand::thread_rng(); + let signing_key = SigningKey::random(&mut rng); + let verifying_key = VerifyingKey::from(&signing_key); + let pubkey_bytes = verifying_key.to_encoded_point(true).as_bytes().to_vec(); // 33 bytes compressed + + let rp_id = "lazorkit.test"; + let rp_id_bytes = rp_id.as_bytes(); + let rp_id_len = rp_id_bytes.len() as u8; + + let mut hasher = sha2::Sha256::new(); + hasher.update(rp_id_bytes); + let rp_id_hash = hasher.finalize(); + let credential_hash: [u8; 32] = rp_id_hash.into(); + + let credential_id_hash = [5u8; 32]; + let user_seed = rand::random::<[u8; 32]>(); + + // Derive PDAs + let (wallet_pda, _) = + Pubkey::find_program_address(&[b"wallet", &user_seed], &context.program_id); + let (vault_pda, _) = + Pubkey::find_program_address(&[b"vault", wallet_pda.as_ref()], &context.program_id); + let (auth_pda, auth_bump) = Pubkey::find_program_address( + &[b"authority", wallet_pda.as_ref(), &credential_id_hash], + &context.program_id, + ); + + // Derive Config PDA and a Treasury Shard PDA for the payer (shard 0 for tests) + let (config_pda, _) = + Pubkey::find_program_address(&[b"config"], &context.program_id); + let shard_id: u8 = 0; + let shard_id_bytes = [shard_id]; + let (treasury_pda, _) = + Pubkey::find_program_address(&[b"treasury", &shard_id_bytes], &context.program_id); + + // Initialize Config and Treasury shard accounts in the LiteSVM context so that + // CreateWallet can charge protocol fees without failing. + { + use solana_sdk::account::Account; + use lazorkit_program::state::{config::ConfigAccount, AccountDiscriminator, CURRENT_ACCOUNT_VERSION}; + + // Minimal ConfigAccount with 1 shard and zero fees for this focused test. + let config_data = ConfigAccount { + discriminator: AccountDiscriminator::Config as u8, + bump: 0, + version: CURRENT_ACCOUNT_VERSION, + num_shards: 1, + _padding: [0; 4], + admin: context.payer.pubkey().to_bytes(), + wallet_fee: 0, + action_fee: 0, + }; + + let mut config_bytes = vec![0u8; std::mem::size_of::()]; + unsafe { + std::ptr::write_unaligned( + config_bytes.as_mut_ptr() as *mut ConfigAccount, + config_data, + ); + } + + let config_account = Account { + lamports: 1, + data: config_bytes, + owner: context.program_id, + executable: false, + rent_epoch: 0, + }; + let _ = context.svm.set_account(config_pda, config_account); + + // Treasury shard as a simple system-owned account with some lamports (no data). + let treasury_account = Account { + lamports: 1_000_000, + data: vec![], + owner: solana_sdk::system_program::id(), + executable: false, + rent_epoch: 0, + }; + let _ = context.svm.set_account(treasury_pda, treasury_account); + } + + // CreateWallet + { + let mut instruction_data = Vec::new(); + instruction_data.extend_from_slice(&user_seed); + instruction_data.push(1); // Secp256r1 + instruction_data.push(auth_bump); + instruction_data.extend_from_slice(&[0; 6]); // padding + instruction_data.extend_from_slice(&credential_id_hash); + instruction_data.extend_from_slice(&pubkey_bytes); + + let ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(auth_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: { + let mut data = vec![0]; // CreateWallet + data.extend_from_slice(&instruction_data); + data + }, + }; + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0( + v0::Message::try_compile( + &context.payer.pubkey(), + &[ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(), + ), + &[&context.payer], + ) + .unwrap(); + context + .svm + .send_transaction(tx) + .expect("CreateWallet failed"); + } + + // 2. Manipulate SysvarSlotHashes to simulate a specific slot history + let current_slot = 10050u64; + let spoof_slot = 9050u64; // Collides with 10050 if truncated by 1000 + + let mut slot_hashes_data = Vec::new(); + let history_len = 512u64; + slot_hashes_data.extend_from_slice(&history_len.to_le_bytes()); // length + + for i in 0..history_len { + let h = current_slot - i; + slot_hashes_data.extend_from_slice(&h.to_le_bytes()); + slot_hashes_data.extend_from_slice(&[0u8; 32]); // Dummy hashes + } + + let slothashes_pubkey = solana_sdk::sysvar::slot_hashes::ID; + let account = Account { + lamports: 1, + data: slot_hashes_data, + owner: solana_sdk::sysvar::id(), + executable: false, + rent_epoch: 0, + }; + let _ = context.svm.set_account(slothashes_pubkey, account); + + // 3. Construct Auth Payload pointing to spoof slot + // Indices in the Execute accounts list (defined below) + // 0: payer, 1: wallet, 2: authority, 3: vault, + // 4: config, 5: treasury_shard, 6: system_program, + // 7: sysvar_instructions, 8: slot_hashes + let ix_sysvar_idx = 7u8; + let slothashes_sysvar_idx = 8u8; + + let mut authenticator_data = Vec::new(); + authenticator_data.extend_from_slice(&credential_hash); // RP ID Hash + authenticator_data.push(0x01); // UP flag + authenticator_data.extend_from_slice(&1u32.to_be_bytes()); // counter + + let mut auth_payload = Vec::new(); + auth_payload.extend_from_slice(&spoof_slot.to_le_bytes()); + auth_payload.push(ix_sysvar_idx); + auth_payload.push(slothashes_sysvar_idx); + auth_payload.push(0); // type_and_flags + auth_payload.push(rp_id_len); + auth_payload.extend_from_slice(rp_id_bytes); + auth_payload.extend_from_slice(&authenticator_data); + + // 4. Construct Execute Instruction + let mut execute_data = vec![4u8]; // Execute discriminator + execute_data.push(0u8); // 0 compact instructions (u8) + execute_data.extend_from_slice(&auth_payload); + + let execute_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), // 0 + AccountMeta::new(wallet_pda, false), // 1 + AccountMeta::new(auth_pda, false), // 2 - Authority + AccountMeta::new(vault_pda, false), // 3 - Vault + AccountMeta::new(config_pda, false), // 4 - Config PDA + AccountMeta::new(treasury_pda, false), // 5 - Treasury shard + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), // 6 - System program + AccountMeta::new_readonly(solana_sdk::sysvar::instructions::id(), false), // 7 - Instructions sysvar + AccountMeta::new_readonly(slothashes_pubkey, false), // 8 - SlotHashes sysvar + ], + data: execute_data, + }; + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0( + v0::Message::try_compile( + &context.payer.pubkey(), + &[execute_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(), + ), + &[&context.payer], + ) + .unwrap(); + + let res = context.svm.send_transaction(tx); + + // We only require that the spoofed nonce is rejected. + // The exact error code may vary depending on additional + // signature validation checks, but a successful transaction + // would indicate a regression in nonce validation. + assert!(res.is_err(), "Spoofed nonce should have been rejected!"); +} diff --git a/program/tests/secp256r1_tests.rs b/program/tests/secp256r1_tests.rs new file mode 100644 index 0000000..8d5c35c --- /dev/null +++ b/program/tests/secp256r1_tests.rs @@ -0,0 +1,321 @@ +mod common; + +use common::*; +use p256::ecdsa::{SigningKey, VerifyingKey}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + message::{v0, VersionedMessage}, + pubkey::Pubkey, + signer::Signer, + transaction::VersionedTransaction, +}; + +#[test] +fn test_create_wallet_secp256r1_repro() { + let mut context = setup_test(); + + // 1. Generate Secp256r1 Key + let mut rng = rand::thread_rng(); + let signing_key = SigningKey::random(&mut rng); + let verifying_key = VerifyingKey::from(&signing_key); + let pubkey_bytes = verifying_key.to_encoded_point(true).as_bytes().to_vec(); // 33 bytes compressed + + // Fake credential ID hash (used as PDA seed) + let credential_id_hash = [2u8; 32]; + + let user_seed = rand::random::<[u8; 32]>(); + + let (wallet_pda, _) = + Pubkey::find_program_address(&[b"wallet", &user_seed], &context.program_id); + let (vault_pda, _) = + Pubkey::find_program_address(&[b"vault", wallet_pda.as_ref()], &context.program_id); + // Authority seed for Secp256r1 is the credential_id_hash + let (auth_pda, auth_bump) = Pubkey::find_program_address( + &[b"authority", wallet_pda.as_ref(), &credential_id_hash], + &context.program_id, + ); + + // Build CreateWallet args + // [user_seed(32)][type(1)][bump(1)][padding(6)][credential_id_hash(32)][pubkey(33)] + let mut instruction_data = Vec::new(); + instruction_data.extend_from_slice(&user_seed); + instruction_data.push(1); // Secp256r1 + instruction_data.push(auth_bump); // Use correct bump + instruction_data.extend_from_slice(&[0; 6]); // padding + + // "rest" part for Secp256r1: credential_id_hash + pubkey + instruction_data.extend_from_slice(&credential_id_hash); + instruction_data.extend_from_slice(&pubkey_bytes); + + // Derive Config + Treasury shard used by CreateWallet fee logic + let (config_pda, _) = + Pubkey::find_program_address(&[b"config"], &context.program_id); + let shard_id: u8 = 0; + let shard_id_bytes = [shard_id]; + let (treasury_pda, _) = + Pubkey::find_program_address(&[b"treasury", &shard_id_bytes], &context.program_id); + + let create_wallet_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(auth_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: { + let mut data = vec![0]; // Discriminator + data.extend_from_slice(&instruction_data); + data + }, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[create_wallet_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + + let tx = + VersionedTransaction::try_new(VersionedMessage::V0(message), &[&context.payer]).unwrap(); + + // This should now SUCCEED + context + .svm + .send_transaction(tx) + .expect("CreateWallet Secp256r1 failed"); + println!("✅ Wallet created with Secp256r1 Authority"); +} + +#[test] +fn test_add_multiple_secp256r1_authorities() { + let mut context = setup_test(); + + // 1. Setup Wallet with First Ed25519 Authority (Owner) + let user_seed = rand::random::<[u8; 32]>(); + let owner_keypair = solana_sdk::signature::Keypair::new(); + let owner_pubkey = owner_keypair.pubkey(); + + let (wallet_pda, _) = + Pubkey::find_program_address(&[b"wallet", &user_seed], &context.program_id); + let (vault_pda, _) = + Pubkey::find_program_address(&[b"vault", wallet_pda.as_ref()], &context.program_id); + let (owner_pda, owner_bump) = Pubkey::find_program_address( + &[b"authority", wallet_pda.as_ref(), owner_pubkey.as_ref()], + &context.program_id, + ); + + { + // Derive Config + Treasury shard for this wallet + let (config_pda, _) = + Pubkey::find_program_address(&[b"config"], &context.program_id); + let shard_id: u8 = 0; + let shard_id_bytes = [shard_id]; + let (treasury_pda, _) = + Pubkey::find_program_address(&[b"treasury", &shard_id_bytes], &context.program_id); + let mut instruction_data = Vec::new(); + instruction_data.extend_from_slice(&user_seed); + instruction_data.push(0); // Ed25519 + instruction_data.push(owner_bump); + instruction_data.extend_from_slice(&[0; 6]); // padding + instruction_data.extend_from_slice(owner_pubkey.as_ref()); + + let create_wallet_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(owner_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: { + let mut data = vec![0]; // CreateWallet + data.extend_from_slice(&instruction_data); + data + }, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[create_wallet_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = VersionedTransaction::try_new(VersionedMessage::V0(message), &[&context.payer]) + .unwrap(); + context + .svm + .send_transaction(tx) + .expect("CreateWallet Ed25519 failed"); + } + + // Passkeys from the SAME domain + let mut rng = rand::thread_rng(); + + // --- Add Passkey 1 --- + let credential_id_hash1 = [3u8; 32]; + let signing_key1 = SigningKey::random(&mut rng); + let pubkey_bytes1 = VerifyingKey::from(&signing_key1) + .to_encoded_point(true) + .as_bytes() + .to_vec(); + let (auth_pda1, _auth_bump1) = Pubkey::find_program_address( + &[b"authority", wallet_pda.as_ref(), &credential_id_hash1], + &context.program_id, + ); + + let add_auth_args = { + let mut args = Vec::new(); + args.push(1); // Secp256r1 (authority_type) + args.push(0); // Owner (new_role) + args.extend_from_slice(&[0; 6]); // padding + args + }; + + let data_payload = { + let mut payload = Vec::new(); + payload.extend_from_slice(&add_auth_args); + payload.extend_from_slice(&credential_id_hash1); + payload.extend_from_slice(&pubkey_bytes1); + payload + }; + + let signature = owner_keypair.sign_message(&data_payload); + let mut add_auth_ix_data = vec![1]; // AddAuthority (discriminator 1) + add_auth_ix_data.extend_from_slice(&add_auth_args); + add_auth_ix_data.extend_from_slice(&credential_id_hash1); + add_auth_ix_data.extend_from_slice(&pubkey_bytes1); + add_auth_ix_data.extend_from_slice(signature.as_ref()); + + // Re-derive Config + Treasury shard (same as used for wallet creation) + let (config_pda, _) = + Pubkey::find_program_address(&[b"config"], &context.program_id); + let shard_id: u8 = 0; + let shard_id_bytes = [shard_id]; + let (treasury_pda, _) = + Pubkey::find_program_address(&[b"treasury", &shard_id_bytes], &context.program_id); + + let add_auth_ix1 = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(owner_pda, false), // Admin Auth PDA (Ed25519) - Writable for counter + AccountMeta::new(auth_pda1, false), // New Auth PDA (Secp256r1) + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + // Config + Treasury shard for protocol fee + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + AccountMeta::new_readonly(owner_keypair.pubkey(), true), // Ed25519 Master Signer (after positional accounts) + ], + data: add_auth_ix_data, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[add_auth_ix1], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(message), + &[&context.payer, &owner_keypair], + ) + .unwrap(); + + context + .svm + .send_transaction(tx) + .expect("AddAuthority Secp256r1 (1) failed"); + println!("✅ Added Secp256r1 Authority 1"); + + // Each passkey has a unique credential_id, producing a unique credential_id_hash. + // This means each passkey derives a unique Authority PDA, preventing collisions. + // --- Add Passkey 2 (Same Domain, Different credential_id) --- + let credential_id_hash2 = [4u8; 32]; + let signing_key2 = SigningKey::random(&mut rng); + let pubkey_bytes2 = VerifyingKey::from(&signing_key2) + .to_encoded_point(true) + .as_bytes() + .to_vec(); + let (auth_pda2, _auth_bump2) = Pubkey::find_program_address( + &[b"authority", wallet_pda.as_ref(), &credential_id_hash2], + &context.program_id, + ); + + let data_payload = { + let mut payload = Vec::new(); + payload.extend_from_slice(&add_auth_args); + payload.extend_from_slice(&credential_id_hash2); + payload.extend_from_slice(&pubkey_bytes2); + payload + }; + + let signature = owner_keypair.sign_message(&data_payload); + let mut add_auth_ix_data = vec![1]; // AddAuthority (discriminator 1) + add_auth_ix_data.extend_from_slice(&add_auth_args); + add_auth_ix_data.extend_from_slice(&credential_id_hash2); + add_auth_ix_data.extend_from_slice(&pubkey_bytes2); + add_auth_ix_data.extend_from_slice(signature.as_ref()); + + let add_auth_ix2 = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(owner_pda, false), // Admin Auth PDA (Ed25519) - Writable for counter + AccountMeta::new(auth_pda2, false), // New Auth PDA (Secp256r1) + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + // Config + Treasury shard for protocol fee + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + AccountMeta::new_readonly(owner_keypair.pubkey(), true), // Ed25519 Master Signer (after positional accounts) + ], + data: add_auth_ix_data, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[add_auth_ix2], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(message), + &[&context.payer, &owner_keypair], + ) + .unwrap(); + + context + .svm + .send_transaction(tx) + .expect("AddAuthority Secp256r1 (2) failed"); + println!("✅ Added Secp256r1 Authority 2"); + + assert_ne!( + pubkey_bytes1[1..33], + pubkey_bytes2[1..33], + "Passkey X-coordinates must be unique" + ); + assert_ne!( + auth_pda1, auth_pda2, + "Authority PDAs must not collide for passkeys on the same domain" + ); + + println!("✅ Passed: Multiple Secp256r1 Authorities from identical domain derive unique PDAs successfully"); +} diff --git a/program/tests/session_tests.rs b/program/tests/session_tests.rs new file mode 100644 index 0000000..681b610 --- /dev/null +++ b/program/tests/session_tests.rs @@ -0,0 +1,274 @@ +mod common; + +use common::*; +use lazorkit_program::compact::{self, CompactInstruction}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + message::{v0, VersionedMessage}, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + transaction::VersionedTransaction, +}; + +#[test] +fn test_session_lifecycle() { + let mut context = setup_test(); + + // 1. Create Wallet (Owner) + let user_seed = rand::random::<[u8; 32]>(); + let owner_keypair = Keypair::new(); + + let (wallet_pda, _) = + Pubkey::find_program_address(&[b"wallet", &user_seed], &context.program_id); + let (vault_pda, _) = + Pubkey::find_program_address(&[b"vault", wallet_pda.as_ref()], &context.program_id); + let (config_pda, _) = Pubkey::find_program_address(&[b"config"], &context.program_id); + let (treasury_pda, _) = + Pubkey::find_program_address(&[b"treasury", &[0]], &context.program_id); + let (owner_auth_pda, owner_bump) = Pubkey::find_program_address( + &[ + b"authority", + wallet_pda.as_ref(), + owner_keypair.pubkey().as_ref(), + ], + &context.program_id, + ); + + // Create Wallet logic + { + let mut instruction_data = Vec::new(); + instruction_data.extend_from_slice(&user_seed); + instruction_data.push(0); // Ed25519 + instruction_data.push(owner_bump); // Correct bump + instruction_data.extend_from_slice(&[0; 6]); // padding + instruction_data.extend_from_slice(owner_keypair.pubkey().as_ref()); + + let create_wallet_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(owner_auth_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: { + let mut data = vec![0]; // CreateWallet discriminator + data.extend_from_slice(&instruction_data); + data + }, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[create_wallet_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = VersionedTransaction::try_new(VersionedMessage::V0(message), &[&context.payer]) + .unwrap(); + context + .svm + .send_transaction(tx) + .expect("CreateWallet failed"); + } + + // Fund Vault + { + let transfer_ix = solana_sdk::system_instruction::transfer( + &context.payer.pubkey(), + &vault_pda, + 1_000_000, + ); + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[transfer_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = VersionedTransaction::try_new(VersionedMessage::V0(message), &[&context.payer]) + .unwrap(); + context.svm.send_transaction(tx).expect("Fund vault failed"); + } + + // 2. Create Session + let session_keypair = Keypair::new(); + let current_slot = context.svm.get_sysvar::().slot; + let expires_at = current_slot + 100; // Expires in 100 slots + + let (session_pda, _) = Pubkey::find_program_address( + &[ + b"session", + wallet_pda.as_ref(), + session_keypair.pubkey().as_ref(), + ], + &context.program_id, + ); + + { + // session_key(32) + expires_at(8) + let mut session_args = Vec::new(); + session_args.extend_from_slice(session_keypair.pubkey().as_ref()); + session_args.extend_from_slice(&expires_at.to_le_bytes()); + + let create_session_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(owner_auth_pda, false), // Authorizer + AccountMeta::new(session_pda, false), // New Session PDA + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(treasury_pda, false), + AccountMeta::new_readonly(owner_keypair.pubkey(), true), // Signer + ], + data: { + let mut data = vec![5]; // CreateSession discriminator + data.extend_from_slice(&session_args); + data + }, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[create_session_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(message), + &[&context.payer, &owner_keypair], + ) + .unwrap(); + context + .svm + .send_transaction(tx) + .expect("CreateSession failed"); + } + println!("✅ Session created"); + + // 3. Execute with Session (Success) + { + let transfer_amount = 1000u64; + let mut transfer_data = Vec::new(); + transfer_data.extend_from_slice(&2u32.to_le_bytes()); // System Transfer + transfer_data.extend_from_slice(&transfer_amount.to_le_bytes()); + + let compact_ix = CompactInstruction { + program_id_index: 9, // SystemProgram is at index 9 + accounts: vec![7, 8, 9], // Vault (7), Payer (8), SystemProgram (9) + data: transfer_data, + }; + let compact_bytes = compact::serialize_compact_instructions(&[compact_ix]); + + let execute_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(session_pda, false), // Session PDA as Authority + AccountMeta::new(vault_pda, false), + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(treasury_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + // Inner accounts + AccountMeta::new(vault_pda, false), + AccountMeta::new(context.payer.pubkey(), false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + // Signer for Session Match + AccountMeta::new_readonly(session_keypair.pubkey(), true), + ], + data: { + let mut data = vec![4]; // Execute discriminator + data.extend_from_slice(&compact_bytes); + data + }, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[execute_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(message), + &[&context.payer, &session_keypair], // Session Key signs + ) + .unwrap(); + context + .svm + .send_transaction(tx) + .expect("Session execution failed"); + } + println!("✅ Session execution succeeded"); + + // 4. Execute with Expired Session (Fail) + { + // Warp time + let mut clock = context.svm.get_sysvar::(); + clock.slot = expires_at + 1; + context.svm.set_sysvar(&clock); + + let transfer_amount = 1000u64; + let mut transfer_data = Vec::new(); + transfer_data.extend_from_slice(&2u32.to_le_bytes()); + transfer_data.extend_from_slice(&transfer_amount.to_le_bytes()); + let compact_ix = CompactInstruction { + program_id_index: 9, + accounts: vec![7, 8, 9], // Vault, Payer, SystemProgram + data: transfer_data, + }; + let compact_bytes = compact::serialize_compact_instructions(&[compact_ix]); + + let execute_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(session_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(treasury_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(context.payer.pubkey(), false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(session_keypair.pubkey(), true), + ], + data: { + let mut data = vec![4]; + data.extend_from_slice(&compact_bytes); + data + }, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[execute_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(message), + &[&context.payer, &session_keypair], + ) + .unwrap(); + + let res = context.svm.send_transaction(tx); + assert!(res.is_err()); + // Could verify specific error but SessionExpired is expected + } + println!("✅ Expired session rejected"); +} diff --git a/program/tests/slothashes_oob_test.rs b/program/tests/slothashes_oob_test.rs new file mode 100644 index 0000000..901e3ed --- /dev/null +++ b/program/tests/slothashes_oob_test.rs @@ -0,0 +1,49 @@ +use lazorkit_program::auth::secp256r1::slothashes::SlotHashes; + +#[test] +fn test_slot_hashes_oob_read() { + // 1. Setup Mock Data + // num_entries: 2 (u64) + // entry 0: slot 100, hash [1; 32] + // entry 1: slot 99, hash [2; 32] + let mut data = Vec::new(); + data.extend_from_slice(&2u64.to_le_bytes()); // len = 2 + + // Entry 0 + data.extend_from_slice(&100u64.to_le_bytes()); + data.extend_from_slice(&[1u8; 32]); + + // Entry 1 + data.extend_from_slice(&99u64.to_le_bytes()); + data.extend_from_slice(&[2u8; 32]); + + // Interpret data as &[u8] + let data_slice: &[u8] = &data; + + // Safety: we constructed data correctly + let slot_hashes = unsafe { SlotHashes::new_unchecked(data_slice) }; + + // 2. Verify Valid Access + let hash_0 = slot_hashes.get_slot_hash(0).unwrap(); + assert_eq!(hash_0.height, 100); + + let hash_1 = slot_hashes.get_slot_hash(1).unwrap(); + assert_eq!(hash_1.height, 99); + + // 3. Verify OOB Access (The Bug) + println!("Trying to access OOB index 2..."); + // This call accesses index 2. + // Length is 2. + // Current Buggy Logic: 2 > 2 is FALSE. + // So it PROCEEDS to unsafe code and returns Ok or Panics. + // We expect it to be Err(PermissionDenied). + + let result = slot_hashes.get_slot_hash(2); + + // If the bug is present, result.is_ok() will be true (or panic). + // If fixed, result.is_err() will be true. + assert!( + result.is_err(), + "Index equal to length should be an error! (OOB Read)" + ); +} diff --git a/program/tests/system_program_check.rs b/program/tests/system_program_check.rs new file mode 100644 index 0000000..e73cf90 --- /dev/null +++ b/program/tests/system_program_check.rs @@ -0,0 +1,89 @@ +mod common; +use common::*; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + message::{v0, VersionedMessage}, + pubkey::Pubkey, + signature::{Keypair, Signer}, + transaction::VersionedTransaction, +}; + +#[test] +fn test_spoof_system_program() { + let mut context = setup_test(); + + // 1. Create a Fake System Program (just a random keypair) + // In a real attack, this would be a program controlled by attacker, + // but for this test, just passing a non-system-program account is enough to check validation. + let fake_system_program = Keypair::new(); + + // 2. Prepare CreateWallet args + let user_seed = rand::random::<[u8; 32]>(); + let owner_keypair = Keypair::new(); + + let (wallet_pda, _) = + Pubkey::find_program_address(&[b"wallet", &user_seed], &context.program_id); + let (vault_pda, _) = + Pubkey::find_program_address(&[b"vault", wallet_pda.as_ref()], &context.program_id); + let (auth_pda, auth_bump) = Pubkey::find_program_address( + &[ + b"authority", + wallet_pda.as_ref(), + owner_keypair.pubkey().as_ref(), + ], + &context.program_id, + ); + + let mut instruction_data = Vec::new(); + instruction_data.extend_from_slice(&user_seed); + instruction_data.push(0); // Ed25519 + instruction_data.push(auth_bump); + instruction_data.extend_from_slice(&[0; 6]); + instruction_data.extend_from_slice(owner_keypair.pubkey().as_ref()); + + // 3. Create Instruction with FAKE System Program + let create_wallet_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(auth_pda, false), + // PASS FAKE SYSTEM PROGRAM HERE + AccountMeta::new_readonly(fake_system_program.pubkey(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + ], + data: { + let mut data = vec![0]; // CreateWallet discriminator + data.extend_from_slice(&instruction_data); + data + }, + }; + + // 4. Submit Transaction + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[create_wallet_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = + VersionedTransaction::try_new(VersionedMessage::V0(message), &[&context.payer]).unwrap(); + + let res = context.svm.send_transaction(tx); + + // 5. Assert Failure + // If validation works, this should fail with IncorrectProgramId or similar. + // If vulnerability exists, it might fail with something else (like InstructionError because fake program doesn't handle instruction) + // or PASS if the contract doesn't invoke it or invokes it successfully (unlikely for random key). + // The critical check is if the CONTRACT returns an error BEFORE invoking. + + if let Err(err) = res { + println!("Transaction failed as expected: {:?}", err); + // Verify it is NOT "IncorrectProgramId" if we want to prove vulnerability? + // Wait, if it returns IncorrectProgramId, then the check IS working. + } else { + panic!("Transaction succeeded but should have failed due to fake system program!"); + } +} diff --git a/program/tests/wallet_lifecycle.rs b/program/tests/wallet_lifecycle.rs new file mode 100644 index 0000000..cbc930b --- /dev/null +++ b/program/tests/wallet_lifecycle.rs @@ -0,0 +1,518 @@ +mod common; + +use common::*; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + message::{v0, VersionedMessage}, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + transaction::VersionedTransaction, +}; + +#[test] +fn test_create_wallet_ed25519() { + let mut context = setup_test(); + + // Generate test data + let user_seed = rand::random::<[u8; 32]>(); + let owner_keypair = Keypair::new(); + + // Derive PDAs + let (wallet_pda, _wallet_bump) = + Pubkey::find_program_address(&[b"wallet", &user_seed], &context.program_id); + + let (vault_pda, _vault_bump) = + Pubkey::find_program_address(&[b"vault", wallet_pda.as_ref()], &context.program_id); + + let (auth_pda, auth_bump) = Pubkey::find_program_address( + &[ + b"authority", + wallet_pda.as_ref(), + owner_keypair.pubkey().as_ref(), + ], + &context.program_id, + ); + + // Build instruction data + // Format: [user_seed(32)][authority_type(1)][role(1)][padding(6)][pubkey(32)] + let mut instruction_data = Vec::new(); + instruction_data.extend_from_slice(&user_seed); + instruction_data.push(0); // Ed25519 + instruction_data.push(auth_bump); // Owner role (bump) + instruction_data.extend_from_slice(&[0; 6]); // padding + instruction_data.extend_from_slice(owner_keypair.pubkey().as_ref()); + + // Derive Config PDA and Treasury shard PDA (shard 0 for tests) + let (config_pda, _) = + Pubkey::find_program_address(&[b"config"], &context.program_id); + let shard_id: u8 = 0; + let shard_id_bytes = [shard_id]; + let (treasury_pda, _) = + Pubkey::find_program_address(&[b"treasury", &shard_id_bytes], &context.program_id); + + // Build CreateWallet instruction + let create_wallet_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(auth_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: { + let mut data = vec![0]; // CreateWallet discriminator + data.extend_from_slice(&instruction_data); + data + }, + }; + + // Send transaction + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[create_wallet_ix], + &[], + context.svm.latest_blockhash(), + ) + .expect("Failed to compile message"); + + let tx = VersionedTransaction::try_new(VersionedMessage::V0(message), &[&context.payer]) + .expect("Failed to create transaction"); + + let result = context.svm.send_transaction(tx); + + if result.is_err() { + let err = result.err().unwrap(); + eprintln!("Transaction failed:"); + eprintln!("{}", err.meta.pretty_logs()); + panic!("CreateWallet failed"); + } + + let metadata = result.unwrap(); + println!("✅ CreateWallet succeeded"); + println!(" CU consumed: {}", metadata.compute_units_consumed); + + // Verify wallet account exists + let wallet_account = context.svm.get_account(&wallet_pda); + assert!(wallet_account.is_some(), "Wallet account should exist"); + + // Verify vault exists + let vault_account = context.svm.get_account(&vault_pda); + assert!(vault_account.is_some(), "Vault account should exist"); + + // Verify authority exists + let auth_account = context.svm.get_account(&auth_pda); + assert!(auth_account.is_some(), "Authority account should exist"); + + println!("✅ All accounts created successfully"); +} + +#[test] +fn test_compact_instructions_basic() { + // Simple test to verify CompactInstructions work + use lazorkit_program::compact::{ + parse_compact_instructions, serialize_compact_instructions, CompactInstruction, + }; + + let instructions = vec![CompactInstruction { + program_id_index: 0, + accounts: vec![1, 2], + data: vec![1, 2, 3], + }]; + + let bytes = serialize_compact_instructions(&instructions); + let parsed = parse_compact_instructions(&bytes).unwrap(); + + assert_eq!(parsed.len(), 1); + assert_eq!(parsed[0].program_id_index, 0); + assert_eq!(parsed[0].accounts, vec![1, 2]); + assert_eq!(parsed[0].data, vec![1, 2, 3]); + + println!("✅ CompactInstructions serialization works"); +} + +#[test] +fn test_authority_lifecycle() { + let mut context = setup_test(); + + // 1. Create Wallet (Owner) + let user_seed = rand::random::<[u8; 32]>(); + let owner_keypair = Keypair::new(); + + let (wallet_pda, _) = + Pubkey::find_program_address(&[b"wallet", &user_seed], &context.program_id); + + let (vault_pda, _) = + Pubkey::find_program_address(&[b"vault", wallet_pda.as_ref()], &context.program_id); + + let (owner_auth_pda, owner_bump) = Pubkey::find_program_address( + &[ + b"authority", + wallet_pda.as_ref(), + owner_keypair.pubkey().as_ref(), // seed for Ed25519 is pubkey + ], + &context.program_id, + ); + + // Derive Config PDA and Treasury shard PDA for this wallet (shard 0 for tests) + let (config_pda, _) = + Pubkey::find_program_address(&[b"config"], &context.program_id); + let shard_id: u8 = 0; + let shard_id_bytes = [shard_id]; + let (treasury_pda, _) = + Pubkey::find_program_address(&[b"treasury", &shard_id_bytes], &context.program_id); + + // Create Wallet Instruction + { + let mut instruction_data = Vec::new(); + instruction_data.extend_from_slice(&user_seed); + instruction_data.push(0); // Ed25519 + instruction_data.push(owner_bump); // Owner role (bump) + instruction_data.extend_from_slice(&[0; 6]); // padding + instruction_data.extend_from_slice(owner_keypair.pubkey().as_ref()); + + let create_wallet_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(owner_auth_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: { + let mut data = vec![0]; // CreateWallet discriminator + data.extend_from_slice(&instruction_data); + data + }, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[create_wallet_ix], + &[], + context.svm.latest_blockhash(), + ) + .expect("Failed to compile create message"); + + let tx = VersionedTransaction::try_new(VersionedMessage::V0(message), &[&context.payer]) + .expect("Failed to create create transaction"); + + context + .svm + .send_transaction(tx) + .expect("CreateWallet failed"); + } + println!("✅ Wallet created with Owner"); + + // 2. Add New Authority (Admin) + let admin_keypair = Keypair::new(); + let (admin_auth_pda, _) = Pubkey::find_program_address( + &[ + b"authority", + wallet_pda.as_ref(), + admin_keypair.pubkey().as_ref(), + ], + &context.program_id, + ); + + { + // AddAuthorityArgs: type(1) + role(1) + padding(6) + let mut add_auth_args = Vec::new(); + add_auth_args.push(0); // Ed25519 + add_auth_args.push(1); // Admin role + add_auth_args.extend_from_slice(&[0; 6]); + + // Data payload for Ed25519 is just the pubkey + let mut instruction_payload = Vec::new(); + instruction_payload.extend_from_slice(&add_auth_args); + instruction_payload.extend_from_slice(admin_keypair.pubkey().as_ref()); + + // Auth payload (signature from Owner to authorize adding) + // Since we are not using the aggregated signature verification yet (assuming it checks transaction signatures if provided?) + // Wait, manage_authority checks signatures internally using ed25519_verify or similar? + // Actually, LazorKit uses `check_signature` which verifies the transaction signature for Ed25519. + // But for `AddAuthority`, we need to pass the "authority payload" at the end of instruction data? + // Let's check manage_authority.rs again. + // For Ed25519, the `authority_payload` is usually empty if we just rely on the account being a signer in the transaction. + // BUT, the instruction data splitting in manage_authority looks like: `(args, rest) = AddAuthorityArgs::from_bytes`. + // Then `rest` is split into `id_seed` (pubkey) and `authority_payload`. + // So we need to append the authority payload. + // For Ed25519 authorities, usually the payload is empty if it's just a standard signer check. + // However, the `authenticate` function in `auth/ed25519.rs` might expect something. + // Let's assume for now Ed25519 just needs to be a signer and payload is empty. + + let add_authority_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(owner_auth_pda, false), // PDA must be writable for auth logic + AccountMeta::new(admin_auth_pda, false), // New authority PDA being created + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + // Config + Treasury shard for protocol fee + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(treasury_pda, false), + AccountMeta::new_readonly(owner_keypair.pubkey(), true), // Actual signer + ], + data: { + let mut data = vec![1]; // AddAuthority discriminator + data.extend_from_slice(&instruction_payload); + // No extra auth payload for Ed25519 simple signer? + data + }, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[add_authority_ix], + &[], + context.svm.latest_blockhash(), + ) + .expect("Failed to compile add_auth message"); + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(message), + &[&context.payer, &owner_keypair], // Owner must sign + ) + .expect("Failed to create add_auth transaction"); + + let res = context.svm.send_transaction(tx); + if let Err(e) = res { + eprintln!("AddAuthority failed: {}", e.meta.pretty_logs()); + panic!("AddAuthority failed"); + } + } + println!("✅ Admin authority added"); + + // Verify Admin PDA exists + let admin_acc = context.svm.get_account(&admin_auth_pda); + assert!(admin_acc.is_some(), "Admin authority PDA should exist"); + + // 3. Remove Authority (Admin removes itself? Or Owner removes Admin?) + // Let's have Owner remove Admin. + { + // For RemoveAuthority, instruction data is just "authority_payload" (for authentication). + // Since Owner is Ed25519, payload matches what `authenticate` expects. + // For Ed25519, `authenticate` expects empty payload if it relies on transaction signatures. + + let remove_authority_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(owner_auth_pda, false), // PDA must be writable + AccountMeta::new(admin_auth_pda, false), // Target to remove + AccountMeta::new(context.payer.pubkey(), false), // Refund destination + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + // Config + Treasury shard for protocol fee + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(treasury_pda, false), + AccountMeta::new_readonly(owner_keypair.pubkey(), true), // Actual signer + ], + data: vec![2], // RemoveAuthority discriminator (and empty payload) + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[remove_authority_ix], + &[], + context.svm.latest_blockhash(), + ) + .expect("Failed to compile remove_auth message"); + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(message), + &[&context.payer, &owner_keypair], + ) + .expect("Failed to create remove_auth transaction"); + + let res = context.svm.send_transaction(tx); + if let Err(e) = res { + eprintln!("RemoveAuthority failed: {}", e.meta.pretty_logs()); + panic!("RemoveAuthority failed"); + } + } + println!("✅ Admin authority removed"); + + // Verify Admin PDA is gone (or data allows for re-initialization? Standard behavior is closing account) + let admin_acc = context.svm.get_account(&admin_auth_pda); + if let Some(acc) = &admin_acc { + println!( + "Admin Acc: Lamports={}, DataLen={}, Owner={}", + acc.lamports, + acc.data.len(), + acc.owner + ); + assert_eq!( + acc.lamports, 0, + "Admin authority PDA should have 0 lamports" + ); + } else { + // None is also acceptable (means fully purged) + } +} + +#[test] +fn test_execute_with_compact_instructions() { + let mut context = setup_test(); + + // 1. Setup Wallet & Authority + let user_seed = rand::random::<[u8; 32]>(); + let owner_keypair = Keypair::new(); + + let (wallet_pda, _) = + Pubkey::find_program_address(&[b"wallet", &user_seed], &context.program_id); + let (vault_pda, _) = + Pubkey::find_program_address(&[b"vault", wallet_pda.as_ref()], &context.program_id); + let (owner_auth_pda, owner_bump) = Pubkey::find_program_address( + &[ + b"authority", + wallet_pda.as_ref(), + owner_keypair.pubkey().as_ref(), + ], + &context.program_id, + ); + + // Derive Config PDA and Treasury shard PDA for this wallet (shard 0 for tests) + let (config_pda, _) = + Pubkey::find_program_address(&[b"config"], &context.program_id); + let shard_id: u8 = 0; + let shard_id_bytes = [shard_id]; + let (treasury_pda, _) = + Pubkey::find_program_address(&[b"treasury", &shard_id_bytes], &context.program_id); + + // Create Wallet logic (simplified re-use) + { + let mut instruction_data = Vec::new(); + instruction_data.extend_from_slice(&user_seed); + instruction_data.push(0); // Ed25519 + instruction_data.push(owner_bump); // Owner role (bump) + instruction_data.extend_from_slice(&[0; 6]); // padding + instruction_data.extend_from_slice(owner_keypair.pubkey().as_ref()); + + let create_wallet_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), + AccountMeta::new(wallet_pda, false), + AccountMeta::new(vault_pda, false), + AccountMeta::new(owner_auth_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::rent::id(), false), + AccountMeta::new(config_pda, false), + AccountMeta::new(treasury_pda, false), + ], + data: { + let mut data = vec![0]; + data.extend_from_slice(&instruction_data); + data + }, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[create_wallet_ix], + &[], + context.svm.latest_blockhash(), + ) + .expect("Failed compile create"); + let tx = VersionedTransaction::try_new(VersionedMessage::V0(message), &[&context.payer]) + .unwrap(); + context.svm.send_transaction(tx).expect("Create failed"); + } + + // Fund Vault so it can transfer + { + let transfer_ix = solana_sdk::system_instruction::transfer( + &context.payer.pubkey(), + &vault_pda, + 1_000_000, + ); + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[transfer_ix], + &[], + context.svm.latest_blockhash(), + ) + .unwrap(); + let tx = VersionedTransaction::try_new(VersionedMessage::V0(message), &[&context.payer]) + .unwrap(); + context.svm.send_transaction(tx).expect("Fund vault failed"); + } + + // 2. Prepare Compact Instruction (Transfer 5000 lamports from Vault to Payer) + use lazorkit_program::compact::{self, CompactInstruction}; + + // Inner accounts indices (relative to the slice passed after fixed accounts) + // 0: Vault (Signer) + // 1: Payer (Destination) + // 2: SystemProgram + + let transfer_amount = 5000u64; + let mut transfer_data = Vec::new(); + transfer_data.extend_from_slice(&2u32.to_le_bytes()); // Transfer instruction discriminator (2) + transfer_data.extend_from_slice(&transfer_amount.to_le_bytes()); + + let compact_ix = CompactInstruction { + program_id_index: 9, // SystemProgram is now the 10th account (index 9) + accounts: vec![7, 8, 9], // Vault (7), Payer (8), SystemProgram (9) + data: transfer_data, + }; + + let compact_bytes = compact::serialize_compact_instructions(&[compact_ix]); + + // 3. Execute Instruction + let execute_ix = Instruction { + program_id: context.program_id, + accounts: vec![ + AccountMeta::new(context.payer.pubkey(), true), // Payer + AccountMeta::new(wallet_pda, false), // Wallet + AccountMeta::new(owner_auth_pda, false), // Authority (PDA) must be writable + AccountMeta::new(vault_pda, false), // Vault (Context) + AccountMeta::new_readonly(config_pda, false), + AccountMeta::new(treasury_pda, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + // Inner accounts start here (Index 7): + AccountMeta::new(vault_pda, false), // Index 7: Vault + AccountMeta::new(context.payer.pubkey(), false), // Index 8: Payer + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), // Index 9: SystemProgram + // Authentication: Owner Keypair (Index 10) + AccountMeta::new_readonly(owner_keypair.pubkey(), true), // Owner signs transaction + ], + data: { + let mut data = vec![4]; // Execute discriminator + data.extend_from_slice(&compact_bytes); + // Ed25519 needs no extra payload, signature is validated against owner_keypair account + data + }, + }; + + let message = v0::Message::try_compile( + &context.payer.pubkey(), + &[execute_ix], + &[], + context.svm.latest_blockhash(), + ) + .expect("Failed compile execute"); + + let tx = VersionedTransaction::try_new( + VersionedMessage::V0(message), + &[&context.payer, &owner_keypair], // Owner signs + ) + .expect("Failed create execute tx"); + + let res = context.svm.send_transaction(tx); + if let Err(e) = res { + eprintln!("Execute failed: {}", e.meta.pretty_logs()); + panic!("Execute failed"); + } + println!("✅ Execute transaction succeeded"); +} diff --git a/programs/default_rule/Cargo.toml b/programs/default_rule/Cargo.toml deleted file mode 100644 index 3e73e1d..0000000 --- a/programs/default_rule/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "default_rule" -version = "0.1.0" -description = "Created with Anchor" -edition = "2021" - -[lib] -crate-type = ["cdylib", "lib"] -name = "default_rule" - -[features] -default = [] -cpi = ["no-entrypoint"] -no-entrypoint = [] -no-idl = [] -no-log-ix-name = [] -anchor-debug = [] -idl-build = ["anchor-lang/idl-build"] - - -[dependencies] -anchor-lang = { version = "0.31.0", features = ["init-if-needed"] } -lazorkit = { path = "../lazorkit", features = ["no-entrypoint", "cpi"] } - - diff --git a/programs/default_rule/Xargo.toml b/programs/default_rule/Xargo.toml deleted file mode 100644 index 475fb71..0000000 --- a/programs/default_rule/Xargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.bpfel-unknown-unknown.dependencies.std] -features = [] diff --git a/programs/default_rule/src/error.rs b/programs/default_rule/src/error.rs deleted file mode 100644 index 58a961b..0000000 --- a/programs/default_rule/src/error.rs +++ /dev/null @@ -1,8 +0,0 @@ -use anchor_lang::error_code; - -#[error_code] -pub enum RuleError { - InvalidPasskey, - - UnAuthorize, -} diff --git a/programs/default_rule/src/instructions/check_rule.rs b/programs/default_rule/src/instructions/check_rule.rs deleted file mode 100644 index 2f180a4..0000000 --- a/programs/default_rule/src/instructions/check_rule.rs +++ /dev/null @@ -1,19 +0,0 @@ -use anchor_lang::prelude::*; - -use crate::{error::RuleError, state::Rule, ID}; - -pub fn check_rule(_ctx: Context) -> Result<()> { - Ok(()) -} - -#[derive(Accounts)] -pub struct CheckRule<'info> { - pub smart_wallet_authenticator: Signer<'info>, - - #[account( - mut, - owner = ID, - constraint = smart_wallet_authenticator.key() == rule.admin @ RuleError::UnAuthorize, - )] - pub rule: Account<'info, Rule>, -} diff --git a/programs/default_rule/src/instructions/destroy.rs b/programs/default_rule/src/instructions/destroy.rs deleted file mode 100644 index 8925784..0000000 --- a/programs/default_rule/src/instructions/destroy.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::error::RuleError; -use crate::state::Rule; -use crate::ID; -use anchor_lang::prelude::*; - -pub fn destroy(_ctx: Context) -> Result<()> { - Ok(()) -} - -#[derive(Accounts)] -pub struct Destroy<'info> { - /// CHECK - pub smart_wallet: UncheckedAccount<'info>, - /// CHECK - pub smart_wallet_authenticator: Signer<'info>, - #[account( - mut, - owner = ID, - constraint = smart_wallet_authenticator.key() == rule.admin @ RuleError::UnAuthorize, - constraint = rule.smart_wallet == smart_wallet.key() @ RuleError::UnAuthorize, - close = smart_wallet - )] - pub rule: Account<'info, Rule>, -} diff --git a/programs/default_rule/src/instructions/init_rule.rs b/programs/default_rule/src/instructions/init_rule.rs deleted file mode 100644 index 8e995ce..0000000 --- a/programs/default_rule/src/instructions/init_rule.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::state::Rule; -use anchor_lang::prelude::*; -use lazorkit::program::Lazorkit; - -pub fn init_rule(ctx: Context) -> Result<()> { - let rule = &mut ctx.accounts.rule; - - rule.smart_wallet = ctx.accounts.smart_wallet.key(); - rule.admin = ctx.accounts.smart_wallet_authenticator.key(); - rule.is_initialized = true; - - Ok(()) -} - -#[derive(Accounts)] -pub struct InitRule<'info> { - #[account(mut)] - pub payer: Signer<'info>, - - /// CHECK: - pub smart_wallet: UncheckedAccount<'info>, - - /// CHECK - pub smart_wallet_authenticator: Signer<'info>, - - #[account( - init, - payer = payer, - space = 8 + Rule::INIT_SPACE, - seeds = [b"rule".as_ref(), smart_wallet.key().as_ref()], - bump, - )] - pub rule: Account<'info, Rule>, - - pub lazorkit: Program<'info, Lazorkit>, - - pub system_program: Program<'info, System>, -} diff --git a/programs/default_rule/src/instructions/mod.rs b/programs/default_rule/src/instructions/mod.rs deleted file mode 100644 index dfad7f8..0000000 --- a/programs/default_rule/src/instructions/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod check_rule; -mod destroy; -mod init_rule; - -pub use init_rule::*; - -pub use check_rule::*; - -pub use destroy::*; diff --git a/programs/default_rule/src/lib.rs b/programs/default_rule/src/lib.rs deleted file mode 100644 index 911cdb5..0000000 --- a/programs/default_rule/src/lib.rs +++ /dev/null @@ -1,27 +0,0 @@ -use anchor_lang::prelude::*; - -declare_id!("7H16pVKG2stkkhQ6H9LyXvnHLpXjfB7LLShGjXhYmEWs"); - -mod error; -mod instructions; -mod state; - -use instructions::*; - -#[program] -pub mod default_rule { - - use super::*; - - pub fn init_rule(ctx: Context) -> Result<()> { - instructions::init_rule(ctx) - } - - pub fn check_rule(_ctx: Context) -> Result<()> { - instructions::check_rule(_ctx) - } - - pub fn destroy(ctx: Context) -> Result<()> { - instructions::destroy(ctx) - } -} diff --git a/programs/default_rule/src/state.rs b/programs/default_rule/src/state.rs deleted file mode 100644 index 3e32619..0000000 --- a/programs/default_rule/src/state.rs +++ /dev/null @@ -1,9 +0,0 @@ -use anchor_lang::prelude::*; - -#[account] -#[derive(Debug, InitSpace)] -pub struct Rule { - pub smart_wallet: Pubkey, - pub admin: Pubkey, - pub is_initialized: bool, -} diff --git a/programs/lazorkit/Cargo.toml b/programs/lazorkit/Cargo.toml deleted file mode 100644 index 721d6bc..0000000 --- a/programs/lazorkit/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "lazorkit" -version = "0.1.0" -description = "Created with Anchor" -edition = "2021" - -[lib] -crate-type = ["cdylib", "lib"] -name = "lazorkit" - -[features] -default = [] -cpi = ["no-entrypoint"] -no-entrypoint = [] -no-idl = [] -no-log-ix-name = [] -anchor-debug = [] -idl-build = ["anchor-lang/idl-build"] - - -[dependencies] -anchor-lang = { version = "0.31.0", features = ["init-if-needed"] } -serde_json = "1.0.140" - diff --git a/programs/lazorkit/Xargo.toml b/programs/lazorkit/Xargo.toml deleted file mode 100644 index 475fb71..0000000 --- a/programs/lazorkit/Xargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.bpfel-unknown-unknown.dependencies.std] -features = [] diff --git a/programs/lazorkit/src/constants.rs b/programs/lazorkit/src/constants.rs deleted file mode 100644 index 0c3549a..0000000 --- a/programs/lazorkit/src/constants.rs +++ /dev/null @@ -1,12 +0,0 @@ -use anchor_lang::prelude::*; - -/// Program IDs -pub const SECP256R1_ID: Pubkey = pubkey!("Secp256r1SigVerify1111111111111111111111111"); - -/// Seeds for PDA derivation -pub const SMART_WALLET_SEED: &[u8] = b"smart_wallet"; - -/// Size constants for account data -pub const PASSKEY_SIZE: usize = 33; // Secp256r1 compressed pubkey size - -pub const SOL_TRANSFER_DISCRIMINATOR: [u8; 4] = [2, 0, 0, 0]; diff --git a/programs/lazorkit/src/error.rs b/programs/lazorkit/src/error.rs deleted file mode 100644 index 3fd0326..0000000 --- a/programs/lazorkit/src/error.rs +++ /dev/null @@ -1,34 +0,0 @@ -use anchor_lang::error_code; - -/// Custom errors for the Lazor Kit program -#[error_code] -pub enum LazorKitError { - /// Authentication errors - #[msg("Invalid passkey provided")] - InvalidPasskey, - #[msg("Invalid authenticator for smart wallet")] - InvalidAuthenticator, - #[msg("Invalid rule program for operation")] - InvalidRuleProgram, - /// Secp256r1 verification errors - #[msg("Invalid instruction length for signature verification")] - InvalidLengthForVerification, - #[msg("Signature header verification failed")] - VerifyHeaderMismatchError, - #[msg("Signature data verification failed")] - VerifyDataMismatchError, - /// Account validation errors - #[msg("Invalid bump seed provided")] - InvalidBump, - #[msg("Invalid or missing required account")] - InvalidAccountInput, - - InsufficientFunds, - - #[msg("Invalid rule instruction provided")] - InvalidRuleInstruction, - - InvalidTimestamp, - - InvalidNonce, -} diff --git a/programs/lazorkit/src/instructions/create_smart_wallet.rs b/programs/lazorkit/src/instructions/create_smart_wallet.rs deleted file mode 100644 index 27709ca..0000000 --- a/programs/lazorkit/src/instructions/create_smart_wallet.rs +++ /dev/null @@ -1,128 +0,0 @@ -use anchor_lang::prelude::*; - -use crate::{ - constants::{PASSKEY_SIZE, SMART_WALLET_SEED}, - state::{ - Config, SmartWalletAuthenticator, SmartWalletConfig, SmartWalletSeq, WhitelistRulePrograms, - }, - utils::{execute_cpi, transfer_sol_from_pda, PasskeyExt, PdaSigner}, - ID, -}; - -pub fn create_smart_wallet( - ctx: Context, - passkey_pubkey: [u8; PASSKEY_SIZE], - credential_id: Vec, - rule_data: Vec, -) -> Result<()> { - let wallet_data = &mut ctx.accounts.smart_wallet_config; - let sequence_account = &mut ctx.accounts.smart_wallet_seq; - let smart_wallet_authenticator = &mut ctx.accounts.smart_wallet_authenticator; - - wallet_data.set_inner(SmartWalletConfig { - rule_program: ctx.accounts.config.default_rule_program, - id: sequence_account.seq, - last_nonce: 0, - bump: ctx.bumps.smart_wallet, - }); - - // Initialize the smart wallet authenticator - smart_wallet_authenticator.set_inner(SmartWalletAuthenticator { - passkey_pubkey, - smart_wallet: ctx.accounts.smart_wallet.key(), - credential_id, - bump: ctx.bumps.smart_wallet_authenticator, - }); - let signer = PdaSigner { - seeds: vec![passkey_pubkey - .to_hashed_bytes(ctx.accounts.smart_wallet.key()) - .to_vec()], - bump: ctx.bumps.smart_wallet_authenticator, - }; - - execute_cpi( - &ctx.remaining_accounts, - &rule_data, - &ctx.accounts.default_rule_program, - Some(signer), - )?; - - sequence_account.seq += 1; - - transfer_sol_from_pda( - &ctx.accounts.smart_wallet, - &mut ctx.accounts.signer, - ctx.accounts.config.create_smart_wallet_fee, - )?; - - Ok(()) -} - -#[derive(Accounts)] -#[instruction(passkey_pubkey: [u8; PASSKEY_SIZE])] -pub struct CreateSmartWallet<'info> { - #[account(mut)] - pub signer: Signer<'info>, - - /// CHECK: This account is only used for its public key and seeds. - #[account( - mut, - seeds = [SmartWalletSeq::PREFIX_SEED], - bump, - )] - pub smart_wallet_seq: Account<'info, SmartWalletSeq>, - - #[account( - seeds = [WhitelistRulePrograms::PREFIX_SEED], - bump, - owner = ID - )] - pub whitelist_rule_programs: Account<'info, WhitelistRulePrograms>, - - #[account( - init, - payer = signer, - space = 0, - seeds = [SMART_WALLET_SEED, smart_wallet_seq.seq.to_le_bytes().as_ref()], - bump - )] - /// CHECK: This account is only used for its public key and seeds. - pub smart_wallet: UncheckedAccount<'info>, - - #[account( - init, - payer = signer, - space = 8 + SmartWalletConfig::INIT_SPACE, - seeds = [SmartWalletConfig::PREFIX_SEED, smart_wallet.key().as_ref()], - bump - )] - pub smart_wallet_config: Box>, - - #[account( - init, - payer = signer, - space = 8 + SmartWalletAuthenticator::INIT_SPACE, - seeds = [ - SmartWalletAuthenticator::PREFIX_SEED, - smart_wallet.key().as_ref(), - passkey_pubkey.to_hashed_bytes(smart_wallet.key()).as_ref() - ], - bump - )] - pub smart_wallet_authenticator: Box>, - - #[account( - seeds = [Config::PREFIX_SEED], - bump, - owner = ID - )] - pub config: Box>, - - #[account( - address = config.default_rule_program - )] - /// CHECK: - pub default_rule_program: UncheckedAccount<'info>, - - pub system_program: Program<'info, System>, -} diff --git a/programs/lazorkit/src/instructions/execute_instruction.rs b/programs/lazorkit/src/instructions/execute_instruction.rs deleted file mode 100644 index cd08ad2..0000000 --- a/programs/lazorkit/src/instructions/execute_instruction.rs +++ /dev/null @@ -1,387 +0,0 @@ -use anchor_lang::solana_program::hash::hash; // ✅ required import - -use anchor_lang::{prelude::*, solana_program::sysvar::instructions::load_instruction_at_checked}; - -use crate::state::{Config, Message}; -use crate::utils::{ - check_whitelist, execute_cpi, get_pda_signer, sighash, transfer_sol_from_pda, - verify_secp256r1_instruction, PasskeyExt, PdaSigner, -}; -use crate::{ - constants::{SMART_WALLET_SEED, SOL_TRANSFER_DISCRIMINATOR}, - error::LazorKitError, - state::{SmartWalletAuthenticator, SmartWalletConfig, WhitelistRulePrograms}, - ID, -}; -use anchor_lang::solana_program::sysvar::instructions::ID as IX_ID; - -const MAX_TIMESTAMP_DRIFT_SECONDS: i64 = 30; - -/// Enum for supported actions in the instruction -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] -pub enum Action { - #[default] - ExecuteCpi, - ChangeProgramRule, - CheckAuthenticator, - CallRuleProgram, -} - -/// Arguments for the execute_instruction entrypoint -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct ExecuteInstructionArgs { - pub passkey_pubkey: [u8; 33], - pub signature: Vec, - pub client_data_json_raw: Vec, // Match field name used in SDK - pub authenticator_data_raw: Vec, // Added missing field - pub verify_instruction_index: u8, - pub rule_data: CpiData, - pub cpi_data: Option, - pub action: Action, - pub create_new_authenticator: Option<[u8; 33]>, // Make sure this field name is consistent -} - -/// Data for a CPI call (instruction data and account slice) -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct CpiData { - pub data: Vec, - pub start_index: u8, // starting index in remaining accounts - pub length: u8, // number of accounts to take from remaining accounts -} - -/// Entrypoint for executing smart wallet instructions -pub fn execute_instruction( - mut ctx: Context, - args: ExecuteInstructionArgs, -) -> Result<()> { - verify_passkey_and_signature(&ctx, &args, ctx.accounts.smart_wallet_config.last_nonce)?; - - // --- Action dispatch --- - match args.action { - Action::ExecuteCpi => handle_execute_cpi(&mut ctx, &args)?, - Action::ChangeProgramRule => handle_change_program_rule(&mut ctx, &args)?, - Action::CallRuleProgram => handle_call_rule_program(&mut ctx, &args)?, - Action::CheckAuthenticator => { - // --- No-op: used for checking authenticator existence --- - } - } - - // update last nonce - ctx.accounts.smart_wallet_config.last_nonce = ctx - .accounts - .smart_wallet_config - .last_nonce - .checked_add(1) - .ok_or(LazorKitError::InvalidNonce)?; - - Ok(()) -} - -fn verify_passkey_and_signature( - ctx: &Context, - args: &ExecuteInstructionArgs, - last_nonce: u64, -) -> Result<()> { - let authenticator = &ctx.accounts.smart_wallet_authenticator; - - // --- Passkey and wallet validation --- - require!( - authenticator.passkey_pubkey == args.passkey_pubkey - && authenticator.smart_wallet == ctx.accounts.smart_wallet.key(), - LazorKitError::InvalidPasskey - ); - - // --- Signature verification using secp256r1 --- - let secp_ix = load_instruction_at_checked( - args.verify_instruction_index as usize, - &ctx.accounts.ix_sysvar, - )?; - - let client_hash = hash(&args.client_data_json_raw); - - let mut message = Vec::with_capacity(args.authenticator_data_raw.len() + client_hash.as_ref().len()); - message.extend_from_slice(&args.authenticator_data_raw); - message.extend_from_slice(client_hash.as_ref()); - - let json_str = core::str::from_utf8(&args.client_data_json_raw) - .map_err(|_| ProgramError::InvalidInstructionData)?; - let parsed: serde_json::Value = - serde_json::from_str(json_str).map_err(|_| ProgramError::InvalidInstructionData)?; - let challenge = parsed["challenge"] - .as_str() - .ok_or(ProgramError::InvalidInstructionData)?; - - let msg = Message::try_from_slice(challenge.as_bytes())?; - - msg!("msg: {:?}", msg); - - let now = Clock::get()?.unix_timestamp; - - // check if timestamp is within the allowed drift - require!( - now.saturating_sub(MAX_TIMESTAMP_DRIFT_SECONDS) <= msg.timestamp && - msg.timestamp <= now.saturating_add(MAX_TIMESTAMP_DRIFT_SECONDS), - LazorKitError::InvalidTimestamp - ); - - // check if nonce matches the expected nonce - require!(msg.nonce == last_nonce, LazorKitError::InvalidNonce); - - verify_secp256r1_instruction( - &secp_ix, - authenticator.passkey_pubkey, - message, - args.signature.clone(), - ) -} - -fn handle_execute_cpi( - ctx: &mut Context, - args: &ExecuteInstructionArgs, -) -> Result<()> { - // --- Rule program whitelist check --- - let rule_program_key = ctx.accounts.authenticator_program.key(); - check_whitelist(&ctx.accounts.whitelist_rule_programs, &rule_program_key)?; - - // --- Prepare PDA signer for rule CPI --- - let rule_signer = get_pda_signer( - &args.passkey_pubkey, - ctx.accounts.smart_wallet.key(), - ctx.bumps.smart_wallet_authenticator, - ); - let rule_accounts = &ctx.remaining_accounts[args.rule_data.start_index as usize - ..(args.rule_data.start_index as usize + args.rule_data.length as usize)]; - - // --- Rule instruction discriminator check --- - require!( - args.rule_data.data.get(0..8) == Some(&sighash("global", "check_rule")), - LazorKitError::InvalidRuleInstruction - ); - - // --- Execute rule CPI --- - execute_cpi( - rule_accounts, - &args.rule_data.data, - &ctx.accounts.authenticator_program, - Some(rule_signer), - )?; - - // --- CPI for main instruction --- - let cpi_data = args - .cpi_data - .as_ref() - .ok_or(LazorKitError::InvalidAccountInput)?; - let cpi_accounts = &ctx.remaining_accounts - [cpi_data.start_index as usize..(cpi_data.start_index as usize + cpi_data.length as usize)]; - - // --- Special handling for SOL transfer --- - if cpi_data.data.get(0..4) == Some(&SOL_TRANSFER_DISCRIMINATOR) - && ctx.accounts.cpi_program.key() == anchor_lang::solana_program::system_program::ID - { - require!( - ctx.remaining_accounts.len() >= 2, - LazorKitError::InvalidAccountInput - ); - let amount = u64::from_le_bytes(cpi_data.data[4..12].try_into().unwrap()); - transfer_sol_from_pda( - &ctx.accounts.smart_wallet, - &ctx.remaining_accounts[1].to_account_info(), - amount, - )?; - } else { - // --- Generic CPI with wallet signer --- - let wallet_signer = PdaSigner { - seeds: vec![ - SMART_WALLET_SEED.to_vec(), - ctx.accounts.smart_wallet_config.id.to_le_bytes().to_vec(), - ], - bump: ctx.accounts.smart_wallet_config.bump, - }; - execute_cpi( - cpi_accounts, - &cpi_data.data, - &ctx.accounts.cpi_program, - Some(wallet_signer), - )?; - } - Ok(()) -} - -fn handle_change_program_rule( - ctx: &mut Context, - args: &ExecuteInstructionArgs, -) -> Result<()> { - // --- Change rule program logic --- - let old_rule_program_key = ctx.accounts.authenticator_program.key(); - let new_rule_program_key = ctx.accounts.cpi_program.key(); - let whitelist = &ctx.accounts.whitelist_rule_programs; - let cpi_data = args - .cpi_data - .as_ref() - .ok_or(LazorKitError::InvalidAccountInput)?; - - check_whitelist(whitelist, &old_rule_program_key)?; - check_whitelist(whitelist, &new_rule_program_key)?; - - // --- Destroy/init discriminators check --- - require!( - args.rule_data.data.get(0..8) == Some(&sighash("global", "destroy")), - LazorKitError::InvalidRuleInstruction - ); - require!( - cpi_data.data.get(0..8) == Some(&sighash("global", "init_rule")), - LazorKitError::InvalidRuleInstruction - ); - - // --- Only one of the programs can be the default, and they must differ --- - let default_rule_program = ctx.accounts.config.default_rule_program; - require!( - (old_rule_program_key == default_rule_program - || new_rule_program_key == default_rule_program) - && (old_rule_program_key != new_rule_program_key), - LazorKitError::InvalidRuleProgram - ); - - // --- Update rule program in config --- - ctx.accounts.smart_wallet_config.rule_program = new_rule_program_key; - - // --- Destroy old rule program --- - let rule_signer = get_pda_signer( - &args.passkey_pubkey, - ctx.accounts.smart_wallet.key(), - ctx.bumps.smart_wallet_authenticator, - ); - let rule_accounts = &ctx.remaining_accounts - [args.rule_data.start_index as usize..(args.rule_data.start_index as usize + args.rule_data.length as usize)]; - - execute_cpi( - rule_accounts, - &args.rule_data.data, - &ctx.accounts.authenticator_program, - Some(rule_signer.clone()), - )?; - - // --- Init new rule program --- - let cpi_accounts = &ctx.remaining_accounts - [cpi_data.start_index as usize..(cpi_data.start_index as usize + cpi_data.length as usize)]; - execute_cpi( - cpi_accounts, - &cpi_data.data, - &ctx.accounts.cpi_program, - Some(rule_signer), - )?; - Ok(()) -} - -fn handle_call_rule_program( - ctx: &mut Context, - args: &ExecuteInstructionArgs, -) -> Result<()> { - // --- Call rule program logic --- - let rule_program_key = ctx.accounts.authenticator_program.key(); - check_whitelist(&ctx.accounts.whitelist_rule_programs, &rule_program_key)?; - - // --- Optionally create a new smart wallet authenticator --- - if let Some(new_authenticator_pubkey) = args.create_new_authenticator { - let new_auth = ctx - .accounts - .new_smart_wallet_authenticator - .as_mut() - .ok_or(LazorKitError::InvalidAccountInput)?; - new_auth.smart_wallet = ctx.accounts.smart_wallet.key(); - new_auth.passkey_pubkey = new_authenticator_pubkey; - new_auth.bump = ctx.bumps.new_smart_wallet_authenticator.unwrap_or_default(); - } else { - return Err(LazorKitError::InvalidAccountInput.into()); - } - - let rule_signer = get_pda_signer( - &args.passkey_pubkey, - ctx.accounts.smart_wallet.key(), - ctx.bumps.smart_wallet_authenticator, - ); - let rule_accounts = &ctx.remaining_accounts - [args.rule_data.start_index as usize..(args.rule_data.start_index as usize + args.rule_data.length as usize)]; - execute_cpi( - rule_accounts, - &args.rule_data.data, - &ctx.accounts.authenticator_program, - Some(rule_signer), - )?; - Ok(()) -} - -/// Accounts context for execute_instruction -#[derive(Accounts)] -#[instruction(args: ExecuteInstructionArgs)] -pub struct ExecuteInstruction<'info> { - #[account(mut)] - pub payer: Signer<'info>, - - #[account( - seeds = [Config::PREFIX_SEED], - bump, - owner = ID - )] - pub config: Box>, - - #[account( - mut, - seeds = [SMART_WALLET_SEED, smart_wallet_config.id.to_le_bytes().as_ref()], - bump, - owner = ID, - )] - /// CHECK: Only used for key and seeds. - pub smart_wallet: UncheckedAccount<'info>, - - #[account( - mut, - seeds = [SmartWalletConfig::PREFIX_SEED, smart_wallet.key().as_ref()], - bump, - owner = ID, - )] - pub smart_wallet_config: Box>, - - #[account( - seeds = [ - SmartWalletAuthenticator::PREFIX_SEED, - smart_wallet.key().as_ref(), - args.passkey_pubkey.to_hashed_bytes(smart_wallet.key()).as_ref() - ], - bump, - owner = ID, - )] - pub smart_wallet_authenticator: Box>, - - #[account( - seeds = [WhitelistRulePrograms::PREFIX_SEED], - bump, - owner = ID - )] - pub whitelist_rule_programs: Box>, - - /// CHECK: Used for rule CPI. - pub authenticator_program: UncheckedAccount<'info>, - - #[account(address = IX_ID)] - /// CHECK: Sysvar for instructions. - pub ix_sysvar: UncheckedAccount<'info>, - - pub system_program: Program<'info, System>, - - /// CHECK: Used for CPI, not deserialized. - pub cpi_program: UncheckedAccount<'info>, - - #[account( - init_if_needed, // Change to init_if_needed to handle both cases - payer = payer, - space = 8 + SmartWalletAuthenticator::INIT_SPACE, - seeds = [ - SmartWalletAuthenticator::PREFIX_SEED, // Add this constant seed - smart_wallet.key().as_ref(), - args.create_new_authenticator.unwrap_or([0; 33]).to_hashed_bytes(smart_wallet.key()).as_ref() - ], - bump, - )] - pub new_smart_wallet_authenticator: Option>, -} diff --git a/programs/lazorkit/src/instructions/initialize.rs b/programs/lazorkit/src/instructions/initialize.rs deleted file mode 100644 index b1a0417..0000000 --- a/programs/lazorkit/src/instructions/initialize.rs +++ /dev/null @@ -1,55 +0,0 @@ -use anchor_lang::prelude::*; - -use crate::state::{Config, SmartWalletSeq, WhitelistRulePrograms}; - -pub fn initialize(ctx: Context) -> Result<()> { - let whitelist_rule_programs = &mut ctx.accounts.whitelist_rule_programs; - whitelist_rule_programs.list = vec![ctx.accounts.default_rule_program.key()]; - - let smart_wallet_seq = &mut ctx.accounts.smart_wallet_seq; - smart_wallet_seq.seq = 0; - - let config: &mut Box> = &mut ctx.accounts.config; - config.authority = ctx.accounts.signer.key(); - config.create_smart_wallet_fee = 0; // LAMPORTS - config.default_rule_program = ctx.accounts.default_rule_program.key(); - Ok(()) -} - -#[derive(Accounts)] -pub struct Initialize<'info> { - #[account(mut)] - pub signer: Signer<'info>, - - #[account( - init_if_needed, - payer = signer, - space = 8 + Config::INIT_SPACE, - seeds = [Config::PREFIX_SEED], - bump, - )] - pub config: Box>, - - #[account( - init_if_needed, - payer = signer, - space = 8 + WhitelistRulePrograms::INIT_SPACE, - seeds = [WhitelistRulePrograms::PREFIX_SEED], - bump - )] - pub whitelist_rule_programs: Box>, - - #[account( - init_if_needed, - payer = signer, - space = 8 + SmartWalletSeq::INIT_SPACE, - seeds = [SmartWalletSeq::PREFIX_SEED], - bump - )] - pub smart_wallet_seq: Box>, - - /// CHECK: - pub default_rule_program: UncheckedAccount<'info>, - - pub system_program: Program<'info, System>, -} diff --git a/programs/lazorkit/src/instructions/mod.rs b/programs/lazorkit/src/instructions/mod.rs deleted file mode 100644 index de65549..0000000 --- a/programs/lazorkit/src/instructions/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod create_smart_wallet; -mod execute_instruction; -mod initialize; -mod upsert_whitelist_rule_programs; - -pub use create_smart_wallet::*; -pub use execute_instruction::*; -pub use initialize::*; -pub use upsert_whitelist_rule_programs::*; diff --git a/programs/lazorkit/src/instructions/upsert_whitelist_rule_programs.rs b/programs/lazorkit/src/instructions/upsert_whitelist_rule_programs.rs deleted file mode 100644 index 18d0206..0000000 --- a/programs/lazorkit/src/instructions/upsert_whitelist_rule_programs.rs +++ /dev/null @@ -1,40 +0,0 @@ -use anchor_lang::prelude::*; - -use crate::{ - state::{Config, WhitelistRulePrograms}, - ID, -}; - -pub fn upsert_whitelist_rule_programs( - ctx: Context, - program_id: Pubkey, -) -> Result<()> { - let whitelist = &mut ctx.accounts.whitelist_rule_programs; - - if !whitelist.list.contains(&program_id) { - whitelist.list.push(program_id); - } - - Ok(()) -} - -#[derive(Accounts)] -pub struct UpsertWhitelistRulePrograms<'info> { - #[account(mut)] - pub authority: Signer<'info>, - - #[account( - seeds = [Config::PREFIX_SEED], - bump, - has_one = authority - )] - pub config: Box>, - - #[account( - mut, - seeds = [WhitelistRulePrograms::PREFIX_SEED], - bump, - owner = ID, - )] - pub whitelist_rule_programs: Account<'info, WhitelistRulePrograms>, -} diff --git a/programs/lazorkit/src/lib.rs b/programs/lazorkit/src/lib.rs deleted file mode 100644 index a45685a..0000000 --- a/programs/lazorkit/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -use anchor_lang::prelude::*; - -pub mod constants; -pub mod error; -pub mod instructions; -pub mod state; -pub mod utils; - -use constants::PASSKEY_SIZE; -use instructions::*; - -declare_id!("6Jh4kA4rkZquv9XofKqgbyrRcTDF19uM5HL4xyh6gaSo"); - -/// The Lazor Kit program provides smart wallet functionality with passkey authentication -#[program] -pub mod lazorkit { - use super::*; - - /// Initialize the program by creating the sequence tracker - pub fn initialize(ctx: Context) -> Result<()> { - instructions::initialize(ctx) - } - - /// Create a new smart wallet with passkey authentication - pub fn create_smart_wallet( - ctx: Context, - passkey_pubkey: [u8; PASSKEY_SIZE], - credential_id: Vec, - rule_data: Vec, - ) -> Result<()> { - instructions::create_smart_wallet(ctx, passkey_pubkey, credential_id, rule_data) - } - - /// Execute an instruction with passkey authentication - pub fn execute_instruction( - ctx: Context, - args: ExecuteInstructionArgs, - ) -> Result<()> { - instructions::execute_instruction(ctx, args) - } - - /// Update the list of whitelisted rule programs - pub fn upsert_whitelist_rule_programs( - ctx: Context, - program_id: Pubkey, - ) -> Result<()> { - instructions::upsert_whitelist_rule_programs(ctx, program_id) - } -} diff --git a/programs/lazorkit/src/state/config.rs b/programs/lazorkit/src/state/config.rs deleted file mode 100644 index ed682c6..0000000 --- a/programs/lazorkit/src/state/config.rs +++ /dev/null @@ -1,13 +0,0 @@ -use anchor_lang::prelude::*; - -#[account] -#[derive(Default, InitSpace)] -pub struct Config { - pub authority: Pubkey, - pub create_smart_wallet_fee: u64, - pub default_rule_program: Pubkey, -} - -impl Config { - pub const PREFIX_SEED: &'static [u8] = b"config"; -} diff --git a/programs/lazorkit/src/state/message.rs b/programs/lazorkit/src/state/message.rs deleted file mode 100644 index 93e5f52..0000000 --- a/programs/lazorkit/src/state/message.rs +++ /dev/null @@ -1,7 +0,0 @@ -use anchor_lang::prelude::*; - -#[derive(Default, AnchorSerialize, AnchorDeserialize, Debug)] -pub struct Message { - pub nonce: u64, - pub timestamp: i64, -} diff --git a/programs/lazorkit/src/state/mod.rs b/programs/lazorkit/src/state/mod.rs deleted file mode 100644 index aa1fd80..0000000 --- a/programs/lazorkit/src/state/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod config; -mod smart_wallet_authenticator; -mod smart_wallet_config; -mod smart_wallet_seq; -mod whitelist_rule_programs; -mod message; - -pub use config::*; -pub use smart_wallet_authenticator::*; -pub use smart_wallet_config::*; -pub use smart_wallet_seq::*; -pub use whitelist_rule_programs::*; -pub use message::*; diff --git a/programs/lazorkit/src/state/smart_wallet_authenticator.rs b/programs/lazorkit/src/state/smart_wallet_authenticator.rs deleted file mode 100644 index 5e05c29..0000000 --- a/programs/lazorkit/src/state/smart_wallet_authenticator.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::constants::PASSKEY_SIZE; -use anchor_lang::prelude::*; - -/// Account that stores authentication data for a smart wallet -#[account] -#[derive(Debug, InitSpace)] -pub struct SmartWalletAuthenticator { - /// The public key of the passkey that can authorize transactions - pub passkey_pubkey: [u8; PASSKEY_SIZE], - /// The smart wallet this authenticator belongs to - pub smart_wallet: Pubkey, - - /// The credential ID this authenticator belongs to - #[max_len(256)] - pub credential_id: Vec, - - /// Bump seed for PDA derivation - pub bump: u8, -} - -impl SmartWalletAuthenticator { - pub const PREFIX_SEED: &'static [u8] = b"smart_wallet_authenticator"; -} diff --git a/programs/lazorkit/src/state/smart_wallet_config.rs b/programs/lazorkit/src/state/smart_wallet_config.rs deleted file mode 100644 index 3c0b307..0000000 --- a/programs/lazorkit/src/state/smart_wallet_config.rs +++ /dev/null @@ -1,19 +0,0 @@ -use anchor_lang::prelude::*; - -/// Data account for a smart wallet -#[account] -#[derive(Default, InitSpace)] -pub struct SmartWalletConfig { - /// Unique identifier for this smart wallet - pub id: u64, - /// Optional rule program that governs this wallet's operations - pub rule_program: Pubkey, - // last nonce used for message verification - pub last_nonce: u64, - /// Bump seed for PDA derivation - pub bump: u8, -} - -impl SmartWalletConfig { - pub const PREFIX_SEED: &'static [u8] = b"smart_wallet_config"; -} diff --git a/programs/lazorkit/src/state/smart_wallet_seq.rs b/programs/lazorkit/src/state/smart_wallet_seq.rs deleted file mode 100644 index 6140b63..0000000 --- a/programs/lazorkit/src/state/smart_wallet_seq.rs +++ /dev/null @@ -1,15 +0,0 @@ -use anchor_lang::prelude::*; - -/// Account that maintains the sequence number for smart wallet creation -#[account] -#[derive(Debug, InitSpace)] -pub struct SmartWalletSeq { - /// Current sequence number, incremented for each new smart wallet - pub seq: u64, - /// Bump seed for PDA derivation - pub bump: u8, -} - -impl SmartWalletSeq { - pub const PREFIX_SEED: &'static [u8] = b"smart_wallet_seq"; -} diff --git a/programs/lazorkit/src/state/whitelist_rule_programs.rs b/programs/lazorkit/src/state/whitelist_rule_programs.rs deleted file mode 100644 index 7c20c29..0000000 --- a/programs/lazorkit/src/state/whitelist_rule_programs.rs +++ /dev/null @@ -1,16 +0,0 @@ -use anchor_lang::prelude::*; - -/// Account that stores whitelisted rule program addresses -#[account] -#[derive(Debug, InitSpace)] -pub struct WhitelistRulePrograms { - /// List of whitelisted program addresses - #[max_len(10)] - pub list: Vec, - /// Bump seed for PDA derivation - pub bump: u8, -} - -impl WhitelistRulePrograms { - pub const PREFIX_SEED: &'static [u8] = b"whitelist_rule_programs"; -} diff --git a/programs/lazorkit/src/utils.rs b/programs/lazorkit/src/utils.rs deleted file mode 100644 index 02c6791..0000000 --- a/programs/lazorkit/src/utils.rs +++ /dev/null @@ -1,253 +0,0 @@ -use crate::constants::SECP256R1_ID; -use crate::{error::LazorKitError, ID}; -use anchor_lang::solana_program::{ - instruction::Instruction, - program::{invoke, invoke_signed}, -}; -use anchor_lang::{prelude::*, solana_program::hash::hash}; - -// Constants for Secp256r1 signature verification -const SECP_HEADER_SIZE: u16 = 14; -const SECP_DATA_START: u16 = 2 + SECP_HEADER_SIZE; -const SECP_PUBKEY_SIZE: u16 = 33; -const SECP_SIGNATURE_SIZE: u16 = 64; -const SECP_HEADER_TOTAL: usize = 16; - -/// Convenience wrapper to pass PDA seeds & bump into [`execute_cpi`]. -/// -/// Anchor expects PDA seeds as `&[&[u8]]` when calling `invoke_signed`. Generating that slice of -/// byte-slices at every call-site is error-prone, so we hide the details behind this struct. The -/// helper converts the `Vec>` into the required `&[&[u8]]` on the stack just before the -/// CPI. -#[derive(Clone)] -pub struct PdaSigner { - /// PDA derivation seeds **without** the trailing bump. - pub seeds: Vec>, - /// The bump associated with the PDA. - pub bump: u8, -} - -/// Helper to check if a slice matches a pattern -#[inline] -pub fn slice_eq(a: &[u8], b: &[u8]) -> bool { - a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x == y) -} - -/// Execute a Cross-Program Invocation (CPI). -/// -/// * `accounts` – slice of `AccountInfo` that will be forwarded to the target program. -/// * `data` – raw instruction data **as a slice**; passing a slice removes the need for the -/// caller to allocate a new `Vec` every time a CPI is performed. A single allocation is -/// still required internally when constructing the `Instruction`, but this change avoids an -/// additional clone at every call-site. -/// * `program` – account info of the program to invoke. -/// * `signer` – optional PDA signer information. When provided, the seeds are appended with the -/// bump and the CPI is invoked with `invoke_signed`. -pub fn execute_cpi( - accounts: &[AccountInfo], - data: &[u8], - program: &AccountInfo, - signer: Option, -) -> Result<()> { - // Allocate a single Vec for the instruction – unavoidable because the SDK expects owned - // data. This keeps the allocation inside the helper and eliminates clones at the call-site. - let ix = create_cpi_instruction(accounts, data.to_vec(), program, &signer); - - match signer { - Some(s) => { - // Build seed slice **once** to avoid repeated heap allocations. - let mut seed_slices: Vec<&[u8]> = s.seeds.iter().map(|s| s.as_slice()).collect(); - let bump_slice = [s.bump]; - seed_slices.push(&bump_slice); - invoke_signed(&ix, accounts, &[&seed_slices]) - } - None => invoke(&ix, accounts), - } - .map_err(Into::into) -} - -/// Create a CPI instruction with proper account meta configuration -fn create_cpi_instruction( - accounts: &[AccountInfo], - data: Vec, - program: &AccountInfo, - pda_signer: &Option, -) -> Instruction { - let pda_pubkey = pda_signer.as_ref().map(|pda| { - let seed_slices: Vec<&[u8]> = pda.seeds.iter().map(|s| s.as_slice()).collect(); - Pubkey::find_program_address(&seed_slices, &ID).0 - }); - - Instruction { - program_id: program.key(), - accounts: accounts - .iter() - .map(|acc| { - let is_signer = if let Some(pda_key) = pda_pubkey { - acc.is_signer || *acc.key == pda_key - } else { - acc.is_signer - }; - - AccountMeta { - pubkey: *acc.key, - is_signer, - is_writable: acc.is_writable, - } - }) - .collect(), - data, - } -} - -/// Verify a Secp256r1 signature instruction -pub fn verify_secp256r1_instruction( - ix: &Instruction, - pubkey: [u8; SECP_PUBKEY_SIZE as usize], - msg: Vec, - sig: Vec, -) -> Result<()> { - let expected_len = - (SECP_DATA_START + SECP_PUBKEY_SIZE + SECP_SIGNATURE_SIZE) as usize + msg.len(); - if ix.program_id != SECP256R1_ID || !ix.accounts.is_empty() || ix.data.len() != expected_len { - return Err(LazorKitError::InvalidLengthForVerification.into()); - } - verify_secp256r1_data(&ix.data, pubkey, msg, sig) -} - -/// Verify the data portion of a Secp256r1 signature -fn verify_secp256r1_data( - data: &[u8], - public_key: [u8; SECP_PUBKEY_SIZE as usize], - message: Vec, - signature: Vec, -) -> Result<()> { - let msg_len = message.len() as u16; - let offsets = calculate_secp_offsets(msg_len); - - if !verify_secp_header(data, &offsets) { - return Err(LazorKitError::VerifyHeaderMismatchError.into()); - } - - if !verify_secp_data(data, &public_key, &signature, &message) { - return Err(LazorKitError::VerifyDataMismatchError.into()); - } - - Ok(()) -} - -/// Calculate offsets for Secp256r1 signature verification -#[derive(Debug)] -struct SecpOffsets { - pubkey_offset: u16, - sig_offset: u16, - msg_offset: u16, - msg_len: u16, -} - -#[inline] -fn calculate_secp_offsets(msg_len: u16) -> SecpOffsets { - SecpOffsets { - pubkey_offset: SECP_DATA_START, - sig_offset: SECP_DATA_START + SECP_PUBKEY_SIZE, - msg_offset: SECP_DATA_START + SECP_PUBKEY_SIZE + SECP_SIGNATURE_SIZE, - msg_len, - } -} - -#[inline] -fn verify_secp_header(data: &[u8], offsets: &SecpOffsets) -> bool { - data[0] == 1 - && u16::from_le_bytes(data[2..=3].try_into().unwrap()) == offsets.sig_offset - && u16::from_le_bytes(data[4..=5].try_into().unwrap()) == 0xFFFF - && u16::from_le_bytes(data[6..=7].try_into().unwrap()) == offsets.pubkey_offset - && u16::from_le_bytes(data[8..=9].try_into().unwrap()) == 0xFFFF - && u16::from_le_bytes(data[10..=11].try_into().unwrap()) == offsets.msg_offset - && u16::from_le_bytes(data[12..=13].try_into().unwrap()) == offsets.msg_len - && u16::from_le_bytes(data[14..=15].try_into().unwrap()) == 0xFFFF -} - -#[inline] -fn verify_secp_data(data: &[u8], public_key: &[u8], signature: &[u8], message: &[u8]) -> bool { - let pubkey_range = SECP_HEADER_TOTAL..SECP_HEADER_TOTAL + SECP_PUBKEY_SIZE as usize; - let sig_range = pubkey_range.end..pubkey_range.end + SECP_SIGNATURE_SIZE as usize; - let msg_range = sig_range.end..; - - data[pubkey_range] == public_key[..] - && data[sig_range] == signature[..] - && data[msg_range] == message[..] -} - -/// Extension trait for passkey operations -pub trait PasskeyExt { - fn to_hashed_bytes(&self, wallet: Pubkey) -> [u8; 32]; -} - -impl PasskeyExt for [u8; SECP_PUBKEY_SIZE as usize] { - #[inline] - fn to_hashed_bytes(&self, wallet: Pubkey) -> [u8; 32] { - let mut buf = [0u8; 65]; - buf[..SECP_PUBKEY_SIZE as usize].copy_from_slice(self); - buf[SECP_PUBKEY_SIZE as usize..].copy_from_slice(&wallet.to_bytes()); - hash(&buf).to_bytes() - } -} - -/// Transfer SOL from a PDA-owned account -#[inline] -pub fn transfer_sol_from_pda(from: &AccountInfo, to: &AccountInfo, amount: u64) -> Result<()> { - // Ensure the 'from' account is owned by this program - if *from.owner != ID { - return Err(ProgramError::IllegalOwner.into()); - } - // Debit from source account - **from.try_borrow_mut_lamports()? -= amount; - // Credit to destination account - **to.try_borrow_mut_lamports()? += amount; - Ok(()) -} - -/// Helper to get sighash for anchor instructions -pub fn sighash(namespace: &str, name: &str) -> [u8; 8] { - let preimage = format!("{}:{}", namespace, name); - let mut out = [0u8; 8]; - out.copy_from_slice( - &anchor_lang::solana_program::hash::hash(preimage.as_bytes()).to_bytes()[..8], - ); - out -} - -/// Helper: Get a slice of accounts from remaining_accounts -pub fn get_account_slice<'a>( - accounts: &'a [AccountInfo<'a>], - start: u8, - len: u8, -) -> Result<&'a [AccountInfo<'a>]> { - accounts - .get(start as usize..(start as usize + len as usize)) - .ok_or(crate::error::LazorKitError::InvalidAccountInput.into()) -} - -/// Helper: Create a PDA signer struct -pub fn get_pda_signer(passkey: &[u8; 33], wallet: Pubkey, bump: u8) -> PdaSigner { - PdaSigner { - seeds: vec![ - crate::state::SmartWalletAuthenticator::PREFIX_SEED.to_vec(), - wallet.to_bytes().to_vec(), - passkey.to_hashed_bytes(wallet).to_vec(), - ], - bump, - } -} - -/// Helper: Check if a program is in the whitelist -pub fn check_whitelist( - whitelist: &crate::state::WhitelistRulePrograms, - program: &Pubkey, -) -> Result<()> { - require!( - whitelist.list.contains(program), - crate::error::LazorKitError::InvalidRuleProgram - ); - Ok(()) -} diff --git a/programs/transfer_limit/Cargo.toml b/programs/transfer_limit/Cargo.toml deleted file mode 100644 index d38d366..0000000 --- a/programs/transfer_limit/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "transfer_limit" -version = "0.1.0" -description = "Created with Anchor" -edition = "2021" - -[lib] -crate-type = ["cdylib", "lib"] -name = "transfer_limit" - -[features] -default = [] -cpi = ["no-entrypoint"] -no-entrypoint = [] -no-idl = [] -no-log-ix-name = [] -anchor-debug = [] -custom-heap = [] -solana = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] - - -[dependencies] -anchor-lang = { version = "0.31.0", features = ["init-if-needed"] } -anchor-spl = { version = "0.31.0", features = ["token", "metadata"] } - -lazorkit = { path = "../lazorkit", features = ["no-entrypoint", "cpi"] } - diff --git a/programs/transfer_limit/Xargo.toml b/programs/transfer_limit/Xargo.toml deleted file mode 100644 index 475fb71..0000000 --- a/programs/transfer_limit/Xargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.bpfel-unknown-unknown.dependencies.std] -features = [] diff --git a/programs/transfer_limit/src/errors.rs b/programs/transfer_limit/src/errors.rs deleted file mode 100644 index 98e5993..0000000 --- a/programs/transfer_limit/src/errors.rs +++ /dev/null @@ -1,30 +0,0 @@ -use anchor_lang::error_code; - -#[error_code] -pub enum TransferLimitError { - MemberNotAdmin, - - InvalidNewPasskey, - - InvalidTokenAccount, - - InvalidToken, - - InvalidBalance, - - InvalidTransferAmount, - - RuleNotInitialized, - - InvalidRuleAccount, - - InvalidAccountInput, - - UnAuthorize, - - InvalidBump, - - MemberNotInitialized, - - TransferAmountExceedLimit, -} diff --git a/programs/transfer_limit/src/instructions/add_member.rs b/programs/transfer_limit/src/instructions/add_member.rs deleted file mode 100644 index 0e61034..0000000 --- a/programs/transfer_limit/src/instructions/add_member.rs +++ /dev/null @@ -1,81 +0,0 @@ -use anchor_lang::prelude::*; -use lazorkit::{program::Lazorkit, state::SmartWalletAuthenticator, utils::PasskeyExt}; - -use crate::{ - errors::TransferLimitError, - state::{Member, MemberType}, - ID, -}; - -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct AddMemberArgs { - pub member: Pubkey, -} - -pub fn add_member(ctx: Context, new_passkey_pubkey: [u8; 33], bump: u8) -> Result<()> { - let member = &mut ctx.accounts.member; - let new_smart_wallet_authenticator = &mut ctx.accounts.new_smart_wallet_authenticator; - let smart_wallet_authenticator = &mut ctx.accounts.smart_wallet_authenticator; - - let seeds: &[&[u8]] = - &[&new_passkey_pubkey.to_hashed_bytes(smart_wallet_authenticator.smart_wallet.key())]; - let (expected_pubkey, expected_bump) = - Pubkey::find_program_address(seeds, &ctx.accounts.lazorkit.key()); - - require!( - expected_pubkey == new_smart_wallet_authenticator.key(), - TransferLimitError::InvalidNewPasskey - ); - - require!(expected_bump == bump, TransferLimitError::InvalidBump); - - member.set_inner(Member { - owner: new_smart_wallet_authenticator.key(), - member_type: MemberType::Member, - smart_wallet: smart_wallet_authenticator.smart_wallet, - bump: expected_bump, - is_initialized: true, - }); - - Ok(()) -} - -#[derive(Accounts)] -#[instruction(new_passkey_pubkey: [u8; 33], bump: u8)] -pub struct AddMember<'info> { - #[account(mut)] - pub payer: Signer<'info>, - - #[account( - owner = lazorkit.key(), - signer, - )] - pub smart_wallet_authenticator: Account<'info, SmartWalletAuthenticator>, - - #[account( - owner = lazorkit.key(), - )] - /// CHECK: - pub new_smart_wallet_authenticator: UncheckedAccount<'info>, - - #[account( - seeds = [Member::PREFIX_SEED, smart_wallet_authenticator.smart_wallet.key().as_ref(), smart_wallet_authenticator.key().as_ref()], - bump, - owner = ID, - constraint = admin.member_type == MemberType::Admin, - )] - pub admin: Account<'info, Member>, - - #[account( - init, - payer = payer, - space = 8 + Member::INIT_SPACE, - seeds = [Member::PREFIX_SEED, smart_wallet_authenticator.smart_wallet.key().as_ref(), new_smart_wallet_authenticator.key().as_ref()], - bump, - )] - pub member: Account<'info, Member>, - - pub lazorkit: Program<'info, Lazorkit>, - - pub system_program: Program<'info, System>, -} diff --git a/programs/transfer_limit/src/instructions/check_rule.rs b/programs/transfer_limit/src/instructions/check_rule.rs deleted file mode 100644 index cb5764e..0000000 --- a/programs/transfer_limit/src/instructions/check_rule.rs +++ /dev/null @@ -1,95 +0,0 @@ -use anchor_lang::prelude::*; -use anchor_lang::system_program::ID as SYSTEM_ID; -use anchor_spl::token::ID as SPL_TOKEN; -use lazorkit::{ - constants::SOL_TRANSFER_DISCRIMINATOR, program::Lazorkit, state::SmartWalletAuthenticator, -}; - -use crate::{ - errors::TransferLimitError, - state::{Member, MemberType, RuleData}, - ID, -}; - -pub fn check_rule( - ctx: Context, - _token: Option, - cpi_data: Vec, - program_id: Pubkey, -) -> Result<()> { - let member = &ctx.accounts.member; - let rule_data = &ctx.accounts.rule_data; - - // Admins can bypass the transfer limit check - if member.member_type == MemberType::Admin { - return Ok(()); - } - - require!( - member.is_initialized, - TransferLimitError::MemberNotInitialized - ); - require!(rule_data.is_initialized, TransferLimitError::UnAuthorize); - - let amount = if program_id == SYSTEM_ID { - if let Some(discriminator) = cpi_data.get(0..4) { - if discriminator == SOL_TRANSFER_DISCRIMINATOR { - u64::from_le_bytes(cpi_data[4..12].try_into().unwrap()) - } else { - return Err(TransferLimitError::UnAuthorize.into()); - } - } else { - return Err(TransferLimitError::UnAuthorize.into()); - } - } else if program_id == SPL_TOKEN { - // Handle SPL token transfer instruction (transfer: instruction 3) - if let Some(&instruction_index) = cpi_data.get(0) { - if instruction_index == 3 { - // This is a Transfer instruction - if cpi_data.len() >= 9 { - u64::from_le_bytes(cpi_data[1..9].try_into().unwrap()) - } else { - return Err(TransferLimitError::UnAuthorize.into()); - } - } else { - return Err(TransferLimitError::UnAuthorize.into()); - } - } else { - return Err(TransferLimitError::UnAuthorize.into()); - } - } else { - return Err(TransferLimitError::UnAuthorize.into()); - }; - - if amount > rule_data.limit_amount { - return Err(TransferLimitError::TransferAmountExceedLimit.into()); - } - - Ok(()) -} - -#[derive(Accounts)] -#[instruction(token: Option)] -pub struct CheckRule<'info> { - #[account( - owner = lazorkit.key(), - signer, - )] - pub smart_wallet_authenticator: Account<'info, SmartWalletAuthenticator>, - - #[account( - seeds = [Member::PREFIX_SEED, smart_wallet_authenticator.smart_wallet.key().as_ref(), smart_wallet_authenticator.key().as_ref()], - bump, - owner = ID, - )] - pub member: Account<'info, Member>, - - #[account( - seeds = [RuleData::PREFIX_SEED, smart_wallet_authenticator.smart_wallet.key().as_ref(), token.as_ref().unwrap_or(&Pubkey::default()).as_ref()], - bump, - owner = ID, - )] - pub rule_data: Box>, - - pub lazorkit: Program<'info, Lazorkit>, -} diff --git a/programs/transfer_limit/src/instructions/init_rule.rs b/programs/transfer_limit/src/instructions/init_rule.rs deleted file mode 100644 index 49cac6c..0000000 --- a/programs/transfer_limit/src/instructions/init_rule.rs +++ /dev/null @@ -1,90 +0,0 @@ -use anchor_lang::prelude::*; -use lazorkit::{ - constants::SMART_WALLET_SEED, - program::Lazorkit, - state::{SmartWalletAuthenticator, SmartWalletConfig}, -}; - -use crate::{errors::TransferLimitError, state::*}; - -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct InitRuleArgs { - pub passkey_pubkey: [u8; 33], - pub token: Option, - pub limit_amount: u64, - pub limit_period: u64, -} - -pub fn init_rule(ctx: Context, args: InitRuleArgs) -> Result<()> { - let rule_data = &mut ctx.accounts.rule_data; - rule_data.set_inner(RuleData { - token: args.token, - limit_amount: args.limit_amount, - bump: ctx.bumps.rule_data, - is_initialized: true, - }); - - let member = &mut ctx.accounts.member; - if !member.is_initialized { - member.set_inner(Member { - smart_wallet: ctx.accounts.smart_wallet.key(), - owner: ctx.accounts.smart_wallet_authenticator.key(), - bump: ctx.bumps.member, - is_initialized: true, - member_type: MemberType::Admin, - }); - } else { - require!( - member.member_type == MemberType::Admin, - TransferLimitError::MemberNotAdmin - ); - } - Ok(()) -} - -#[derive(Accounts)] -#[instruction(args: InitRuleArgs)] -pub struct InitRule<'info> { - #[account(mut)] - pub payer: Signer<'info>, - - #[account( - seeds = [SMART_WALLET_SEED, smart_wallet_config.id.to_le_bytes().as_ref()], - bump, - seeds::program = lazorkit.key(), // LazorKit ID - )] - /// CHECK - pub smart_wallet: UncheckedAccount<'info>, - - #[account( - init_if_needed, - payer = payer, - space = 8 + Member::INIT_SPACE, - seeds = [Member::PREFIX_SEED, smart_wallet.key().as_ref(), smart_wallet_authenticator.key().as_ref()], - bump, - )] - pub member: Box>, - - #[account( - init, - payer = payer, - space = 8 + RuleData::INIT_SPACE, - seeds = [RuleData::PREFIX_SEED, smart_wallet.key().as_ref(), args.token.as_ref().unwrap_or(&Pubkey::default()).as_ref()], - bump, - )] - pub rule_data: Box>, - - #[account( - seeds = [SmartWalletConfig::PREFIX_SEED, smart_wallet.key().as_ref()], - bump, - seeds::program = lazorkit.key(), // LazorKit ID - )] - pub smart_wallet_config: Account<'info, SmartWalletConfig>, - - #[account(signer)] - pub smart_wallet_authenticator: Account<'info, SmartWalletAuthenticator>, - - pub lazorkit: Program<'info, Lazorkit>, - - pub system_program: Program<'info, System>, -} diff --git a/programs/transfer_limit/src/instructions/mod.rs b/programs/transfer_limit/src/instructions/mod.rs deleted file mode 100644 index a0b2418..0000000 --- a/programs/transfer_limit/src/instructions/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod add_member; -mod check_rule; -mod init_rule; - -pub use add_member::*; -pub use check_rule::*; -pub use init_rule::*; diff --git a/programs/transfer_limit/src/instructions/remove_member.rs b/programs/transfer_limit/src/instructions/remove_member.rs deleted file mode 100644 index e69de29..0000000 diff --git a/programs/transfer_limit/src/lib.rs b/programs/transfer_limit/src/lib.rs deleted file mode 100644 index 0b6b9b6..0000000 --- a/programs/transfer_limit/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -use anchor_lang::prelude::*; - -mod errors; -mod instructions; -mod state; - -use instructions::*; - -declare_id!("EEVtLAZVcyzrEc4LLfk8WB749uAkLsScbCVrjtQv3yQB"); - -#[program] -pub mod transfer_limit { - use super::*; - - pub fn init_rule(ctx: Context, init_rule_args: InitRuleArgs) -> Result<()> { - instructions::init_rule(ctx, init_rule_args) - } - - pub fn add_member( - ctx: Context, - new_passkey_pubkey: [u8; 33], - bump: u8, - ) -> Result<()> { - instructions::add_member(ctx, new_passkey_pubkey, bump) - } - - pub fn check_rule( - ctx: Context, - token: Option, - cpi_data: Vec, - program_id: Pubkey, - ) -> Result<()> { - instructions::check_rule(ctx, token, cpi_data, program_id) - } - - // pub fn execute_instruction<'c: 'info, 'info>( - // ctx: Context<'_, '_, 'c, 'info, ExecuteInstruction<'info>>, - // args: ExecuteInstructionArgs, - // ) -> Result<()> { - // instructions::execute_instruction(ctx, args) - // } -} diff --git a/programs/transfer_limit/src/state/member.rs b/programs/transfer_limit/src/state/member.rs deleted file mode 100644 index 354aebb..0000000 --- a/programs/transfer_limit/src/state/member.rs +++ /dev/null @@ -1,22 +0,0 @@ -use anchor_lang::prelude::*; - -#[derive(Default, InitSpace, Clone, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] -pub enum MemberType { - Admin, - #[default] - Member, -} - -#[derive(Default, InitSpace)] -#[account] -pub struct Member { - pub owner: Pubkey, - pub member_type: MemberType, - pub smart_wallet: Pubkey, - pub bump: u8, - pub is_initialized: bool, -} - -impl Member { - pub const PREFIX_SEED: &'static [u8] = b"member"; -} diff --git a/programs/transfer_limit/src/state/mod.rs b/programs/transfer_limit/src/state/mod.rs deleted file mode 100644 index fdeab39..0000000 --- a/programs/transfer_limit/src/state/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod member; -mod record; -mod rule; - -pub use member::*; -pub use rule::*; diff --git a/programs/transfer_limit/src/state/record.rs b/programs/transfer_limit/src/state/record.rs deleted file mode 100644 index c2d1cc7..0000000 --- a/programs/transfer_limit/src/state/record.rs +++ /dev/null @@ -1,5 +0,0 @@ -use anchor_lang::prelude::*; - -#[account] -#[derive(Debug)] -pub struct Record {} diff --git a/programs/transfer_limit/src/state/rule.rs b/programs/transfer_limit/src/state/rule.rs deleted file mode 100644 index c1f54ed..0000000 --- a/programs/transfer_limit/src/state/rule.rs +++ /dev/null @@ -1,14 +0,0 @@ -use anchor_lang::prelude::*; - -#[account] -#[derive(Default, InitSpace)] -pub struct RuleData { - pub token: Option, - pub limit_amount: u64, - pub bump: u8, - pub is_initialized: bool, -} - -impl RuleData { - pub const PREFIX_SEED: &'static [u8] = b"rule_data"; -} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..08eb6dd --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.89.0" +components = ["rustfmt", "clippy", "rust-analyzer"] \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..0c1612a --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,23 @@ +# Rustfmt configuration for Solana projects + +# Basic formatting options +edition = "2021" +max_width = 100 +tab_spaces = 4 +newline_style = "Unix" + +# Import organization +imports_granularity = "Crate" +group_imports = "StdExternalCrate" +reorder_imports = true + +reorder_modules = true +match_block_trailing_comma = true +use_field_init_shorthand = true +wrap_comments = true +format_code_in_doc_comments = true +format_strings = true +normalize_comments = true +normalize_doc_attributes = true +merge_derives = true +use_try_shorthand = true \ No newline at end of file diff --git a/scripts/build-all.sh b/scripts/build-all.sh new file mode 100755 index 0000000..a521dbf --- /dev/null +++ b/scripts/build-all.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Configuration +PROGRAM_ID=$1 +ROOT_DIR=$(pwd) +PROGRAM_DIR="$ROOT_DIR/program" +SDK_DIR="$ROOT_DIR/sdk" + +if [ -z "$PROGRAM_ID" ]; then + echo "Usage: $0 " + echo "Example: $0 2m47smrvCRpuqAyX2dLqPxpAC1658n1BAQga1wRCsQiT" + exit 1 +fi + +echo "--- 🚀 Starting LazorKit Full Sync Workflow ---" + +# Step 1: Update Program ID everywhere +echo "[1/4] Syncing Program ID to $PROGRAM_ID..." +./scripts/sync-program-id.sh "$PROGRAM_ID" + +# Step 2: Build Rust Program +echo "[2/4] Building Rust Program..." +cargo build-sbf + +# Step 3: Generate IDL using Shank +echo "[3/4] Generating IDL..." +cd "$PROGRAM_DIR" +# Assuming shank is installed. If not, this will fail with a clear msg. +if command -v shank &> /dev/null; then + shank idl -o . --out-filename lazor_kit.json -p "$PROGRAM_ID" +else + echo "❌ Error: shank CLI not found. Please install it with 'cargo install shank-cli'." + exit 1 +fi + +# Step 4: Regenerate SDK with Codama and Solita +echo "[4/4] Patching IDL and Regenerating SDKs..." +cd "$ROOT_DIR" +node scripts/patch_idl.js + +cd "$SDK_DIR" +npm run generate:all + +echo "--- ✅ All Done! ---" +echo "Next: Deploy your program using 'solana program deploy program/target/deploy/lazorkit_program.so -u d'" diff --git a/scripts/patch_idl.js b/scripts/patch_idl.js new file mode 100644 index 0000000..9ac7715 --- /dev/null +++ b/scripts/patch_idl.js @@ -0,0 +1,27 @@ +const fs = require('fs'); +const path = require('path'); + +const idlPath = path.join(__dirname, '..', 'program', 'lazor_kit.json'); +const idl = JSON.parse(fs.readFileSync(idlPath, 'utf-8')); + +console.log('--- 🛠 Patching IDL for Runtime Alignments ---'); + +// 1. Inject program address (missing from Shank IDL) +idl.metadata = idl.metadata || {}; +idl.metadata.address = 'DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2'; + +// 3. Cast [u8; 32] fields to publicKey for Accounts +if (idl.accounts) { + idl.accounts.forEach(acc => { + if (acc.type && acc.type.fields) { + acc.type.fields.forEach(f => { + if (['wallet', 'sessionKey'].includes(f.name)) { + f.type = 'publicKey'; + } + }); + } + }); +} + +fs.writeFileSync(idlPath, JSON.stringify(idl, null, 2)); +console.log('✓ Successfully patched lazor_kit.json'); diff --git a/scripts/sync-program-id.sh b/scripts/sync-program-id.sh new file mode 100755 index 0000000..109d8e8 --- /dev/null +++ b/scripts/sync-program-id.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Check if new Program ID is provided +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +NEW_ID=$1 + +# Detect OLD_ID from assertions/src/lib.rs +OLD_ID=$(grep -oE "declare_id\!\(\"[A-Za-z0-9]+\"\)" assertions/src/lib.rs | sed -E 's/declare_id\!\(\"([A-Za-z0-9]+)\"\)/\1/') + +if [ -z "$OLD_ID" ]; then + echo "❌ Error: Could not detect current Program ID from assertions/src/lib.rs" + exit 1 +fi + +if [ "$OLD_ID" == "$NEW_ID" ]; then + echo "Program ID is already $NEW_ID. Skipping sync." + exit 0 +fi + +echo "Syncing Program ID: $OLD_ID -> $NEW_ID" + +# 1. Update Rust assertions +sed -i '' "s/$OLD_ID/$NEW_ID/g" assertions/src/lib.rs + +# 2. Update SDK generation script +sed -i '' "s/$OLD_ID/$NEW_ID/g" sdk/lazorkit-ts/generate.mjs + +# 3. Update Real RPC tests common configuration +sed -i '' "s/$OLD_ID/$NEW_ID/g" tests-real-rpc/tests/common.ts + +# 4. Update local test script +sed -i '' "s/$OLD_ID/$NEW_ID/g" tests-real-rpc/scripts/test-local.sh + +# 5. Run SDK generation to update the TypeScript client +echo "Regenerating SDK..." +cd sdk/lazorkit-ts +npm run generate +cd ../.. + +echo "✓ Program ID synced across: Rust code, SDK, and Tests." +echo "✓ SDK regenerated with new address." +echo "Pro tip: Now run 'cargo build-sbf' to rebuild the program with the correct ID." diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..36fd96f --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,60 @@ +#!/bin/bash +set -e + +# LazorKit Unified Test Script +# This script builds the program and runs integration tests against a local validator. + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +PROGRAM_DIR="$ROOT_DIR/program" +TARGET_DIR="$ROOT_DIR/target/deploy" +TEST_DIR="$ROOT_DIR/tests-real-rpc" +LEDGER_DIR="$TEST_DIR/.test-ledger" + +# 1. Build and Run Rust Logic Tests +echo "🔨 Building LazorKit and running Rust tests..." +cd "$PROGRAM_DIR" +cargo test-sbf +cd "$ROOT_DIR" + +# 2. Get Program ID (default or from keypair) +PROGRAM_ID=$(solana address -k "$TARGET_DIR/lazorkit_program-keypair.json") +echo "📍 Program ID: $PROGRAM_ID" + +# 3. Cleanup existing validator +cleanup() { + echo "🧹 Cleaning up..." + if [ -f "$LEDGER_DIR/validator.pid" ]; then + PID=$(cat "$LEDGER_DIR/validator.pid") + kill $PID 2>/dev/null || true + fi + pkill -f solana-test-validator 2>/dev/null || true + # rm -rf "$LEDGER_DIR" # Keeps ledger for debugging if needed, or remove if fresh start preferred +} +trap cleanup EXIT + +# 4. Start local validator +echo "🚀 Starting solana-test-validator..." +mkdir -p "$LEDGER_DIR" +solana-test-validator \ + --ledger "$LEDGER_DIR" \ + --bpf-program "$PROGRAM_ID" "$TARGET_DIR/lazorkit_program.so" \ + --reset \ + --quiet & +VALIDATOR_PID=$! +echo $VALIDATOR_PID > "$LEDGER_DIR/validator.pid" + +# Wait for validator +echo "⏳ Waiting for validator to be ready..." +while ! curl -s http://127.0.0.1:8899 > /dev/null; do + sleep 1 +done +echo "✅ Validator is up!" + +# 5. Run tests +echo "🧪 Running TypeScript integration tests..." +export RPC_URL="http://127.0.0.1:8899" +export WS_URL="ws://127.0.0.1:8900" +cd "$TEST_DIR" +npm run test -- --fileParallelism=false --testTimeout=30000 + +echo "🎉 All tests passed!" diff --git a/sdk/codama-client/generate.mjs b/sdk/codama-client/generate.mjs new file mode 100644 index 0000000..b745753 --- /dev/null +++ b/sdk/codama-client/generate.mjs @@ -0,0 +1,44 @@ +/** + * LazorKit SDK Code Generation Script + * + * Converts the Shank IDL to a Codama root node, enriches it with + * account types, error codes, PDA definitions, and enum types, + * then renders a TypeScript client. + * + * Usage: node generate.mjs + */ +import { readFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { rootNodeFromAnchor } from '@codama/nodes-from-anchor'; +import { renderVisitor } from '@codama/renderers-js'; +import { createFromRoot, visit } from 'codama'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// ─── 1. Read Shank IDL ─────────────────────────────────────────── +const idlPath = join(__dirname, '../../program/lazor_kit.json'); +const idl = JSON.parse(readFileSync(idlPath, 'utf-8')); +console.log('✓ Read IDL from', idlPath); + +// ─── 2. Inject program address (missing from Shank IDL) ───────── +idl.metadata = idl.metadata || {}; +idl.metadata.address = 'DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2'; +console.log('✓ Injected program address'); + +// Removed inline patching; it is now handled by root `scripts/patch_idl.js` + +// ─── 6. Convert to Codama root node ────────────────────────────── +const rootNode = rootNodeFromAnchor(idl); +console.log('✓ Converted enriched IDL to Codama root node'); + +// ─── 7. Create Codama instance ─────────────────────────────────── +const codama = createFromRoot(rootNode); +console.log('✓ Created Codama instance'); + +// ─── 8. Render to TypeScript ───────────────────────────────────── +const outputDir = join(__dirname, 'src', 'generated'); +console.log(' Rendering to', outputDir); + +visit(codama.getRoot(), renderVisitor(outputDir)); +console.log('✓ Done! Generated files in src/generated/'); diff --git a/sdk/codama-client/package-lock.json b/sdk/codama-client/package-lock.json new file mode 100644 index 0000000..0668f6c --- /dev/null +++ b/sdk/codama-client/package-lock.json @@ -0,0 +1,3170 @@ +{ + "name": "lazorkit-ts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lazorkit-ts", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@solana/kit": "^6.0.1" + }, + "devDependencies": { + "@codama/nodes-from-anchor": "^1.3.8", + "@codama/renderers-js": "^1.7.0", + "@types/node": "^25.2.3", + "codama": "^1.5.0", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + } + }, + "node_modules/@codama/cli": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@codama/cli/-/cli-1.4.4.tgz", + "integrity": "sha512-0uLecW/RZC2c1wx3j/eiRAYvilvNY+2DoyEYu/hV0OfM1/uIgIyuy5U+wolV+LY4wLFYdApjYdy+5D32lngCHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/nodes": "1.5.0", + "@codama/visitors": "1.5.0", + "@codama/visitors-core": "1.5.0", + "commander": "^14.0.2", + "picocolors": "^1.1.1", + "prompts": "^2.4.2" + }, + "bin": { + "codama": "bin/cli.cjs" + } + }, + "node_modules/@codama/errors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@codama/errors/-/errors-1.5.0.tgz", + "integrity": "sha512-i4cS+S7JaZXhofQHFY3cwzt8rqxUVPNaeJND5VOyKUbtcOi933YXJXk52gDG4mc+CpGqHJijsJjfSpr1lJGxzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/node-types": "1.5.0", + "commander": "^14.0.2", + "picocolors": "^1.1.1" + }, + "bin": { + "errors": "bin/cli.cjs" + } + }, + "node_modules/@codama/node-types": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@codama/node-types/-/node-types-1.5.0.tgz", + "integrity": "sha512-Ebz2vOUukmNaFXWdkni1ZihXkAIUnPYtqIMXYxKXOxjMP+TGz2q0lGtRo7sqw1pc2ksFBIkfBp5pZsl5p6gwXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@codama/nodes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@codama/nodes/-/nodes-1.5.0.tgz", + "integrity": "sha512-yg+xmorWiMNjS3n19CGIt/FZ/ZCuDIu+HEY45bq6gHu1MN3RtJZY+Q3v0ErnBPA60D8mNWkvkKoeSZXfzcAvfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/node-types": "1.5.0" + } + }, + "node_modules/@codama/nodes-from-anchor": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@codama/nodes-from-anchor/-/nodes-from-anchor-1.3.8.tgz", + "integrity": "sha512-+nGg7YcXbuqrKDFh/8KBRtR3PcR7bJr4w4jy9pNogHpM5CsDcTEa+fiPRHcYh3CgbaX86DgTfvkEw2dmgf+qzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "@codama/visitors": "1.5.0", + "@noble/hashes": "^2.0.1", + "@solana/codecs": "^5.1.0" + } + }, + "node_modules/@codama/nodes-from-anchor/node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@codama/nodes-from-anchor/node_modules/@solana/codecs": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-5.5.1.tgz", + "integrity": "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/codecs-data-structures": "5.5.1", + "@solana/codecs-numbers": "5.5.1", + "@solana/codecs-strings": "5.5.1", + "@solana/options": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@codama/nodes-from-anchor/node_modules/@solana/codecs-core": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.5.1.tgz", + "integrity": "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@codama/nodes-from-anchor/node_modules/@solana/codecs-data-structures": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-5.5.1.tgz", + "integrity": "sha512-97bJWGyUY9WvBz3mX1UV3YPWGDTez6btCfD0ip3UVEXJbItVuUiOkzcO5iFDUtQT5riKT6xC+Mzl+0nO76gd0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/codecs-numbers": "5.5.1", + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@codama/nodes-from-anchor/node_modules/@solana/codecs-numbers": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.5.1.tgz", + "integrity": "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@codama/nodes-from-anchor/node_modules/@solana/codecs-strings": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-5.5.1.tgz", + "integrity": "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/codecs-numbers": "5.5.1", + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "fastestsmallesttextencoderdecoder": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@codama/nodes-from-anchor/node_modules/@solana/errors": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.5.1.tgz", + "integrity": "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@codama/nodes-from-anchor/node_modules/@solana/options": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/options/-/options-5.5.1.tgz", + "integrity": "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/codecs-data-structures": "5.5.1", + "@solana/codecs-numbers": "5.5.1", + "@solana/codecs-strings": "5.5.1", + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@codama/nodes-from-anchor/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@codama/renderers-core": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@codama/renderers-core/-/renderers-core-1.3.5.tgz", + "integrity": "sha512-MuZLU+3LZPQb1HuZffwZl+v5JHQDe5LYHGhA1wTMNlwRedYIysSxBjogHNciNIHsKP3JjmqyYmLO5LCEp3hjaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "@codama/visitors-core": "1.5.0" + } + }, + "node_modules/@codama/renderers-js": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@codama/renderers-js/-/renderers-js-1.7.0.tgz", + "integrity": "sha512-WwKkSkNPdUBVWjGmkG+RNXyZ5K/4ji8UZQGzowDNTrqktUrqPsBThOkc7Zpmv+TpCapxrfjj0Txpo+0q5FjKGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "^1.4.4", + "@codama/nodes": "^1.4.4", + "@codama/renderers-core": "^1.3.4", + "@codama/visitors-core": "^1.4.4", + "@solana/codecs-strings": "^6.0.0", + "prettier": "^3.8.1", + "semver": "^7.7.3" + }, + "engines": { + "node": ">=20.18.0" + } + }, + "node_modules/@codama/validators": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@codama/validators/-/validators-1.5.0.tgz", + "integrity": "sha512-p3ufDxnCH1jiuHGzcBv4/d+ctzUcKD2K3gX/W8169tC41o9DggjlEpNy1Z6YAAhVb3wHnmXVGA2qmp32rWSfWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "@codama/visitors-core": "1.5.0" + } + }, + "node_modules/@codama/visitors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@codama/visitors/-/visitors-1.5.0.tgz", + "integrity": "sha512-SwtQaleXxAaFz6uHygxki621q4nPUDQlnwEhsg+QKOjHpKWXjLYdJof+R8gUiTV/n7/IeNnjvxJTTNfUsvETPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "@codama/visitors-core": "1.5.0" + } + }, + "node_modules/@codama/visitors-core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@codama/visitors-core/-/visitors-core-1.5.0.tgz", + "integrity": "sha512-3PIAlBX0a06hIxzyPtQMfQcqWGFBgfbwysSwcXBbvHUYbemwhD6xwlBKJuqTwm9DyFj3faStp5fpvcp03Rjxtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "json-stable-stringify": "^1.3.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@solana/accounts": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/accounts/-/accounts-6.0.1.tgz", + "integrity": "sha512-wdW2KI31jeAIyryL2hLytu+bmIbfKBPkO2Qsu7DO80m2pqOVVOGQ0L0wIqFdNXZN7Eu/FVTY8sh6gqF9bnf5LQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/rpc-spec": "6.0.1", + "@solana/rpc-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/addresses": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/addresses/-/addresses-6.0.1.tgz", + "integrity": "sha512-i/7JuTZF1MInCulP8/+aK9khKcDgjTrqqEl3wRmg6Kk/Dq+rOBrjXggLf3bEtGSWV53iH0NGDQt+psUNFd5Reg==", + "license": "MIT", + "dependencies": { + "@solana/assertions": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/nominal-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/assertions": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/assertions/-/assertions-6.0.1.tgz", + "integrity": "sha512-Fnk0PCxjeNLDrsRQX+DRS3HnN5PRYQedosmtqx0/xK2CIB4lG/4coK/IdoL6i8/yS4EcKq8gNcMfH4fkmaMfLQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-6.0.1.tgz", + "integrity": "sha512-xNL69WA50fCMItk3zXA7UMDHVMDyW9paL32wwxzL++sv7txfgma3UIAxP90tn9GBMwjPTB74hI6ook1mA2DhTQ==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/codecs-data-structures": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/options": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-core": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-6.0.1.tgz", + "integrity": "sha512-OnUQk94qfvfE0nVveZ638aNUL3tyRJoorUFiAG0ICTGUo3c6fkYb8vH23o/5O2qmuSmYND1sn+UCaldNMVkFpg==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-data-structures": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-6.0.1.tgz", + "integrity": "sha512-ImPGi5wtpca0gLaD9dJGj29z6GMU8tCYjqnmTc5Lyh5S80iCz9wNlwT1/VvPM6gqeIOFVx8bM9H1iljQ7MuCMw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-numbers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-6.0.1.tgz", + "integrity": "sha512-ZrI1NjUsf4I+Klue/2rlQbZLcGRom/G2E4VB/8x4IEHGOeFLQhXcxmnib8kdgomQRYOzF1BjVDmCYxvZr+6AWA==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-strings": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-6.0.1.tgz", + "integrity": "sha512-OmMIfMFbbJVIxveBeATKCj9DsmZ8l4vJPnOLHUop0hLWRiYHTQ1qokMqfk/X8PCmUjXmbXnlp63BikGtdKN3/g==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "fastestsmallesttextencoderdecoder": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/errors": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-6.0.1.tgz", + "integrity": "sha512-sMe5GCsXto8F1KDeq9GbZR0+m841SqEYep3NAcYlC0lqF2RG4giaaPQHgrWI5DJR/L7yc8FzUIQfTxnaN7bwOQ==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.3" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/fast-stable-stringify": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/fast-stable-stringify/-/fast-stable-stringify-6.0.1.tgz", + "integrity": "sha512-60F0TaKm+mbIfsj94TaPgO2mbKtXVYyELC1Kf8YoRo9jIQSXVGXdljXR1UzqSxrN6V4Ueyx3RE5jW9fAIzQZ/A==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/functional": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/functional/-/functional-6.0.1.tgz", + "integrity": "sha512-qHPw87tCf4Kq4H9cpH6XV/C1wKJzSj0OQ8t+BqbFxvpX+c7svSRUY/It2gJOAcJd9f9hduQ3ZrqARXOU7aILvw==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/instruction-plans": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/instruction-plans/-/instruction-plans-6.0.1.tgz", + "integrity": "sha512-aEwCfksUxVgcrOGnDJmmIp4phYn+DpOeS0fq7v3uteBu7T7lkwW+EJCu2iT1j1VLxcjDuPf243pNBp5GR13+yw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/instructions": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/promises": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/instructions": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/instructions/-/instructions-6.0.1.tgz", + "integrity": "sha512-qNTc3GrmiesN2x27Ap8qhKKn9vIocz/1Dc/Am7hiYU4TFiKtuj34TARyDa5VVbLGKRY5vZCpNsX2jqVx2V0lSQ==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/keys": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/keys/-/keys-6.0.1.tgz", + "integrity": "sha512-naN3yRzN2VDJUgdcrxwsObr2ik8MV2brOI/MLrOWDUW8nlVfcs4OC7mB/HC1hYd60DT0rsP18P33Gjd8juknYw==", + "license": "MIT", + "dependencies": { + "@solana/assertions": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/nominal-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/kit": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-6.0.1.tgz", + "integrity": "sha512-zCU5URMgkCgL5hZOxjIzhAD7SjqVAJN4sbpyC4MatxbXE/NGoabPc4I2y5STrXsZLokQD0t4KZ1zs9v5M8Ylag==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "6.0.1", + "@solana/addresses": "6.0.1", + "@solana/codecs": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/instruction-plans": "6.0.1", + "@solana/instructions": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/offchain-messages": "6.0.1", + "@solana/plugin-core": "6.0.1", + "@solana/programs": "6.0.1", + "@solana/rpc": "6.0.1", + "@solana/rpc-api": "6.0.1", + "@solana/rpc-parsed-types": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "@solana/rpc-subscriptions": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/signers": "6.0.1", + "@solana/sysvars": "6.0.1", + "@solana/transaction-confirmation": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/nominal-types": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/nominal-types/-/nominal-types-6.0.1.tgz", + "integrity": "sha512-2/1loP3iHgLQIaeksrDPNL2be2zboKbsF2EKDAt7zqbiDCOsPY9Kgdq50WJGGileIXD0v7yincq6UTdOLcaR8Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/offchain-messages": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/offchain-messages/-/offchain-messages-6.0.1.tgz", + "integrity": "sha512-lwpNl+kusH2v5nLgUfwxme66uDonCn8+TqzYqJeENolaAbV0nnF8rV4ZHjfFs1Bc/3UG+TxrI0WYvRI+B5nVBA==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-data-structures": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/nominal-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/options": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/options/-/options-6.0.1.tgz", + "integrity": "sha512-ld13WWyMgicU8FkN6dNOmMJgVaV0uqU8HDQRJCfClsPl0v2TQ1t3aOYHkxpYfX+OvBjja1x2v2wflqJgUHKS+Q==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/codecs-data-structures": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/plugin-core": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/plugin-core/-/plugin-core-6.0.1.tgz", + "integrity": "sha512-mrVb6cf/HurU93z2bgCOoRxWuZWF/fWzIK+v7YMl9t8aKHhGdB4/iElXvPwGoArapZJaAe7dRqHgCJvYRPFvCg==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/programs": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/programs/-/programs-6.0.1.tgz", + "integrity": "sha512-eKsSVuADG/bzTu66iwhJctbIEQQLZVnD/kx98gtPAuNG6Z1WjMXO8tn6EYLn3ndc5yS+oeNSQBV6z3ozL+gTkQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/promises": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/promises/-/promises-6.0.1.tgz", + "integrity": "sha512-6W8yFBtjhwy8Gn7aagXBUjiQejpa+ENgqot2uy3LACQPQMCnd+TwZk9Pggnm5+Q12rm+d9bMvAa4110eoXR0Bw==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc/-/rpc-6.0.1.tgz", + "integrity": "sha512-fuRnm1SNcRLWii6N3WeJL8LSJJDEVEdS+ZDXclUWAPXUccl6wGb99/1tHWeOOwczgk9nmWoTYY9XeOLJt88HSg==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/fast-stable-stringify": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/rpc-api": "6.0.1", + "@solana/rpc-spec": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "@solana/rpc-transformers": "6.0.1", + "@solana/rpc-transport-http": "6.0.1", + "@solana/rpc-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-api": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-api/-/rpc-api-6.0.1.tgz", + "integrity": "sha512-lCXPGHx2eF8wl0kdpuDLWX44vdDaTcPTAD9hCIsHQFLWeahJDarieoOacaAuse6TsRtGaPExBvbW6Da555Lnaw==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/rpc-parsed-types": "6.0.1", + "@solana/rpc-spec": "6.0.1", + "@solana/rpc-transformers": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-parsed-types": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-parsed-types/-/rpc-parsed-types-6.0.1.tgz", + "integrity": "sha512-2CnWhtJuiOgefU3EerwM1bg/OvmLJTMBUuGiSVoVAr7WfGjUXcoRa3mNO0HUDDoRo2gZwM/8BaFzhiRSbamsmQ==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-spec": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec/-/rpc-spec-6.0.1.tgz", + "integrity": "sha512-SfZZUCbpiKNHsIAYq9WKd6PhnpXkH8ASRIda9KMkpFtTVg1thm4sA/A/Jpk8vJDpUVvzYLBVblNHQWqwRiRxAA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/rpc-spec-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-spec-types": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec-types/-/rpc-spec-types-6.0.1.tgz", + "integrity": "sha512-dosqI9gWs5Cz5T9Uanu4FkMkBN7AD6bRVw0YDIkalRcpC50Ak2iP48fJKArw3lh/phjcxEBVQxY3XeKEVGZP7Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions/-/rpc-subscriptions-6.0.1.tgz", + "integrity": "sha512-h2LXD8PiXPZca3vtECmUSEzLjc5m6EswgnJcq+HtJqA0M+xINFRl8mL6yS5D2d1Cf7sl/CwU/7935GJ8uLFeJA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/fast-stable-stringify": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/promises": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "@solana/rpc-subscriptions-api": "6.0.1", + "@solana/rpc-subscriptions-channel-websocket": "6.0.1", + "@solana/rpc-subscriptions-spec": "6.0.1", + "@solana/rpc-transformers": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/subscribable": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions-api": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-6.0.1.tgz", + "integrity": "sha512-yj6niyZ6jqwg4u4oi55gDPzDNwXdgGBuu1zVfUnD6auCavDl4OxziUEtRIQ3NURJZa5kjTqQ48TuR0tD55vfiA==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/rpc-subscriptions-spec": "6.0.1", + "@solana/rpc-transformers": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions-channel-websocket": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-6.0.1.tgz", + "integrity": "sha512-lxjfG+krZF8np69SQyRbmQL8jYNV/G69Ak782GYYfkEdAYztFs9OOQMgZNuciIgUlQAcXWWkNjJ6GhIbisg9NA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/rpc-subscriptions-spec": "6.0.1", + "@solana/subscribable": "6.0.1", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions-spec": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-6.0.1.tgz", + "integrity": "sha512-FhZOXpP71R5y7q0TEvAFNJ+WmxIJUfhQicgae71WQtaiw+vM/dFnT/AL3I9rRBVzF0UQ7wIeqkuVKltdJEdzqQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/promises": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "@solana/subscribable": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-transformers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-transformers/-/rpc-transformers-6.0.1.tgz", + "integrity": "sha512-tkyTh5jwK/IZV+jI4plFttG1l43g47YB/laFtxYvu8OZx5RTCljryPh5RamjxGAhFk3w6xnLZJbc3MBk8VrPsQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/nominal-types": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "@solana/rpc-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-transport-http": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-transport-http/-/rpc-transport-http-6.0.1.tgz", + "integrity": "sha512-l9TOpQq4cSIhReyzLJb7j+03YaUDClDJOzJr7ZQSo1fqt7Ww6C5+dKOVIUUu6tg9AOO8mCA0QVT/rFmZ9ZjhiQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/rpc-spec": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "undici-types": "^7.20.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-types": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/rpc-types/-/rpc-types-6.0.1.tgz", + "integrity": "sha512-40nXhThKNzh0ih2Pd8ACsIKPgVaP/6OqbLfgcZxPjZ10XjhjMy9crwW1ZF0EPhK8uo+bs9gtztl9OVWWgYYrNQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/nominal-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/signers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/signers/-/signers-6.0.1.tgz", + "integrity": "sha512-iby4CGjk4pBNqytpyyPK2IGZ8/BMcrdtubVCuSYze2DJE3RdrPkuhVv2A6A6Cfk/0DPfUkqZQtTNMxCOj6oCbw==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/instructions": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/nominal-types": "6.0.1", + "@solana/offchain-messages": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/subscribable": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/subscribable/-/subscribable-6.0.1.tgz", + "integrity": "sha512-GGXvRVzOAJlbGwwgOHbcxwT8lILkgrlHYO72ChkG8IbJWq7eTi1+Uz3TQTsXtC923dZ2XHLqp+aHl7Kx3L3ENg==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/sysvars": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-6.0.1.tgz", + "integrity": "sha512-fSMasRQUfbzrhZ3t0XdVpwIezHRelPx3ZxkqyUy8Lx/90YzR1kxaJpmNS7c1pBV60scdiJVQ4vXQtetKxIgRVQ==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "6.0.1", + "@solana/codecs": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/rpc-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/transaction-confirmation": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/transaction-confirmation/-/transaction-confirmation-6.0.1.tgz", + "integrity": "sha512-x0sXnS75xwchAtQU0UbQ7wBQoWqgUQkn0G4DKQMEGllWGRsJFvpQzuUqAgh5fNhe2sMt8+4QdQHrI01zUNDDtQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/promises": "6.0.1", + "@solana/rpc": "6.0.1", + "@solana/rpc-subscriptions": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/transaction-messages": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/transaction-messages/-/transaction-messages-6.0.1.tgz", + "integrity": "sha512-lpSyXsFPMCDo5Vf0LLsdj5+WyYxUD+8WEMWuVDYiG/7e8fVjLEsZ6k/UpvyI7ZJnkMhfwEa3DRAubNDH1Biafg==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-data-structures": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/instructions": "6.0.1", + "@solana/nominal-types": "6.0.1", + "@solana/rpc-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/transactions": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@solana/transactions/-/transactions-6.0.1.tgz", + "integrity": "sha512-VLFug8oKCpEyZv/mMMnQIraupXwMUzO4KzA/kGBHbUnCX95K7UFpc07AFc1nXGbo1jBBO4e+O2cnVWg097Yz0A==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-data-structures": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/instructions": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/nominal-types": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/transaction-messages": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/codama": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/codama/-/codama-1.5.0.tgz", + "integrity": "sha512-hhfSzrOiDX3bV7QmJneEBsBk3ln4gIcMJs6P8BlEJ3EFI+P0QZaTT5W61o8Tq0/79hTZeyj0gP65HZ/LYJil+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/cli": "1.4.4", + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "@codama/validators": "1.5.0", + "@codama/visitors": "1.5.0" + }, + "bin": { + "codama": "bin/cli.cjs" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz", + "integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/sdk/codama-client/package.json b/sdk/codama-client/package.json new file mode 100644 index 0000000..86d5f90 --- /dev/null +++ b/sdk/codama-client/package.json @@ -0,0 +1,26 @@ +{ + "name": "@lazorkit/codama-client", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate": "node generate.mjs", + "test": "vitest tests-real-rpc", + "build": "tsc" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "@codama/nodes-from-anchor": "^1.3.8", + "@codama/renderers-js": "^1.7.0", + "@types/node": "^25.2.3", + "codama": "^1.5.0", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + }, + "dependencies": { + "@solana/kit": "^6.0.1" + } +} diff --git a/sdk/codama-client/src/generated/accounts/authorityAccount.ts b/sdk/codama-client/src/generated/accounts/authorityAccount.ts new file mode 100644 index 0000000..47c2950 --- /dev/null +++ b/sdk/codama-client/src/generated/accounts/authorityAccount.ts @@ -0,0 +1,165 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + assertAccountExists, + assertAccountsExist, + combineCodec, + decodeAccount, + fetchEncodedAccount, + fetchEncodedAccounts, + fixDecoderSize, + fixEncoderSize, + getBytesDecoder, + getBytesEncoder, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, + getU8Decoder, + getU8Encoder, + type Account, + type Address, + type EncodedAccount, + type FetchAccountConfig, + type FetchAccountsConfig, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type MaybeAccount, + type MaybeEncodedAccount, + type ReadonlyUint8Array, +} from "@solana/kit"; + +export type AuthorityAccount = { + discriminator: number; + authorityType: number; + role: number; + bump: number; + version: number; + padding: ReadonlyUint8Array; + counter: bigint; + wallet: ReadonlyUint8Array; +}; + +export type AuthorityAccountArgs = { + discriminator: number; + authorityType: number; + role: number; + bump: number; + version: number; + padding: ReadonlyUint8Array; + counter: number | bigint; + wallet: ReadonlyUint8Array; +}; + +/** Gets the encoder for {@link AuthorityAccountArgs} account data. */ +export function getAuthorityAccountEncoder(): FixedSizeEncoder { + return getStructEncoder([ + ["discriminator", getU8Encoder()], + ["authorityType", getU8Encoder()], + ["role", getU8Encoder()], + ["bump", getU8Encoder()], + ["version", getU8Encoder()], + ["padding", fixEncoderSize(getBytesEncoder(), 3)], + ["counter", getU64Encoder()], + ["wallet", fixEncoderSize(getBytesEncoder(), 32)], + ]); +} + +/** Gets the decoder for {@link AuthorityAccount} account data. */ +export function getAuthorityAccountDecoder(): FixedSizeDecoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["authorityType", getU8Decoder()], + ["role", getU8Decoder()], + ["bump", getU8Decoder()], + ["version", getU8Decoder()], + ["padding", fixDecoderSize(getBytesDecoder(), 3)], + ["counter", getU64Decoder()], + ["wallet", fixDecoderSize(getBytesDecoder(), 32)], + ]); +} + +/** Gets the codec for {@link AuthorityAccount} account data. */ +export function getAuthorityAccountCodec(): FixedSizeCodec< + AuthorityAccountArgs, + AuthorityAccount +> { + return combineCodec( + getAuthorityAccountEncoder(), + getAuthorityAccountDecoder(), + ); +} + +export function decodeAuthorityAccount( + encodedAccount: EncodedAccount, +): Account; +export function decodeAuthorityAccount( + encodedAccount: MaybeEncodedAccount, +): MaybeAccount; +export function decodeAuthorityAccount( + encodedAccount: EncodedAccount | MaybeEncodedAccount, +): + | Account + | MaybeAccount { + return decodeAccount( + encodedAccount as MaybeEncodedAccount, + getAuthorityAccountDecoder(), + ); +} + +export async function fetchAuthorityAccount( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig, +): Promise> { + const maybeAccount = await fetchMaybeAuthorityAccount(rpc, address, config); + assertAccountExists(maybeAccount); + return maybeAccount; +} + +export async function fetchMaybeAuthorityAccount< + TAddress extends string = string, +>( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig, +): Promise> { + const maybeAccount = await fetchEncodedAccount(rpc, address, config); + return decodeAuthorityAccount(maybeAccount); +} + +export async function fetchAllAuthorityAccount( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig, +): Promise[]> { + const maybeAccounts = await fetchAllMaybeAuthorityAccount( + rpc, + addresses, + config, + ); + assertAccountsExist(maybeAccounts); + return maybeAccounts; +} + +export async function fetchAllMaybeAuthorityAccount( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig, +): Promise[]> { + const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); + return maybeAccounts.map((maybeAccount) => + decodeAuthorityAccount(maybeAccount), + ); +} + +export function getAuthorityAccountSize(): number { + return 48; +} diff --git a/sdk/codama-client/src/generated/accounts/index.ts b/sdk/codama-client/src/generated/accounts/index.ts new file mode 100644 index 0000000..f96c7fe --- /dev/null +++ b/sdk/codama-client/src/generated/accounts/index.ts @@ -0,0 +1,11 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +export * from "./authorityAccount"; +export * from "./sessionAccount"; +export * from "./walletAccount"; diff --git a/sdk/codama-client/src/generated/accounts/sessionAccount.ts b/sdk/codama-client/src/generated/accounts/sessionAccount.ts new file mode 100644 index 0000000..4257cf2 --- /dev/null +++ b/sdk/codama-client/src/generated/accounts/sessionAccount.ts @@ -0,0 +1,156 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + assertAccountExists, + assertAccountsExist, + combineCodec, + decodeAccount, + fetchEncodedAccount, + fetchEncodedAccounts, + fixDecoderSize, + fixEncoderSize, + getBytesDecoder, + getBytesEncoder, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, + getU8Decoder, + getU8Encoder, + type Account, + type Address, + type EncodedAccount, + type FetchAccountConfig, + type FetchAccountsConfig, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type MaybeAccount, + type MaybeEncodedAccount, + type ReadonlyUint8Array, +} from "@solana/kit"; + +export type SessionAccount = { + discriminator: number; + bump: number; + version: number; + padding: ReadonlyUint8Array; + wallet: ReadonlyUint8Array; + sessionKey: ReadonlyUint8Array; + expiresAt: bigint; +}; + +export type SessionAccountArgs = { + discriminator: number; + bump: number; + version: number; + padding: ReadonlyUint8Array; + wallet: ReadonlyUint8Array; + sessionKey: ReadonlyUint8Array; + expiresAt: number | bigint; +}; + +/** Gets the encoder for {@link SessionAccountArgs} account data. */ +export function getSessionAccountEncoder(): FixedSizeEncoder { + return getStructEncoder([ + ["discriminator", getU8Encoder()], + ["bump", getU8Encoder()], + ["version", getU8Encoder()], + ["padding", fixEncoderSize(getBytesEncoder(), 5)], + ["wallet", fixEncoderSize(getBytesEncoder(), 32)], + ["sessionKey", fixEncoderSize(getBytesEncoder(), 32)], + ["expiresAt", getU64Encoder()], + ]); +} + +/** Gets the decoder for {@link SessionAccount} account data. */ +export function getSessionAccountDecoder(): FixedSizeDecoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["bump", getU8Decoder()], + ["version", getU8Decoder()], + ["padding", fixDecoderSize(getBytesDecoder(), 5)], + ["wallet", fixDecoderSize(getBytesDecoder(), 32)], + ["sessionKey", fixDecoderSize(getBytesDecoder(), 32)], + ["expiresAt", getU64Decoder()], + ]); +} + +/** Gets the codec for {@link SessionAccount} account data. */ +export function getSessionAccountCodec(): FixedSizeCodec< + SessionAccountArgs, + SessionAccount +> { + return combineCodec(getSessionAccountEncoder(), getSessionAccountDecoder()); +} + +export function decodeSessionAccount( + encodedAccount: EncodedAccount, +): Account; +export function decodeSessionAccount( + encodedAccount: MaybeEncodedAccount, +): MaybeAccount; +export function decodeSessionAccount( + encodedAccount: EncodedAccount | MaybeEncodedAccount, +): Account | MaybeAccount { + return decodeAccount( + encodedAccount as MaybeEncodedAccount, + getSessionAccountDecoder(), + ); +} + +export async function fetchSessionAccount( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig, +): Promise> { + const maybeAccount = await fetchMaybeSessionAccount(rpc, address, config); + assertAccountExists(maybeAccount); + return maybeAccount; +} + +export async function fetchMaybeSessionAccount< + TAddress extends string = string, +>( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig, +): Promise> { + const maybeAccount = await fetchEncodedAccount(rpc, address, config); + return decodeSessionAccount(maybeAccount); +} + +export async function fetchAllSessionAccount( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig, +): Promise[]> { + const maybeAccounts = await fetchAllMaybeSessionAccount( + rpc, + addresses, + config, + ); + assertAccountsExist(maybeAccounts); + return maybeAccounts; +} + +export async function fetchAllMaybeSessionAccount( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig, +): Promise[]> { + const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); + return maybeAccounts.map((maybeAccount) => + decodeSessionAccount(maybeAccount), + ); +} + +export function getSessionAccountSize(): number { + return 80; +} diff --git a/sdk/codama-client/src/generated/accounts/walletAccount.ts b/sdk/codama-client/src/generated/accounts/walletAccount.ts new file mode 100644 index 0000000..f62b58b --- /dev/null +++ b/sdk/codama-client/src/generated/accounts/walletAccount.ts @@ -0,0 +1,133 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + assertAccountExists, + assertAccountsExist, + combineCodec, + decodeAccount, + fetchEncodedAccount, + fetchEncodedAccounts, + fixDecoderSize, + fixEncoderSize, + getBytesDecoder, + getBytesEncoder, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + type Account, + type Address, + type EncodedAccount, + type FetchAccountConfig, + type FetchAccountsConfig, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type MaybeAccount, + type MaybeEncodedAccount, + type ReadonlyUint8Array, +} from "@solana/kit"; + +export type WalletAccount = { + discriminator: number; + bump: number; + version: number; + padding: ReadonlyUint8Array; +}; + +export type WalletAccountArgs = WalletAccount; + +/** Gets the encoder for {@link WalletAccountArgs} account data. */ +export function getWalletAccountEncoder(): FixedSizeEncoder { + return getStructEncoder([ + ["discriminator", getU8Encoder()], + ["bump", getU8Encoder()], + ["version", getU8Encoder()], + ["padding", fixEncoderSize(getBytesEncoder(), 5)], + ]); +} + +/** Gets the decoder for {@link WalletAccount} account data. */ +export function getWalletAccountDecoder(): FixedSizeDecoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["bump", getU8Decoder()], + ["version", getU8Decoder()], + ["padding", fixDecoderSize(getBytesDecoder(), 5)], + ]); +} + +/** Gets the codec for {@link WalletAccount} account data. */ +export function getWalletAccountCodec(): FixedSizeCodec< + WalletAccountArgs, + WalletAccount +> { + return combineCodec(getWalletAccountEncoder(), getWalletAccountDecoder()); +} + +export function decodeWalletAccount( + encodedAccount: EncodedAccount, +): Account; +export function decodeWalletAccount( + encodedAccount: MaybeEncodedAccount, +): MaybeAccount; +export function decodeWalletAccount( + encodedAccount: EncodedAccount | MaybeEncodedAccount, +): Account | MaybeAccount { + return decodeAccount( + encodedAccount as MaybeEncodedAccount, + getWalletAccountDecoder(), + ); +} + +export async function fetchWalletAccount( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig, +): Promise> { + const maybeAccount = await fetchMaybeWalletAccount(rpc, address, config); + assertAccountExists(maybeAccount); + return maybeAccount; +} + +export async function fetchMaybeWalletAccount( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig, +): Promise> { + const maybeAccount = await fetchEncodedAccount(rpc, address, config); + return decodeWalletAccount(maybeAccount); +} + +export async function fetchAllWalletAccount( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig, +): Promise[]> { + const maybeAccounts = await fetchAllMaybeWalletAccount( + rpc, + addresses, + config, + ); + assertAccountsExist(maybeAccounts); + return maybeAccounts; +} + +export async function fetchAllMaybeWalletAccount( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig, +): Promise[]> { + const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); + return maybeAccounts.map((maybeAccount) => decodeWalletAccount(maybeAccount)); +} + +export function getWalletAccountSize(): number { + return 8; +} diff --git a/sdk/codama-client/src/generated/errors/index.ts b/sdk/codama-client/src/generated/errors/index.ts new file mode 100644 index 0000000..f42c168 --- /dev/null +++ b/sdk/codama-client/src/generated/errors/index.ts @@ -0,0 +1,9 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +export * from "./lazorkitProgram"; diff --git a/sdk/codama-client/src/generated/errors/lazorkitProgram.ts b/sdk/codama-client/src/generated/errors/lazorkitProgram.ts new file mode 100644 index 0000000..53c2c21 --- /dev/null +++ b/sdk/codama-client/src/generated/errors/lazorkitProgram.ts @@ -0,0 +1,108 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + isProgramError, + type Address, + type SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM, + type SolanaError, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; + +/** InvalidAuthorityPayload: Invalid authority payload */ +export const LAZORKIT_PROGRAM_ERROR__INVALID_AUTHORITY_PAYLOAD = 0xbb9; // 3001 +/** PermissionDenied: Permission denied */ +export const LAZORKIT_PROGRAM_ERROR__PERMISSION_DENIED = 0xbba; // 3002 +/** InvalidInstruction: Invalid instruction */ +export const LAZORKIT_PROGRAM_ERROR__INVALID_INSTRUCTION = 0xbbb; // 3003 +/** InvalidPubkey: Invalid public key */ +export const LAZORKIT_PROGRAM_ERROR__INVALID_PUBKEY = 0xbbc; // 3004 +/** InvalidMessageHash: Invalid message hash */ +export const LAZORKIT_PROGRAM_ERROR__INVALID_MESSAGE_HASH = 0xbbd; // 3005 +/** SignatureReused: Signature has already been used */ +export const LAZORKIT_PROGRAM_ERROR__SIGNATURE_REUSED = 0xbbe; // 3006 +/** InvalidSignatureAge: Invalid signature age */ +export const LAZORKIT_PROGRAM_ERROR__INVALID_SIGNATURE_AGE = 0xbbf; // 3007 +/** InvalidSessionDuration: Invalid session duration */ +export const LAZORKIT_PROGRAM_ERROR__INVALID_SESSION_DURATION = 0xbc0; // 3008 +/** SessionExpired: Session expired */ +export const LAZORKIT_PROGRAM_ERROR__SESSION_EXPIRED = 0xbc1; // 3009 +/** AuthorityDoesNotSupportSession: Authority type does not support sessions */ +export const LAZORKIT_PROGRAM_ERROR__AUTHORITY_DOES_NOT_SUPPORT_SESSION = 0xbc2; // 3010 +/** InvalidAuthenticationKind: Invalid authentication kind */ +export const LAZORKIT_PROGRAM_ERROR__INVALID_AUTHENTICATION_KIND = 0xbc3; // 3011 +/** InvalidMessage: Invalid message */ +export const LAZORKIT_PROGRAM_ERROR__INVALID_MESSAGE = 0xbc4; // 3012 +/** SelfReentrancyNotAllowed: Self-reentrancy is not allowed */ +export const LAZORKIT_PROGRAM_ERROR__SELF_REENTRANCY_NOT_ALLOWED = 0xbc5; // 3013 + +export type LazorkitProgramError = + | typeof LAZORKIT_PROGRAM_ERROR__AUTHORITY_DOES_NOT_SUPPORT_SESSION + | typeof LAZORKIT_PROGRAM_ERROR__INVALID_AUTHENTICATION_KIND + | typeof LAZORKIT_PROGRAM_ERROR__INVALID_AUTHORITY_PAYLOAD + | typeof LAZORKIT_PROGRAM_ERROR__INVALID_INSTRUCTION + | typeof LAZORKIT_PROGRAM_ERROR__INVALID_MESSAGE + | typeof LAZORKIT_PROGRAM_ERROR__INVALID_MESSAGE_HASH + | typeof LAZORKIT_PROGRAM_ERROR__INVALID_PUBKEY + | typeof LAZORKIT_PROGRAM_ERROR__INVALID_SESSION_DURATION + | typeof LAZORKIT_PROGRAM_ERROR__INVALID_SIGNATURE_AGE + | typeof LAZORKIT_PROGRAM_ERROR__PERMISSION_DENIED + | typeof LAZORKIT_PROGRAM_ERROR__SELF_REENTRANCY_NOT_ALLOWED + | typeof LAZORKIT_PROGRAM_ERROR__SESSION_EXPIRED + | typeof LAZORKIT_PROGRAM_ERROR__SIGNATURE_REUSED; + +let lazorkitProgramErrorMessages: + | Record + | undefined; +if (process.env.NODE_ENV !== "production") { + lazorkitProgramErrorMessages = { + [LAZORKIT_PROGRAM_ERROR__AUTHORITY_DOES_NOT_SUPPORT_SESSION]: `Authority type does not support sessions`, + [LAZORKIT_PROGRAM_ERROR__INVALID_AUTHENTICATION_KIND]: `Invalid authentication kind`, + [LAZORKIT_PROGRAM_ERROR__INVALID_AUTHORITY_PAYLOAD]: `Invalid authority payload`, + [LAZORKIT_PROGRAM_ERROR__INVALID_INSTRUCTION]: `Invalid instruction`, + [LAZORKIT_PROGRAM_ERROR__INVALID_MESSAGE]: `Invalid message`, + [LAZORKIT_PROGRAM_ERROR__INVALID_MESSAGE_HASH]: `Invalid message hash`, + [LAZORKIT_PROGRAM_ERROR__INVALID_PUBKEY]: `Invalid public key`, + [LAZORKIT_PROGRAM_ERROR__INVALID_SESSION_DURATION]: `Invalid session duration`, + [LAZORKIT_PROGRAM_ERROR__INVALID_SIGNATURE_AGE]: `Invalid signature age`, + [LAZORKIT_PROGRAM_ERROR__PERMISSION_DENIED]: `Permission denied`, + [LAZORKIT_PROGRAM_ERROR__SELF_REENTRANCY_NOT_ALLOWED]: `Self-reentrancy is not allowed`, + [LAZORKIT_PROGRAM_ERROR__SESSION_EXPIRED]: `Session expired`, + [LAZORKIT_PROGRAM_ERROR__SIGNATURE_REUSED]: `Signature has already been used`, + }; +} + +export function getLazorkitProgramErrorMessage( + code: LazorkitProgramError, +): string { + if (process.env.NODE_ENV !== "production") { + return ( + lazorkitProgramErrorMessages as Record + )[code]; + } + + return "Error message not available in production bundles."; +} + +export function isLazorkitProgramError< + TProgramErrorCode extends LazorkitProgramError, +>( + error: unknown, + transactionMessage: { + instructions: Record; + }, + code?: TProgramErrorCode, +): error is SolanaError & + Readonly<{ context: Readonly<{ code: TProgramErrorCode }> }> { + return isProgramError( + error, + transactionMessage, + LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + code, + ); +} diff --git a/sdk/codama-client/src/generated/index.ts b/sdk/codama-client/src/generated/index.ts new file mode 100644 index 0000000..3113fee --- /dev/null +++ b/sdk/codama-client/src/generated/index.ts @@ -0,0 +1,13 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +export * from "./accounts"; +export * from "./errors"; +export * from "./instructions"; +export * from "./programs"; +export * from "./types"; diff --git a/sdk/codama-client/src/generated/instructions/addAuthority.ts b/sdk/codama-client/src/generated/instructions/addAuthority.ts new file mode 100644 index 0000000..331cac4 --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/addAuthority.ts @@ -0,0 +1,333 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + addDecoderSizePrefix, + addEncoderSizePrefix, + combineCodec, + fixDecoderSize, + fixEncoderSize, + getBytesDecoder, + getBytesEncoder, + getStructDecoder, + getStructEncoder, + getU32Decoder, + getU32Encoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type Codec, + type Decoder, + type Encoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const ADD_AUTHORITY_DISCRIMINATOR = 1; + +export function getAddAuthorityDiscriminatorBytes() { + return getU8Encoder().encode(ADD_AUTHORITY_DISCRIMINATOR); +} + +export type AddAuthorityInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountPayer extends string | AccountMeta = string, + TAccountWallet extends string | AccountMeta = string, + TAccountAdminAuthority extends string | AccountMeta = string, + TAccountNewAuthority extends string | AccountMeta = string, + TAccountSystemProgram extends string | AccountMeta = + "11111111111111111111111111111111", + TAccountConfig extends string | AccountMeta = string, + TAccountTreasuryShard extends string | AccountMeta = string, + TAccountAuthorizerSigner extends string | AccountMeta = string, + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountPayer extends string + ? WritableSignerAccount & + AccountSignerMeta + : TAccountPayer, + TAccountWallet extends string + ? ReadonlyAccount + : TAccountWallet, + TAccountAdminAuthority extends string + ? WritableAccount + : TAccountAdminAuthority, + TAccountNewAuthority extends string + ? WritableAccount + : TAccountNewAuthority, + TAccountSystemProgram extends string + ? ReadonlyAccount + : TAccountSystemProgram, + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountTreasuryShard extends string + ? WritableAccount + : TAccountTreasuryShard, + TAccountAuthorizerSigner extends string + ? ReadonlySignerAccount & + AccountSignerMeta + : TAccountAuthorizerSigner, + ...TRemainingAccounts, + ] + >; + +export type AddAuthorityInstructionData = { + discriminator: number; + newType: number; + newRole: number; + padding: ReadonlyUint8Array; + payload: ReadonlyUint8Array; +}; + +export type AddAuthorityInstructionDataArgs = { + newType: number; + newRole: number; + padding: ReadonlyUint8Array; + payload: ReadonlyUint8Array; +}; + +export function getAddAuthorityInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ["discriminator", getU8Encoder()], + ["newType", getU8Encoder()], + ["newRole", getU8Encoder()], + ["padding", fixEncoderSize(getBytesEncoder(), 6)], + ["payload", addEncoderSizePrefix(getBytesEncoder(), getU32Encoder())], + ]), + (value) => ({ ...value, discriminator: ADD_AUTHORITY_DISCRIMINATOR }), + ); +} + +export function getAddAuthorityInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["newType", getU8Decoder()], + ["newRole", getU8Decoder()], + ["padding", fixDecoderSize(getBytesDecoder(), 6)], + ["payload", addDecoderSizePrefix(getBytesDecoder(), getU32Decoder())], + ]); +} + +export function getAddAuthorityInstructionDataCodec(): Codec< + AddAuthorityInstructionDataArgs, + AddAuthorityInstructionData +> { + return combineCodec( + getAddAuthorityInstructionDataEncoder(), + getAddAuthorityInstructionDataDecoder(), + ); +} + +export type AddAuthorityInput< + TAccountPayer extends string = string, + TAccountWallet extends string = string, + TAccountAdminAuthority extends string = string, + TAccountNewAuthority extends string = string, + TAccountSystemProgram extends string = string, + TAccountConfig extends string = string, + TAccountTreasuryShard extends string = string, + TAccountAuthorizerSigner extends string = string, +> = { + /** Transaction payer */ + payer: TransactionSigner; + /** Wallet PDA */ + wallet: Address; + /** Admin authority PDA authorizing this action */ + adminAuthority: Address; + /** New authority PDA to be created */ + newAuthority: Address; + /** System Program */ + systemProgram?: Address; + /** Config PDA */ + config: Address; + /** Treasury Shard PDA */ + treasuryShard: Address; + /** Optional signer for Ed25519 authentication */ + authorizerSigner?: TransactionSigner; + newType: AddAuthorityInstructionDataArgs["newType"]; + newRole: AddAuthorityInstructionDataArgs["newRole"]; + padding: AddAuthorityInstructionDataArgs["padding"]; + payload: AddAuthorityInstructionDataArgs["payload"]; +}; + +export function getAddAuthorityInstruction< + TAccountPayer extends string, + TAccountWallet extends string, + TAccountAdminAuthority extends string, + TAccountNewAuthority extends string, + TAccountSystemProgram extends string, + TAccountConfig extends string, + TAccountTreasuryShard extends string, + TAccountAuthorizerSigner extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: AddAuthorityInput< + TAccountPayer, + TAccountWallet, + TAccountAdminAuthority, + TAccountNewAuthority, + TAccountSystemProgram, + TAccountConfig, + TAccountTreasuryShard, + TAccountAuthorizerSigner + >, + config?: { programAddress?: TProgramAddress }, +): AddAuthorityInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountAdminAuthority, + TAccountNewAuthority, + TAccountSystemProgram, + TAccountConfig, + TAccountTreasuryShard, + TAccountAuthorizerSigner +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + payer: { value: input.payer ?? null, isWritable: true }, + wallet: { value: input.wallet ?? null, isWritable: false }, + adminAuthority: { value: input.adminAuthority ?? null, isWritable: true }, + newAuthority: { value: input.newAuthority ?? null, isWritable: true }, + systemProgram: { value: input.systemProgram ?? null, isWritable: false }, + config: { value: input.config ?? null, isWritable: false }, + treasuryShard: { value: input.treasuryShard ?? null, isWritable: true }, + authorizerSigner: { + value: input.authorizerSigner ?? null, + isWritable: false, + }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + // Resolve default values. + if (!accounts.systemProgram.value) { + accounts.systemProgram.value = + "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.payer), + getAccountMeta(accounts.wallet), + getAccountMeta(accounts.adminAuthority), + getAccountMeta(accounts.newAuthority), + getAccountMeta(accounts.systemProgram), + getAccountMeta(accounts.config), + getAccountMeta(accounts.treasuryShard), + getAccountMeta(accounts.authorizerSigner), + ], + data: getAddAuthorityInstructionDataEncoder().encode( + args as AddAuthorityInstructionDataArgs, + ), + programAddress, + } as AddAuthorityInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountAdminAuthority, + TAccountNewAuthority, + TAccountSystemProgram, + TAccountConfig, + TAccountTreasuryShard, + TAccountAuthorizerSigner + >); +} + +export type ParsedAddAuthorityInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Transaction payer */ + payer: TAccountMetas[0]; + /** Wallet PDA */ + wallet: TAccountMetas[1]; + /** Admin authority PDA authorizing this action */ + adminAuthority: TAccountMetas[2]; + /** New authority PDA to be created */ + newAuthority: TAccountMetas[3]; + /** System Program */ + systemProgram: TAccountMetas[4]; + /** Config PDA */ + config: TAccountMetas[5]; + /** Treasury Shard PDA */ + treasuryShard: TAccountMetas[6]; + /** Optional signer for Ed25519 authentication */ + authorizerSigner?: TAccountMetas[7] | undefined; + }; + data: AddAuthorityInstructionData; +}; + +export function parseAddAuthorityInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedAddAuthorityInstruction { + if (instruction.accounts.length < 8) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + const getNextOptionalAccount = () => { + const accountMeta = getNextAccount(); + return accountMeta.address === LAZORKIT_PROGRAM_PROGRAM_ADDRESS + ? undefined + : accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + payer: getNextAccount(), + wallet: getNextAccount(), + adminAuthority: getNextAccount(), + newAuthority: getNextAccount(), + systemProgram: getNextAccount(), + config: getNextAccount(), + treasuryShard: getNextAccount(), + authorizerSigner: getNextOptionalAccount(), + }, + data: getAddAuthorityInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/closeSession.ts b/sdk/codama-client/src/generated/instructions/closeSession.ts new file mode 100644 index 0000000..96c0a26 --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/closeSession.ts @@ -0,0 +1,273 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const CLOSE_SESSION_DISCRIMINATOR = 8; + +export function getCloseSessionDiscriminatorBytes() { + return getU8Encoder().encode(CLOSE_SESSION_DISCRIMINATOR); +} + +export type CloseSessionInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountPayer extends string | AccountMeta = string, + TAccountWallet extends string | AccountMeta = string, + TAccountSession extends string | AccountMeta = string, + TAccountConfig extends string | AccountMeta = string, + TAccountAuthorizer extends string | AccountMeta = string, + TAccountAuthorizerSigner extends string | AccountMeta = string, + TAccountSysvarInstructions extends string | AccountMeta = string, + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountPayer extends string + ? WritableSignerAccount & + AccountSignerMeta + : TAccountPayer, + TAccountWallet extends string + ? ReadonlyAccount + : TAccountWallet, + TAccountSession extends string + ? WritableAccount + : TAccountSession, + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountAuthorizer extends string + ? ReadonlyAccount + : TAccountAuthorizer, + TAccountAuthorizerSigner extends string + ? ReadonlySignerAccount & + AccountSignerMeta + : TAccountAuthorizerSigner, + TAccountSysvarInstructions extends string + ? ReadonlyAccount + : TAccountSysvarInstructions, + ...TRemainingAccounts, + ] + >; + +export type CloseSessionInstructionData = { discriminator: number }; + +export type CloseSessionInstructionDataArgs = {}; + +export function getCloseSessionInstructionDataEncoder(): FixedSizeEncoder { + return transformEncoder( + getStructEncoder([["discriminator", getU8Encoder()]]), + (value) => ({ ...value, discriminator: CLOSE_SESSION_DISCRIMINATOR }), + ); +} + +export function getCloseSessionInstructionDataDecoder(): FixedSizeDecoder { + return getStructDecoder([["discriminator", getU8Decoder()]]); +} + +export function getCloseSessionInstructionDataCodec(): FixedSizeCodec< + CloseSessionInstructionDataArgs, + CloseSessionInstructionData +> { + return combineCodec( + getCloseSessionInstructionDataEncoder(), + getCloseSessionInstructionDataDecoder(), + ); +} + +export type CloseSessionInput< + TAccountPayer extends string = string, + TAccountWallet extends string = string, + TAccountSession extends string = string, + TAccountConfig extends string = string, + TAccountAuthorizer extends string = string, + TAccountAuthorizerSigner extends string = string, + TAccountSysvarInstructions extends string = string, +> = { + /** Receives rent refund */ + payer: TransactionSigner; + /** Session's parent wallet */ + wallet: Address; + /** Target session */ + session: Address; + /** Config PDA for contract admin check */ + config: Address; + /** Wallet authority PDA */ + authorizer?: Address; + /** Ed25519 signer */ + authorizerSigner?: TransactionSigner; + /** Secp256r1 sysvar */ + sysvarInstructions?: Address; +}; + +export function getCloseSessionInstruction< + TAccountPayer extends string, + TAccountWallet extends string, + TAccountSession extends string, + TAccountConfig extends string, + TAccountAuthorizer extends string, + TAccountAuthorizerSigner extends string, + TAccountSysvarInstructions extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: CloseSessionInput< + TAccountPayer, + TAccountWallet, + TAccountSession, + TAccountConfig, + TAccountAuthorizer, + TAccountAuthorizerSigner, + TAccountSysvarInstructions + >, + config?: { programAddress?: TProgramAddress }, +): CloseSessionInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountSession, + TAccountConfig, + TAccountAuthorizer, + TAccountAuthorizerSigner, + TAccountSysvarInstructions +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + payer: { value: input.payer ?? null, isWritable: true }, + wallet: { value: input.wallet ?? null, isWritable: false }, + session: { value: input.session ?? null, isWritable: true }, + config: { value: input.config ?? null, isWritable: false }, + authorizer: { value: input.authorizer ?? null, isWritable: false }, + authorizerSigner: { + value: input.authorizerSigner ?? null, + isWritable: false, + }, + sysvarInstructions: { + value: input.sysvarInstructions ?? null, + isWritable: false, + }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.payer), + getAccountMeta(accounts.wallet), + getAccountMeta(accounts.session), + getAccountMeta(accounts.config), + getAccountMeta(accounts.authorizer), + getAccountMeta(accounts.authorizerSigner), + getAccountMeta(accounts.sysvarInstructions), + ], + data: getCloseSessionInstructionDataEncoder().encode({}), + programAddress, + } as CloseSessionInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountSession, + TAccountConfig, + TAccountAuthorizer, + TAccountAuthorizerSigner, + TAccountSysvarInstructions + >); +} + +export type ParsedCloseSessionInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Receives rent refund */ + payer: TAccountMetas[0]; + /** Session's parent wallet */ + wallet: TAccountMetas[1]; + /** Target session */ + session: TAccountMetas[2]; + /** Config PDA for contract admin check */ + config: TAccountMetas[3]; + /** Wallet authority PDA */ + authorizer?: TAccountMetas[4] | undefined; + /** Ed25519 signer */ + authorizerSigner?: TAccountMetas[5] | undefined; + /** Secp256r1 sysvar */ + sysvarInstructions?: TAccountMetas[6] | undefined; + }; + data: CloseSessionInstructionData; +}; + +export function parseCloseSessionInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedCloseSessionInstruction { + if (instruction.accounts.length < 7) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + const getNextOptionalAccount = () => { + const accountMeta = getNextAccount(); + return accountMeta.address === LAZORKIT_PROGRAM_PROGRAM_ADDRESS + ? undefined + : accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + payer: getNextAccount(), + wallet: getNextAccount(), + session: getNextAccount(), + config: getNextAccount(), + authorizer: getNextOptionalAccount(), + authorizerSigner: getNextOptionalAccount(), + sysvarInstructions: getNextOptionalAccount(), + }, + data: getCloseSessionInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/closeWallet.ts b/sdk/codama-client/src/generated/instructions/closeWallet.ts new file mode 100644 index 0000000..3f994a8 --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/closeWallet.ts @@ -0,0 +1,270 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const CLOSE_WALLET_DISCRIMINATOR = 9; + +export function getCloseWalletDiscriminatorBytes() { + return getU8Encoder().encode(CLOSE_WALLET_DISCRIMINATOR); +} + +export type CloseWalletInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountPayer extends string | AccountMeta = string, + TAccountWallet extends string | AccountMeta = string, + TAccountVault extends string | AccountMeta = string, + TAccountOwnerAuthority extends string | AccountMeta = string, + TAccountDestination extends string | AccountMeta = string, + TAccountOwnerSigner extends string | AccountMeta = string, + TAccountSysvarInstructions extends string | AccountMeta = string, + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountPayer extends string + ? WritableSignerAccount & + AccountSignerMeta + : TAccountPayer, + TAccountWallet extends string + ? WritableAccount + : TAccountWallet, + TAccountVault extends string + ? WritableAccount + : TAccountVault, + TAccountOwnerAuthority extends string + ? ReadonlyAccount + : TAccountOwnerAuthority, + TAccountDestination extends string + ? WritableAccount + : TAccountDestination, + TAccountOwnerSigner extends string + ? ReadonlySignerAccount & + AccountSignerMeta + : TAccountOwnerSigner, + TAccountSysvarInstructions extends string + ? ReadonlyAccount + : TAccountSysvarInstructions, + ...TRemainingAccounts, + ] + >; + +export type CloseWalletInstructionData = { discriminator: number }; + +export type CloseWalletInstructionDataArgs = {}; + +export function getCloseWalletInstructionDataEncoder(): FixedSizeEncoder { + return transformEncoder( + getStructEncoder([["discriminator", getU8Encoder()]]), + (value) => ({ ...value, discriminator: CLOSE_WALLET_DISCRIMINATOR }), + ); +} + +export function getCloseWalletInstructionDataDecoder(): FixedSizeDecoder { + return getStructDecoder([["discriminator", getU8Decoder()]]); +} + +export function getCloseWalletInstructionDataCodec(): FixedSizeCodec< + CloseWalletInstructionDataArgs, + CloseWalletInstructionData +> { + return combineCodec( + getCloseWalletInstructionDataEncoder(), + getCloseWalletInstructionDataDecoder(), + ); +} + +export type CloseWalletInput< + TAccountPayer extends string = string, + TAccountWallet extends string = string, + TAccountVault extends string = string, + TAccountOwnerAuthority extends string = string, + TAccountDestination extends string = string, + TAccountOwnerSigner extends string = string, + TAccountSysvarInstructions extends string = string, +> = { + /** Pays tx fee */ + payer: TransactionSigner; + /** Wallet PDA to close */ + wallet: Address; + /** Vault PDA to drain */ + vault: Address; + /** Owner Authority PDA */ + ownerAuthority: Address; + /** Receives all drained SOL */ + destination: Address; + /** Ed25519 signer */ + ownerSigner?: TransactionSigner; + /** Secp256r1 sysvar */ + sysvarInstructions?: Address; +}; + +export function getCloseWalletInstruction< + TAccountPayer extends string, + TAccountWallet extends string, + TAccountVault extends string, + TAccountOwnerAuthority extends string, + TAccountDestination extends string, + TAccountOwnerSigner extends string, + TAccountSysvarInstructions extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: CloseWalletInput< + TAccountPayer, + TAccountWallet, + TAccountVault, + TAccountOwnerAuthority, + TAccountDestination, + TAccountOwnerSigner, + TAccountSysvarInstructions + >, + config?: { programAddress?: TProgramAddress }, +): CloseWalletInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountVault, + TAccountOwnerAuthority, + TAccountDestination, + TAccountOwnerSigner, + TAccountSysvarInstructions +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + payer: { value: input.payer ?? null, isWritable: true }, + wallet: { value: input.wallet ?? null, isWritable: true }, + vault: { value: input.vault ?? null, isWritable: true }, + ownerAuthority: { value: input.ownerAuthority ?? null, isWritable: false }, + destination: { value: input.destination ?? null, isWritable: true }, + ownerSigner: { value: input.ownerSigner ?? null, isWritable: false }, + sysvarInstructions: { + value: input.sysvarInstructions ?? null, + isWritable: false, + }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.payer), + getAccountMeta(accounts.wallet), + getAccountMeta(accounts.vault), + getAccountMeta(accounts.ownerAuthority), + getAccountMeta(accounts.destination), + getAccountMeta(accounts.ownerSigner), + getAccountMeta(accounts.sysvarInstructions), + ], + data: getCloseWalletInstructionDataEncoder().encode({}), + programAddress, + } as CloseWalletInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountVault, + TAccountOwnerAuthority, + TAccountDestination, + TAccountOwnerSigner, + TAccountSysvarInstructions + >); +} + +export type ParsedCloseWalletInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Pays tx fee */ + payer: TAccountMetas[0]; + /** Wallet PDA to close */ + wallet: TAccountMetas[1]; + /** Vault PDA to drain */ + vault: TAccountMetas[2]; + /** Owner Authority PDA */ + ownerAuthority: TAccountMetas[3]; + /** Receives all drained SOL */ + destination: TAccountMetas[4]; + /** Ed25519 signer */ + ownerSigner?: TAccountMetas[5] | undefined; + /** Secp256r1 sysvar */ + sysvarInstructions?: TAccountMetas[6] | undefined; + }; + data: CloseWalletInstructionData; +}; + +export function parseCloseWalletInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedCloseWalletInstruction { + if (instruction.accounts.length < 7) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + const getNextOptionalAccount = () => { + const accountMeta = getNextAccount(); + return accountMeta.address === LAZORKIT_PROGRAM_PROGRAM_ADDRESS + ? undefined + : accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + payer: getNextAccount(), + wallet: getNextAccount(), + vault: getNextAccount(), + ownerAuthority: getNextAccount(), + destination: getNextAccount(), + ownerSigner: getNextOptionalAccount(), + sysvarInstructions: getNextOptionalAccount(), + }, + data: getCloseWalletInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/createSession.ts b/sdk/codama-client/src/generated/instructions/createSession.ts new file mode 100644 index 0000000..f6d79fe --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/createSession.ts @@ -0,0 +1,342 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + fixDecoderSize, + fixEncoderSize, + getBytesDecoder, + getBytesEncoder, + getI64Decoder, + getI64Encoder, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const CREATE_SESSION_DISCRIMINATOR = 5; + +export function getCreateSessionDiscriminatorBytes() { + return getU8Encoder().encode(CREATE_SESSION_DISCRIMINATOR); +} + +export type CreateSessionInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountPayer extends string | AccountMeta = string, + TAccountWallet extends string | AccountMeta = string, + TAccountAdminAuthority extends string | AccountMeta = string, + TAccountSession extends string | AccountMeta = string, + TAccountSystemProgram extends string | AccountMeta = + "11111111111111111111111111111111", + TAccountRent extends string | AccountMeta = + "SysvarRent111111111111111111111111111111111", + TAccountConfig extends string | AccountMeta = string, + TAccountTreasuryShard extends string | AccountMeta = string, + TAccountAuthorizerSigner extends string | AccountMeta = string, + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountPayer extends string + ? WritableSignerAccount & + AccountSignerMeta + : TAccountPayer, + TAccountWallet extends string + ? ReadonlyAccount + : TAccountWallet, + TAccountAdminAuthority extends string + ? WritableAccount + : TAccountAdminAuthority, + TAccountSession extends string + ? WritableAccount + : TAccountSession, + TAccountSystemProgram extends string + ? ReadonlyAccount + : TAccountSystemProgram, + TAccountRent extends string + ? ReadonlyAccount + : TAccountRent, + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountTreasuryShard extends string + ? WritableAccount + : TAccountTreasuryShard, + TAccountAuthorizerSigner extends string + ? ReadonlySignerAccount & + AccountSignerMeta + : TAccountAuthorizerSigner, + ...TRemainingAccounts, + ] + >; + +export type CreateSessionInstructionData = { + discriminator: number; + sessionKey: ReadonlyUint8Array; + expiresAt: bigint; +}; + +export type CreateSessionInstructionDataArgs = { + sessionKey: ReadonlyUint8Array; + expiresAt: number | bigint; +}; + +export function getCreateSessionInstructionDataEncoder(): FixedSizeEncoder { + return transformEncoder( + getStructEncoder([ + ["discriminator", getU8Encoder()], + ["sessionKey", fixEncoderSize(getBytesEncoder(), 32)], + ["expiresAt", getI64Encoder()], + ]), + (value) => ({ ...value, discriminator: CREATE_SESSION_DISCRIMINATOR }), + ); +} + +export function getCreateSessionInstructionDataDecoder(): FixedSizeDecoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["sessionKey", fixDecoderSize(getBytesDecoder(), 32)], + ["expiresAt", getI64Decoder()], + ]); +} + +export function getCreateSessionInstructionDataCodec(): FixedSizeCodec< + CreateSessionInstructionDataArgs, + CreateSessionInstructionData +> { + return combineCodec( + getCreateSessionInstructionDataEncoder(), + getCreateSessionInstructionDataDecoder(), + ); +} + +export type CreateSessionInput< + TAccountPayer extends string = string, + TAccountWallet extends string = string, + TAccountAdminAuthority extends string = string, + TAccountSession extends string = string, + TAccountSystemProgram extends string = string, + TAccountRent extends string = string, + TAccountConfig extends string = string, + TAccountTreasuryShard extends string = string, + TAccountAuthorizerSigner extends string = string, +> = { + /** Transaction payer and rent contributor */ + payer: TransactionSigner; + /** Wallet PDA */ + wallet: Address; + /** Admin/Owner authority PDA authorizing logic */ + adminAuthority: Address; + /** New session PDA to be created */ + session: Address; + /** System Program */ + systemProgram?: Address; + /** Rent Sysvar */ + rent?: Address; + /** Config PDA */ + config: Address; + /** Treasury Shard PDA */ + treasuryShard: Address; + /** Optional signer for Ed25519 authentication */ + authorizerSigner?: TransactionSigner; + sessionKey: CreateSessionInstructionDataArgs["sessionKey"]; + expiresAt: CreateSessionInstructionDataArgs["expiresAt"]; +}; + +export function getCreateSessionInstruction< + TAccountPayer extends string, + TAccountWallet extends string, + TAccountAdminAuthority extends string, + TAccountSession extends string, + TAccountSystemProgram extends string, + TAccountRent extends string, + TAccountConfig extends string, + TAccountTreasuryShard extends string, + TAccountAuthorizerSigner extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: CreateSessionInput< + TAccountPayer, + TAccountWallet, + TAccountAdminAuthority, + TAccountSession, + TAccountSystemProgram, + TAccountRent, + TAccountConfig, + TAccountTreasuryShard, + TAccountAuthorizerSigner + >, + config?: { programAddress?: TProgramAddress }, +): CreateSessionInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountAdminAuthority, + TAccountSession, + TAccountSystemProgram, + TAccountRent, + TAccountConfig, + TAccountTreasuryShard, + TAccountAuthorizerSigner +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + payer: { value: input.payer ?? null, isWritable: true }, + wallet: { value: input.wallet ?? null, isWritable: false }, + adminAuthority: { value: input.adminAuthority ?? null, isWritable: true }, + session: { value: input.session ?? null, isWritable: true }, + systemProgram: { value: input.systemProgram ?? null, isWritable: false }, + rent: { value: input.rent ?? null, isWritable: false }, + config: { value: input.config ?? null, isWritable: false }, + treasuryShard: { value: input.treasuryShard ?? null, isWritable: true }, + authorizerSigner: { + value: input.authorizerSigner ?? null, + isWritable: false, + }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + // Resolve default values. + if (!accounts.systemProgram.value) { + accounts.systemProgram.value = + "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; + } + if (!accounts.rent.value) { + accounts.rent.value = + "SysvarRent111111111111111111111111111111111" as Address<"SysvarRent111111111111111111111111111111111">; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.payer), + getAccountMeta(accounts.wallet), + getAccountMeta(accounts.adminAuthority), + getAccountMeta(accounts.session), + getAccountMeta(accounts.systemProgram), + getAccountMeta(accounts.rent), + getAccountMeta(accounts.config), + getAccountMeta(accounts.treasuryShard), + getAccountMeta(accounts.authorizerSigner), + ], + data: getCreateSessionInstructionDataEncoder().encode( + args as CreateSessionInstructionDataArgs, + ), + programAddress, + } as CreateSessionInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountAdminAuthority, + TAccountSession, + TAccountSystemProgram, + TAccountRent, + TAccountConfig, + TAccountTreasuryShard, + TAccountAuthorizerSigner + >); +} + +export type ParsedCreateSessionInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Transaction payer and rent contributor */ + payer: TAccountMetas[0]; + /** Wallet PDA */ + wallet: TAccountMetas[1]; + /** Admin/Owner authority PDA authorizing logic */ + adminAuthority: TAccountMetas[2]; + /** New session PDA to be created */ + session: TAccountMetas[3]; + /** System Program */ + systemProgram: TAccountMetas[4]; + /** Rent Sysvar */ + rent: TAccountMetas[5]; + /** Config PDA */ + config: TAccountMetas[6]; + /** Treasury Shard PDA */ + treasuryShard: TAccountMetas[7]; + /** Optional signer for Ed25519 authentication */ + authorizerSigner?: TAccountMetas[8] | undefined; + }; + data: CreateSessionInstructionData; +}; + +export function parseCreateSessionInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedCreateSessionInstruction { + if (instruction.accounts.length < 9) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + const getNextOptionalAccount = () => { + const accountMeta = getNextAccount(); + return accountMeta.address === LAZORKIT_PROGRAM_PROGRAM_ADDRESS + ? undefined + : accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + payer: getNextAccount(), + wallet: getNextAccount(), + adminAuthority: getNextAccount(), + session: getNextAccount(), + systemProgram: getNextAccount(), + rent: getNextAccount(), + config: getNextAccount(), + treasuryShard: getNextAccount(), + authorizerSigner: getNextOptionalAccount(), + }, + data: getCreateSessionInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/createWallet.ts b/sdk/codama-client/src/generated/instructions/createWallet.ts new file mode 100644 index 0000000..9bf78a5 --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/createWallet.ts @@ -0,0 +1,332 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + addDecoderSizePrefix, + addEncoderSizePrefix, + combineCodec, + fixDecoderSize, + fixEncoderSize, + getBytesDecoder, + getBytesEncoder, + getStructDecoder, + getStructEncoder, + getU32Decoder, + getU32Encoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type Codec, + type Decoder, + type Encoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const CREATE_WALLET_DISCRIMINATOR = 0; + +export function getCreateWalletDiscriminatorBytes() { + return getU8Encoder().encode(CREATE_WALLET_DISCRIMINATOR); +} + +export type CreateWalletInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountPayer extends string | AccountMeta = string, + TAccountWallet extends string | AccountMeta = string, + TAccountVault extends string | AccountMeta = string, + TAccountAuthority extends string | AccountMeta = string, + TAccountSystemProgram extends string | AccountMeta = + "11111111111111111111111111111111", + TAccountRent extends string | AccountMeta = + "SysvarRent111111111111111111111111111111111", + TAccountConfig extends string | AccountMeta = string, + TAccountTreasuryShard extends string | AccountMeta = string, + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountPayer extends string + ? WritableSignerAccount & + AccountSignerMeta + : TAccountPayer, + TAccountWallet extends string + ? WritableAccount + : TAccountWallet, + TAccountVault extends string + ? WritableAccount + : TAccountVault, + TAccountAuthority extends string + ? WritableAccount + : TAccountAuthority, + TAccountSystemProgram extends string + ? ReadonlyAccount + : TAccountSystemProgram, + TAccountRent extends string + ? ReadonlyAccount + : TAccountRent, + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountTreasuryShard extends string + ? WritableAccount + : TAccountTreasuryShard, + ...TRemainingAccounts, + ] + >; + +export type CreateWalletInstructionData = { + discriminator: number; + userSeed: ReadonlyUint8Array; + authType: number; + authBump: number; + padding: ReadonlyUint8Array; + payload: ReadonlyUint8Array; +}; + +export type CreateWalletInstructionDataArgs = { + userSeed: ReadonlyUint8Array; + authType: number; + authBump: number; + padding: ReadonlyUint8Array; + payload: ReadonlyUint8Array; +}; + +export function getCreateWalletInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ["discriminator", getU8Encoder()], + ["userSeed", fixEncoderSize(getBytesEncoder(), 32)], + ["authType", getU8Encoder()], + ["authBump", getU8Encoder()], + ["padding", fixEncoderSize(getBytesEncoder(), 6)], + ["payload", addEncoderSizePrefix(getBytesEncoder(), getU32Encoder())], + ]), + (value) => ({ ...value, discriminator: CREATE_WALLET_DISCRIMINATOR }), + ); +} + +export function getCreateWalletInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["userSeed", fixDecoderSize(getBytesDecoder(), 32)], + ["authType", getU8Decoder()], + ["authBump", getU8Decoder()], + ["padding", fixDecoderSize(getBytesDecoder(), 6)], + ["payload", addDecoderSizePrefix(getBytesDecoder(), getU32Decoder())], + ]); +} + +export function getCreateWalletInstructionDataCodec(): Codec< + CreateWalletInstructionDataArgs, + CreateWalletInstructionData +> { + return combineCodec( + getCreateWalletInstructionDataEncoder(), + getCreateWalletInstructionDataDecoder(), + ); +} + +export type CreateWalletInput< + TAccountPayer extends string = string, + TAccountWallet extends string = string, + TAccountVault extends string = string, + TAccountAuthority extends string = string, + TAccountSystemProgram extends string = string, + TAccountRent extends string = string, + TAccountConfig extends string = string, + TAccountTreasuryShard extends string = string, +> = { + /** Payer and rent contributor */ + payer: TransactionSigner; + /** Wallet PDA */ + wallet: Address; + /** Vault PDA */ + vault: Address; + /** Initial owner authority PDA */ + authority: Address; + /** System Program */ + systemProgram?: Address; + /** Rent Sysvar */ + rent?: Address; + /** Config PDA */ + config: Address; + /** Treasury Shard PDA */ + treasuryShard: Address; + userSeed: CreateWalletInstructionDataArgs["userSeed"]; + authType: CreateWalletInstructionDataArgs["authType"]; + authBump: CreateWalletInstructionDataArgs["authBump"]; + padding: CreateWalletInstructionDataArgs["padding"]; + payload: CreateWalletInstructionDataArgs["payload"]; +}; + +export function getCreateWalletInstruction< + TAccountPayer extends string, + TAccountWallet extends string, + TAccountVault extends string, + TAccountAuthority extends string, + TAccountSystemProgram extends string, + TAccountRent extends string, + TAccountConfig extends string, + TAccountTreasuryShard extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: CreateWalletInput< + TAccountPayer, + TAccountWallet, + TAccountVault, + TAccountAuthority, + TAccountSystemProgram, + TAccountRent, + TAccountConfig, + TAccountTreasuryShard + >, + config?: { programAddress?: TProgramAddress }, +): CreateWalletInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountVault, + TAccountAuthority, + TAccountSystemProgram, + TAccountRent, + TAccountConfig, + TAccountTreasuryShard +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + payer: { value: input.payer ?? null, isWritable: true }, + wallet: { value: input.wallet ?? null, isWritable: true }, + vault: { value: input.vault ?? null, isWritable: true }, + authority: { value: input.authority ?? null, isWritable: true }, + systemProgram: { value: input.systemProgram ?? null, isWritable: false }, + rent: { value: input.rent ?? null, isWritable: false }, + config: { value: input.config ?? null, isWritable: false }, + treasuryShard: { value: input.treasuryShard ?? null, isWritable: true }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + // Resolve default values. + if (!accounts.systemProgram.value) { + accounts.systemProgram.value = + "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; + } + if (!accounts.rent.value) { + accounts.rent.value = + "SysvarRent111111111111111111111111111111111" as Address<"SysvarRent111111111111111111111111111111111">; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.payer), + getAccountMeta(accounts.wallet), + getAccountMeta(accounts.vault), + getAccountMeta(accounts.authority), + getAccountMeta(accounts.systemProgram), + getAccountMeta(accounts.rent), + getAccountMeta(accounts.config), + getAccountMeta(accounts.treasuryShard), + ], + data: getCreateWalletInstructionDataEncoder().encode( + args as CreateWalletInstructionDataArgs, + ), + programAddress, + } as CreateWalletInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountVault, + TAccountAuthority, + TAccountSystemProgram, + TAccountRent, + TAccountConfig, + TAccountTreasuryShard + >); +} + +export type ParsedCreateWalletInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Payer and rent contributor */ + payer: TAccountMetas[0]; + /** Wallet PDA */ + wallet: TAccountMetas[1]; + /** Vault PDA */ + vault: TAccountMetas[2]; + /** Initial owner authority PDA */ + authority: TAccountMetas[3]; + /** System Program */ + systemProgram: TAccountMetas[4]; + /** Rent Sysvar */ + rent: TAccountMetas[5]; + /** Config PDA */ + config: TAccountMetas[6]; + /** Treasury Shard PDA */ + treasuryShard: TAccountMetas[7]; + }; + data: CreateWalletInstructionData; +}; + +export function parseCreateWalletInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedCreateWalletInstruction { + if (instruction.accounts.length < 8) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + payer: getNextAccount(), + wallet: getNextAccount(), + vault: getNextAccount(), + authority: getNextAccount(), + systemProgram: getNextAccount(), + rent: getNextAccount(), + config: getNextAccount(), + treasuryShard: getNextAccount(), + }, + data: getCreateWalletInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/execute.ts b/sdk/codama-client/src/generated/instructions/execute.ts new file mode 100644 index 0000000..cfb911e --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/execute.ts @@ -0,0 +1,315 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + addDecoderSizePrefix, + addEncoderSizePrefix, + combineCodec, + getBytesDecoder, + getBytesEncoder, + getStructDecoder, + getStructEncoder, + getU32Decoder, + getU32Encoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type Codec, + type Decoder, + type Encoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const EXECUTE_DISCRIMINATOR = 4; + +export function getExecuteDiscriminatorBytes() { + return getU8Encoder().encode(EXECUTE_DISCRIMINATOR); +} + +export type ExecuteInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountPayer extends string | AccountMeta = string, + TAccountWallet extends string | AccountMeta = string, + TAccountAuthority extends string | AccountMeta = string, + TAccountVault extends string | AccountMeta = string, + TAccountConfig extends string | AccountMeta = string, + TAccountTreasuryShard extends string | AccountMeta = string, + TAccountSystemProgram extends string | AccountMeta = + "11111111111111111111111111111111", + TAccountSysvarInstructions extends string | AccountMeta = string, + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountPayer extends string + ? WritableSignerAccount & + AccountSignerMeta + : TAccountPayer, + TAccountWallet extends string + ? ReadonlyAccount + : TAccountWallet, + TAccountAuthority extends string + ? ReadonlyAccount + : TAccountAuthority, + TAccountVault extends string + ? WritableAccount + : TAccountVault, + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountTreasuryShard extends string + ? WritableAccount + : TAccountTreasuryShard, + TAccountSystemProgram extends string + ? ReadonlyAccount + : TAccountSystemProgram, + TAccountSysvarInstructions extends string + ? ReadonlyAccount + : TAccountSysvarInstructions, + ...TRemainingAccounts, + ] + >; + +export type ExecuteInstructionData = { + discriminator: number; + instructions: ReadonlyUint8Array; +}; + +export type ExecuteInstructionDataArgs = { instructions: ReadonlyUint8Array }; + +export function getExecuteInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ["discriminator", getU8Encoder()], + [ + "instructions", + addEncoderSizePrefix(getBytesEncoder(), getU32Encoder()), + ], + ]), + (value) => ({ ...value, discriminator: EXECUTE_DISCRIMINATOR }), + ); +} + +export function getExecuteInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["instructions", addDecoderSizePrefix(getBytesDecoder(), getU32Decoder())], + ]); +} + +export function getExecuteInstructionDataCodec(): Codec< + ExecuteInstructionDataArgs, + ExecuteInstructionData +> { + return combineCodec( + getExecuteInstructionDataEncoder(), + getExecuteInstructionDataDecoder(), + ); +} + +export type ExecuteInput< + TAccountPayer extends string = string, + TAccountWallet extends string = string, + TAccountAuthority extends string = string, + TAccountVault extends string = string, + TAccountConfig extends string = string, + TAccountTreasuryShard extends string = string, + TAccountSystemProgram extends string = string, + TAccountSysvarInstructions extends string = string, +> = { + /** Transaction payer */ + payer: TransactionSigner; + /** Wallet PDA */ + wallet: Address; + /** Authority or Session PDA authorizing execution */ + authority: Address; + /** Vault PDA */ + vault: Address; + /** Config PDA */ + config: Address; + /** Treasury Shard PDA */ + treasuryShard: Address; + /** System Program */ + systemProgram?: Address; + /** Sysvar Instructions (required for Secp256r1) */ + sysvarInstructions?: Address; + instructions: ExecuteInstructionDataArgs["instructions"]; +}; + +export function getExecuteInstruction< + TAccountPayer extends string, + TAccountWallet extends string, + TAccountAuthority extends string, + TAccountVault extends string, + TAccountConfig extends string, + TAccountTreasuryShard extends string, + TAccountSystemProgram extends string, + TAccountSysvarInstructions extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: ExecuteInput< + TAccountPayer, + TAccountWallet, + TAccountAuthority, + TAccountVault, + TAccountConfig, + TAccountTreasuryShard, + TAccountSystemProgram, + TAccountSysvarInstructions + >, + config?: { programAddress?: TProgramAddress }, +): ExecuteInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountAuthority, + TAccountVault, + TAccountConfig, + TAccountTreasuryShard, + TAccountSystemProgram, + TAccountSysvarInstructions +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + payer: { value: input.payer ?? null, isWritable: true }, + wallet: { value: input.wallet ?? null, isWritable: false }, + authority: { value: input.authority ?? null, isWritable: false }, + vault: { value: input.vault ?? null, isWritable: true }, + config: { value: input.config ?? null, isWritable: false }, + treasuryShard: { value: input.treasuryShard ?? null, isWritable: true }, + systemProgram: { value: input.systemProgram ?? null, isWritable: false }, + sysvarInstructions: { + value: input.sysvarInstructions ?? null, + isWritable: false, + }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + // Resolve default values. + if (!accounts.systemProgram.value) { + accounts.systemProgram.value = + "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.payer), + getAccountMeta(accounts.wallet), + getAccountMeta(accounts.authority), + getAccountMeta(accounts.vault), + getAccountMeta(accounts.config), + getAccountMeta(accounts.treasuryShard), + getAccountMeta(accounts.systemProgram), + getAccountMeta(accounts.sysvarInstructions), + ], + data: getExecuteInstructionDataEncoder().encode( + args as ExecuteInstructionDataArgs, + ), + programAddress, + } as ExecuteInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountAuthority, + TAccountVault, + TAccountConfig, + TAccountTreasuryShard, + TAccountSystemProgram, + TAccountSysvarInstructions + >); +} + +export type ParsedExecuteInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Transaction payer */ + payer: TAccountMetas[0]; + /** Wallet PDA */ + wallet: TAccountMetas[1]; + /** Authority or Session PDA authorizing execution */ + authority: TAccountMetas[2]; + /** Vault PDA */ + vault: TAccountMetas[3]; + /** Config PDA */ + config: TAccountMetas[4]; + /** Treasury Shard PDA */ + treasuryShard: TAccountMetas[5]; + /** System Program */ + systemProgram: TAccountMetas[6]; + /** Sysvar Instructions (required for Secp256r1) */ + sysvarInstructions?: TAccountMetas[7] | undefined; + }; + data: ExecuteInstructionData; +}; + +export function parseExecuteInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedExecuteInstruction { + if (instruction.accounts.length < 8) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + const getNextOptionalAccount = () => { + const accountMeta = getNextAccount(); + return accountMeta.address === LAZORKIT_PROGRAM_PROGRAM_ADDRESS + ? undefined + : accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + payer: getNextAccount(), + wallet: getNextAccount(), + authority: getNextAccount(), + vault: getNextAccount(), + config: getNextAccount(), + treasuryShard: getNextAccount(), + systemProgram: getNextAccount(), + sysvarInstructions: getNextOptionalAccount(), + }, + data: getExecuteInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/index.ts b/sdk/codama-client/src/generated/instructions/index.ts new file mode 100644 index 0000000..1455eba --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/index.ts @@ -0,0 +1,20 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +export * from "./addAuthority"; +export * from "./closeSession"; +export * from "./closeWallet"; +export * from "./createSession"; +export * from "./createWallet"; +export * from "./execute"; +export * from "./initializeConfig"; +export * from "./initTreasuryShard"; +export * from "./removeAuthority"; +export * from "./sweepTreasury"; +export * from "./transferOwnership"; +export * from "./updateConfig"; diff --git a/sdk/codama-client/src/generated/instructions/initTreasuryShard.ts b/sdk/codama-client/src/generated/instructions/initTreasuryShard.ts new file mode 100644 index 0000000..4fd8a90 --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/initTreasuryShard.ts @@ -0,0 +1,254 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const INIT_TREASURY_SHARD_DISCRIMINATOR = 11; + +export function getInitTreasuryShardDiscriminatorBytes() { + return getU8Encoder().encode(INIT_TREASURY_SHARD_DISCRIMINATOR); +} + +export type InitTreasuryShardInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountPayer extends string | AccountMeta = string, + TAccountConfig extends string | AccountMeta = string, + TAccountTreasuryShard extends string | AccountMeta = string, + TAccountSystemProgram extends string | AccountMeta = + "11111111111111111111111111111111", + TAccountRent extends string | AccountMeta = + "SysvarRent111111111111111111111111111111111", + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountPayer extends string + ? WritableSignerAccount & + AccountSignerMeta + : TAccountPayer, + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountTreasuryShard extends string + ? WritableAccount + : TAccountTreasuryShard, + TAccountSystemProgram extends string + ? ReadonlyAccount + : TAccountSystemProgram, + TAccountRent extends string + ? ReadonlyAccount + : TAccountRent, + ...TRemainingAccounts, + ] + >; + +export type InitTreasuryShardInstructionData = { + discriminator: number; + shardId: number; +}; + +export type InitTreasuryShardInstructionDataArgs = { shardId: number }; + +export function getInitTreasuryShardInstructionDataEncoder(): FixedSizeEncoder { + return transformEncoder( + getStructEncoder([ + ["discriminator", getU8Encoder()], + ["shardId", getU8Encoder()], + ]), + (value) => ({ ...value, discriminator: INIT_TREASURY_SHARD_DISCRIMINATOR }), + ); +} + +export function getInitTreasuryShardInstructionDataDecoder(): FixedSizeDecoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["shardId", getU8Decoder()], + ]); +} + +export function getInitTreasuryShardInstructionDataCodec(): FixedSizeCodec< + InitTreasuryShardInstructionDataArgs, + InitTreasuryShardInstructionData +> { + return combineCodec( + getInitTreasuryShardInstructionDataEncoder(), + getInitTreasuryShardInstructionDataDecoder(), + ); +} + +export type InitTreasuryShardInput< + TAccountPayer extends string = string, + TAccountConfig extends string = string, + TAccountTreasuryShard extends string = string, + TAccountSystemProgram extends string = string, + TAccountRent extends string = string, +> = { + /** Pays for rent exemption */ + payer: TransactionSigner; + /** Config PDA */ + config: Address; + /** Treasury Shard PDA */ + treasuryShard: Address; + /** System Program */ + systemProgram?: Address; + /** Rent Sysvar */ + rent?: Address; + shardId: InitTreasuryShardInstructionDataArgs["shardId"]; +}; + +export function getInitTreasuryShardInstruction< + TAccountPayer extends string, + TAccountConfig extends string, + TAccountTreasuryShard extends string, + TAccountSystemProgram extends string, + TAccountRent extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: InitTreasuryShardInput< + TAccountPayer, + TAccountConfig, + TAccountTreasuryShard, + TAccountSystemProgram, + TAccountRent + >, + config?: { programAddress?: TProgramAddress }, +): InitTreasuryShardInstruction< + TProgramAddress, + TAccountPayer, + TAccountConfig, + TAccountTreasuryShard, + TAccountSystemProgram, + TAccountRent +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + payer: { value: input.payer ?? null, isWritable: true }, + config: { value: input.config ?? null, isWritable: false }, + treasuryShard: { value: input.treasuryShard ?? null, isWritable: true }, + systemProgram: { value: input.systemProgram ?? null, isWritable: false }, + rent: { value: input.rent ?? null, isWritable: false }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + // Resolve default values. + if (!accounts.systemProgram.value) { + accounts.systemProgram.value = + "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; + } + if (!accounts.rent.value) { + accounts.rent.value = + "SysvarRent111111111111111111111111111111111" as Address<"SysvarRent111111111111111111111111111111111">; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.payer), + getAccountMeta(accounts.config), + getAccountMeta(accounts.treasuryShard), + getAccountMeta(accounts.systemProgram), + getAccountMeta(accounts.rent), + ], + data: getInitTreasuryShardInstructionDataEncoder().encode( + args as InitTreasuryShardInstructionDataArgs, + ), + programAddress, + } as InitTreasuryShardInstruction< + TProgramAddress, + TAccountPayer, + TAccountConfig, + TAccountTreasuryShard, + TAccountSystemProgram, + TAccountRent + >); +} + +export type ParsedInitTreasuryShardInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Pays for rent exemption */ + payer: TAccountMetas[0]; + /** Config PDA */ + config: TAccountMetas[1]; + /** Treasury Shard PDA */ + treasuryShard: TAccountMetas[2]; + /** System Program */ + systemProgram: TAccountMetas[3]; + /** Rent Sysvar */ + rent: TAccountMetas[4]; + }; + data: InitTreasuryShardInstructionData; +}; + +export function parseInitTreasuryShardInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedInitTreasuryShardInstruction { + if (instruction.accounts.length < 5) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + payer: getNextAccount(), + config: getNextAccount(), + treasuryShard: getNextAccount(), + systemProgram: getNextAccount(), + rent: getNextAccount(), + }, + data: getInitTreasuryShardInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/initializeConfig.ts b/sdk/codama-client/src/generated/instructions/initializeConfig.ts new file mode 100644 index 0000000..6eda057 --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/initializeConfig.ts @@ -0,0 +1,252 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const INITIALIZE_CONFIG_DISCRIMINATOR = 6; + +export function getInitializeConfigDiscriminatorBytes() { + return getU8Encoder().encode(INITIALIZE_CONFIG_DISCRIMINATOR); +} + +export type InitializeConfigInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountAdmin extends string | AccountMeta = string, + TAccountConfig extends string | AccountMeta = string, + TAccountSystemProgram extends string | AccountMeta = + "11111111111111111111111111111111", + TAccountRent extends string | AccountMeta = + "SysvarRent111111111111111111111111111111111", + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountAdmin extends string + ? WritableSignerAccount & + AccountSignerMeta + : TAccountAdmin, + TAccountConfig extends string + ? WritableAccount + : TAccountConfig, + TAccountSystemProgram extends string + ? ReadonlyAccount + : TAccountSystemProgram, + TAccountRent extends string + ? ReadonlyAccount + : TAccountRent, + ...TRemainingAccounts, + ] + >; + +export type InitializeConfigInstructionData = { + discriminator: number; + walletFee: bigint; + actionFee: bigint; + numShards: number; +}; + +export type InitializeConfigInstructionDataArgs = { + walletFee: number | bigint; + actionFee: number | bigint; + numShards: number; +}; + +export function getInitializeConfigInstructionDataEncoder(): FixedSizeEncoder { + return transformEncoder( + getStructEncoder([ + ["discriminator", getU8Encoder()], + ["walletFee", getU64Encoder()], + ["actionFee", getU64Encoder()], + ["numShards", getU8Encoder()], + ]), + (value) => ({ ...value, discriminator: INITIALIZE_CONFIG_DISCRIMINATOR }), + ); +} + +export function getInitializeConfigInstructionDataDecoder(): FixedSizeDecoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["walletFee", getU64Decoder()], + ["actionFee", getU64Decoder()], + ["numShards", getU8Decoder()], + ]); +} + +export function getInitializeConfigInstructionDataCodec(): FixedSizeCodec< + InitializeConfigInstructionDataArgs, + InitializeConfigInstructionData +> { + return combineCodec( + getInitializeConfigInstructionDataEncoder(), + getInitializeConfigInstructionDataDecoder(), + ); +} + +export type InitializeConfigInput< + TAccountAdmin extends string = string, + TAccountConfig extends string = string, + TAccountSystemProgram extends string = string, + TAccountRent extends string = string, +> = { + /** Initial contract admin */ + admin: TransactionSigner; + /** Config PDA */ + config: Address; + /** System Program */ + systemProgram?: Address; + /** Rent Sysvar */ + rent?: Address; + walletFee: InitializeConfigInstructionDataArgs["walletFee"]; + actionFee: InitializeConfigInstructionDataArgs["actionFee"]; + numShards: InitializeConfigInstructionDataArgs["numShards"]; +}; + +export function getInitializeConfigInstruction< + TAccountAdmin extends string, + TAccountConfig extends string, + TAccountSystemProgram extends string, + TAccountRent extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: InitializeConfigInput< + TAccountAdmin, + TAccountConfig, + TAccountSystemProgram, + TAccountRent + >, + config?: { programAddress?: TProgramAddress }, +): InitializeConfigInstruction< + TProgramAddress, + TAccountAdmin, + TAccountConfig, + TAccountSystemProgram, + TAccountRent +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + admin: { value: input.admin ?? null, isWritable: true }, + config: { value: input.config ?? null, isWritable: true }, + systemProgram: { value: input.systemProgram ?? null, isWritable: false }, + rent: { value: input.rent ?? null, isWritable: false }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + // Resolve default values. + if (!accounts.systemProgram.value) { + accounts.systemProgram.value = + "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; + } + if (!accounts.rent.value) { + accounts.rent.value = + "SysvarRent111111111111111111111111111111111" as Address<"SysvarRent111111111111111111111111111111111">; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.admin), + getAccountMeta(accounts.config), + getAccountMeta(accounts.systemProgram), + getAccountMeta(accounts.rent), + ], + data: getInitializeConfigInstructionDataEncoder().encode( + args as InitializeConfigInstructionDataArgs, + ), + programAddress, + } as InitializeConfigInstruction< + TProgramAddress, + TAccountAdmin, + TAccountConfig, + TAccountSystemProgram, + TAccountRent + >); +} + +export type ParsedInitializeConfigInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Initial contract admin */ + admin: TAccountMetas[0]; + /** Config PDA */ + config: TAccountMetas[1]; + /** System Program */ + systemProgram: TAccountMetas[2]; + /** Rent Sysvar */ + rent: TAccountMetas[3]; + }; + data: InitializeConfigInstructionData; +}; + +export function parseInitializeConfigInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedInitializeConfigInstruction { + if (instruction.accounts.length < 4) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + admin: getNextAccount(), + config: getNextAccount(), + systemProgram: getNextAccount(), + rent: getNextAccount(), + }, + data: getInitializeConfigInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/removeAuthority.ts b/sdk/codama-client/src/generated/instructions/removeAuthority.ts new file mode 100644 index 0000000..fe54aed --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/removeAuthority.ts @@ -0,0 +1,312 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const REMOVE_AUTHORITY_DISCRIMINATOR = 2; + +export function getRemoveAuthorityDiscriminatorBytes() { + return getU8Encoder().encode(REMOVE_AUTHORITY_DISCRIMINATOR); +} + +export type RemoveAuthorityInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountPayer extends string | AccountMeta = string, + TAccountWallet extends string | AccountMeta = string, + TAccountAdminAuthority extends string | AccountMeta = string, + TAccountTargetAuthority extends string | AccountMeta = string, + TAccountRefundDestination extends string | AccountMeta = string, + TAccountSystemProgram extends string | AccountMeta = + "11111111111111111111111111111111", + TAccountConfig extends string | AccountMeta = string, + TAccountTreasuryShard extends string | AccountMeta = string, + TAccountAuthorizerSigner extends string | AccountMeta = string, + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountPayer extends string + ? WritableSignerAccount & + AccountSignerMeta + : TAccountPayer, + TAccountWallet extends string + ? ReadonlyAccount + : TAccountWallet, + TAccountAdminAuthority extends string + ? WritableAccount + : TAccountAdminAuthority, + TAccountTargetAuthority extends string + ? WritableAccount + : TAccountTargetAuthority, + TAccountRefundDestination extends string + ? WritableAccount + : TAccountRefundDestination, + TAccountSystemProgram extends string + ? ReadonlyAccount + : TAccountSystemProgram, + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountTreasuryShard extends string + ? WritableAccount + : TAccountTreasuryShard, + TAccountAuthorizerSigner extends string + ? ReadonlySignerAccount & + AccountSignerMeta + : TAccountAuthorizerSigner, + ...TRemainingAccounts, + ] + >; + +export type RemoveAuthorityInstructionData = { discriminator: number }; + +export type RemoveAuthorityInstructionDataArgs = {}; + +export function getRemoveAuthorityInstructionDataEncoder(): FixedSizeEncoder { + return transformEncoder( + getStructEncoder([["discriminator", getU8Encoder()]]), + (value) => ({ ...value, discriminator: REMOVE_AUTHORITY_DISCRIMINATOR }), + ); +} + +export function getRemoveAuthorityInstructionDataDecoder(): FixedSizeDecoder { + return getStructDecoder([["discriminator", getU8Decoder()]]); +} + +export function getRemoveAuthorityInstructionDataCodec(): FixedSizeCodec< + RemoveAuthorityInstructionDataArgs, + RemoveAuthorityInstructionData +> { + return combineCodec( + getRemoveAuthorityInstructionDataEncoder(), + getRemoveAuthorityInstructionDataDecoder(), + ); +} + +export type RemoveAuthorityInput< + TAccountPayer extends string = string, + TAccountWallet extends string = string, + TAccountAdminAuthority extends string = string, + TAccountTargetAuthority extends string = string, + TAccountRefundDestination extends string = string, + TAccountSystemProgram extends string = string, + TAccountConfig extends string = string, + TAccountTreasuryShard extends string = string, + TAccountAuthorizerSigner extends string = string, +> = { + /** Transaction payer */ + payer: TransactionSigner; + /** Wallet PDA */ + wallet: Address; + /** Admin authority PDA authorizing this action */ + adminAuthority: Address; + /** Authority PDA to be removed */ + targetAuthority: Address; + /** Account to receive rent refund */ + refundDestination: Address; + /** System Program */ + systemProgram?: Address; + /** Config PDA */ + config: Address; + /** Treasury Shard PDA */ + treasuryShard: Address; + /** Optional signer for Ed25519 authentication */ + authorizerSigner?: TransactionSigner; +}; + +export function getRemoveAuthorityInstruction< + TAccountPayer extends string, + TAccountWallet extends string, + TAccountAdminAuthority extends string, + TAccountTargetAuthority extends string, + TAccountRefundDestination extends string, + TAccountSystemProgram extends string, + TAccountConfig extends string, + TAccountTreasuryShard extends string, + TAccountAuthorizerSigner extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: RemoveAuthorityInput< + TAccountPayer, + TAccountWallet, + TAccountAdminAuthority, + TAccountTargetAuthority, + TAccountRefundDestination, + TAccountSystemProgram, + TAccountConfig, + TAccountTreasuryShard, + TAccountAuthorizerSigner + >, + config?: { programAddress?: TProgramAddress }, +): RemoveAuthorityInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountAdminAuthority, + TAccountTargetAuthority, + TAccountRefundDestination, + TAccountSystemProgram, + TAccountConfig, + TAccountTreasuryShard, + TAccountAuthorizerSigner +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + payer: { value: input.payer ?? null, isWritable: true }, + wallet: { value: input.wallet ?? null, isWritable: false }, + adminAuthority: { value: input.adminAuthority ?? null, isWritable: true }, + targetAuthority: { value: input.targetAuthority ?? null, isWritable: true }, + refundDestination: { + value: input.refundDestination ?? null, + isWritable: true, + }, + systemProgram: { value: input.systemProgram ?? null, isWritable: false }, + config: { value: input.config ?? null, isWritable: false }, + treasuryShard: { value: input.treasuryShard ?? null, isWritable: true }, + authorizerSigner: { + value: input.authorizerSigner ?? null, + isWritable: false, + }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Resolve default values. + if (!accounts.systemProgram.value) { + accounts.systemProgram.value = + "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.payer), + getAccountMeta(accounts.wallet), + getAccountMeta(accounts.adminAuthority), + getAccountMeta(accounts.targetAuthority), + getAccountMeta(accounts.refundDestination), + getAccountMeta(accounts.systemProgram), + getAccountMeta(accounts.config), + getAccountMeta(accounts.treasuryShard), + getAccountMeta(accounts.authorizerSigner), + ], + data: getRemoveAuthorityInstructionDataEncoder().encode({}), + programAddress, + } as RemoveAuthorityInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountAdminAuthority, + TAccountTargetAuthority, + TAccountRefundDestination, + TAccountSystemProgram, + TAccountConfig, + TAccountTreasuryShard, + TAccountAuthorizerSigner + >); +} + +export type ParsedRemoveAuthorityInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Transaction payer */ + payer: TAccountMetas[0]; + /** Wallet PDA */ + wallet: TAccountMetas[1]; + /** Admin authority PDA authorizing this action */ + adminAuthority: TAccountMetas[2]; + /** Authority PDA to be removed */ + targetAuthority: TAccountMetas[3]; + /** Account to receive rent refund */ + refundDestination: TAccountMetas[4]; + /** System Program */ + systemProgram: TAccountMetas[5]; + /** Config PDA */ + config: TAccountMetas[6]; + /** Treasury Shard PDA */ + treasuryShard: TAccountMetas[7]; + /** Optional signer for Ed25519 authentication */ + authorizerSigner?: TAccountMetas[8] | undefined; + }; + data: RemoveAuthorityInstructionData; +}; + +export function parseRemoveAuthorityInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedRemoveAuthorityInstruction { + if (instruction.accounts.length < 9) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + const getNextOptionalAccount = () => { + const accountMeta = getNextAccount(); + return accountMeta.address === LAZORKIT_PROGRAM_PROGRAM_ADDRESS + ? undefined + : accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + payer: getNextAccount(), + wallet: getNextAccount(), + adminAuthority: getNextAccount(), + targetAuthority: getNextAccount(), + refundDestination: getNextAccount(), + systemProgram: getNextAccount(), + config: getNextAccount(), + treasuryShard: getNextAccount(), + authorizerSigner: getNextOptionalAccount(), + }, + data: getRemoveAuthorityInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/sweepTreasury.ts b/sdk/codama-client/src/generated/instructions/sweepTreasury.ts new file mode 100644 index 0000000..582acba --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/sweepTreasury.ts @@ -0,0 +1,226 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const SWEEP_TREASURY_DISCRIMINATOR = 10; + +export function getSweepTreasuryDiscriminatorBytes() { + return getU8Encoder().encode(SWEEP_TREASURY_DISCRIMINATOR); +} + +export type SweepTreasuryInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountAdmin extends string | AccountMeta = string, + TAccountConfig extends string | AccountMeta = string, + TAccountTreasuryShard extends string | AccountMeta = string, + TAccountDestination extends string | AccountMeta = string, + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountAdmin extends string + ? ReadonlySignerAccount & + AccountSignerMeta + : TAccountAdmin, + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountTreasuryShard extends string + ? WritableAccount + : TAccountTreasuryShard, + TAccountDestination extends string + ? WritableAccount + : TAccountDestination, + ...TRemainingAccounts, + ] + >; + +export type SweepTreasuryInstructionData = { + discriminator: number; + shardId: number; +}; + +export type SweepTreasuryInstructionDataArgs = { shardId: number }; + +export function getSweepTreasuryInstructionDataEncoder(): FixedSizeEncoder { + return transformEncoder( + getStructEncoder([ + ["discriminator", getU8Encoder()], + ["shardId", getU8Encoder()], + ]), + (value) => ({ ...value, discriminator: SWEEP_TREASURY_DISCRIMINATOR }), + ); +} + +export function getSweepTreasuryInstructionDataDecoder(): FixedSizeDecoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["shardId", getU8Decoder()], + ]); +} + +export function getSweepTreasuryInstructionDataCodec(): FixedSizeCodec< + SweepTreasuryInstructionDataArgs, + SweepTreasuryInstructionData +> { + return combineCodec( + getSweepTreasuryInstructionDataEncoder(), + getSweepTreasuryInstructionDataDecoder(), + ); +} + +export type SweepTreasuryInput< + TAccountAdmin extends string = string, + TAccountConfig extends string = string, + TAccountTreasuryShard extends string = string, + TAccountDestination extends string = string, +> = { + /** Contract admin */ + admin: TransactionSigner; + /** Config PDA */ + config: Address; + /** Treasury Shard PDA */ + treasuryShard: Address; + /** Receives swept funds */ + destination: Address; + shardId: SweepTreasuryInstructionDataArgs["shardId"]; +}; + +export function getSweepTreasuryInstruction< + TAccountAdmin extends string, + TAccountConfig extends string, + TAccountTreasuryShard extends string, + TAccountDestination extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: SweepTreasuryInput< + TAccountAdmin, + TAccountConfig, + TAccountTreasuryShard, + TAccountDestination + >, + config?: { programAddress?: TProgramAddress }, +): SweepTreasuryInstruction< + TProgramAddress, + TAccountAdmin, + TAccountConfig, + TAccountTreasuryShard, + TAccountDestination +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + admin: { value: input.admin ?? null, isWritable: false }, + config: { value: input.config ?? null, isWritable: false }, + treasuryShard: { value: input.treasuryShard ?? null, isWritable: true }, + destination: { value: input.destination ?? null, isWritable: true }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.admin), + getAccountMeta(accounts.config), + getAccountMeta(accounts.treasuryShard), + getAccountMeta(accounts.destination), + ], + data: getSweepTreasuryInstructionDataEncoder().encode( + args as SweepTreasuryInstructionDataArgs, + ), + programAddress, + } as SweepTreasuryInstruction< + TProgramAddress, + TAccountAdmin, + TAccountConfig, + TAccountTreasuryShard, + TAccountDestination + >); +} + +export type ParsedSweepTreasuryInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Contract admin */ + admin: TAccountMetas[0]; + /** Config PDA */ + config: TAccountMetas[1]; + /** Treasury Shard PDA */ + treasuryShard: TAccountMetas[2]; + /** Receives swept funds */ + destination: TAccountMetas[3]; + }; + data: SweepTreasuryInstructionData; +}; + +export function parseSweepTreasuryInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedSweepTreasuryInstruction { + if (instruction.accounts.length < 4) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + admin: getNextAccount(), + config: getNextAccount(), + treasuryShard: getNextAccount(), + destination: getNextAccount(), + }, + data: getSweepTreasuryInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/transferOwnership.ts b/sdk/codama-client/src/generated/instructions/transferOwnership.ts new file mode 100644 index 0000000..2538dea --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/transferOwnership.ts @@ -0,0 +1,327 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + addDecoderSizePrefix, + addEncoderSizePrefix, + combineCodec, + getBytesDecoder, + getBytesEncoder, + getStructDecoder, + getStructEncoder, + getU32Decoder, + getU32Encoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type Codec, + type Decoder, + type Encoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const TRANSFER_OWNERSHIP_DISCRIMINATOR = 3; + +export function getTransferOwnershipDiscriminatorBytes() { + return getU8Encoder().encode(TRANSFER_OWNERSHIP_DISCRIMINATOR); +} + +export type TransferOwnershipInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountPayer extends string | AccountMeta = string, + TAccountWallet extends string | AccountMeta = string, + TAccountCurrentOwnerAuthority extends string | AccountMeta = string, + TAccountNewOwnerAuthority extends string | AccountMeta = string, + TAccountSystemProgram extends string | AccountMeta = + "11111111111111111111111111111111", + TAccountAuthorizerSigner extends string | AccountMeta = string, + TAccountConfig extends string | AccountMeta = string, + TAccountTreasuryShard extends string | AccountMeta = string, + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountPayer extends string + ? WritableSignerAccount & + AccountSignerMeta + : TAccountPayer, + TAccountWallet extends string + ? ReadonlyAccount + : TAccountWallet, + TAccountCurrentOwnerAuthority extends string + ? WritableAccount + : TAccountCurrentOwnerAuthority, + TAccountNewOwnerAuthority extends string + ? WritableAccount + : TAccountNewOwnerAuthority, + TAccountSystemProgram extends string + ? ReadonlyAccount + : TAccountSystemProgram, + TAccountAuthorizerSigner extends string + ? ReadonlySignerAccount & + AccountSignerMeta + : TAccountAuthorizerSigner, + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountTreasuryShard extends string + ? WritableAccount + : TAccountTreasuryShard, + ...TRemainingAccounts, + ] + >; + +export type TransferOwnershipInstructionData = { + discriminator: number; + newType: number; + payload: ReadonlyUint8Array; +}; + +export type TransferOwnershipInstructionDataArgs = { + newType: number; + payload: ReadonlyUint8Array; +}; + +export function getTransferOwnershipInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ["discriminator", getU8Encoder()], + ["newType", getU8Encoder()], + ["payload", addEncoderSizePrefix(getBytesEncoder(), getU32Encoder())], + ]), + (value) => ({ ...value, discriminator: TRANSFER_OWNERSHIP_DISCRIMINATOR }), + ); +} + +export function getTransferOwnershipInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ["discriminator", getU8Decoder()], + ["newType", getU8Decoder()], + ["payload", addDecoderSizePrefix(getBytesDecoder(), getU32Decoder())], + ]); +} + +export function getTransferOwnershipInstructionDataCodec(): Codec< + TransferOwnershipInstructionDataArgs, + TransferOwnershipInstructionData +> { + return combineCodec( + getTransferOwnershipInstructionDataEncoder(), + getTransferOwnershipInstructionDataDecoder(), + ); +} + +export type TransferOwnershipInput< + TAccountPayer extends string = string, + TAccountWallet extends string = string, + TAccountCurrentOwnerAuthority extends string = string, + TAccountNewOwnerAuthority extends string = string, + TAccountSystemProgram extends string = string, + TAccountAuthorizerSigner extends string = string, + TAccountConfig extends string = string, + TAccountTreasuryShard extends string = string, +> = { + /** Transaction payer */ + payer: TransactionSigner; + /** Wallet PDA */ + wallet: Address; + /** Current owner authority PDA */ + currentOwnerAuthority: Address; + /** New owner authority PDA to be created */ + newOwnerAuthority: Address; + /** System Program */ + systemProgram?: Address; + /** Optional signer for Ed25519 authentication */ + authorizerSigner?: TransactionSigner; + /** Config PDA */ + config: Address; + /** Treasury Shard PDA */ + treasuryShard: Address; + newType: TransferOwnershipInstructionDataArgs["newType"]; + payload: TransferOwnershipInstructionDataArgs["payload"]; +}; + +export function getTransferOwnershipInstruction< + TAccountPayer extends string, + TAccountWallet extends string, + TAccountCurrentOwnerAuthority extends string, + TAccountNewOwnerAuthority extends string, + TAccountSystemProgram extends string, + TAccountAuthorizerSigner extends string, + TAccountConfig extends string, + TAccountTreasuryShard extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: TransferOwnershipInput< + TAccountPayer, + TAccountWallet, + TAccountCurrentOwnerAuthority, + TAccountNewOwnerAuthority, + TAccountSystemProgram, + TAccountAuthorizerSigner, + TAccountConfig, + TAccountTreasuryShard + >, + config?: { programAddress?: TProgramAddress }, +): TransferOwnershipInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountCurrentOwnerAuthority, + TAccountNewOwnerAuthority, + TAccountSystemProgram, + TAccountAuthorizerSigner, + TAccountConfig, + TAccountTreasuryShard +> { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + payer: { value: input.payer ?? null, isWritable: true }, + wallet: { value: input.wallet ?? null, isWritable: false }, + currentOwnerAuthority: { + value: input.currentOwnerAuthority ?? null, + isWritable: true, + }, + newOwnerAuthority: { + value: input.newOwnerAuthority ?? null, + isWritable: true, + }, + systemProgram: { value: input.systemProgram ?? null, isWritable: false }, + authorizerSigner: { + value: input.authorizerSigner ?? null, + isWritable: false, + }, + config: { value: input.config ?? null, isWritable: false }, + treasuryShard: { value: input.treasuryShard ?? null, isWritable: true }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + // Resolve default values. + if (!accounts.systemProgram.value) { + accounts.systemProgram.value = + "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [ + getAccountMeta(accounts.payer), + getAccountMeta(accounts.wallet), + getAccountMeta(accounts.currentOwnerAuthority), + getAccountMeta(accounts.newOwnerAuthority), + getAccountMeta(accounts.systemProgram), + getAccountMeta(accounts.authorizerSigner), + getAccountMeta(accounts.config), + getAccountMeta(accounts.treasuryShard), + ], + data: getTransferOwnershipInstructionDataEncoder().encode( + args as TransferOwnershipInstructionDataArgs, + ), + programAddress, + } as TransferOwnershipInstruction< + TProgramAddress, + TAccountPayer, + TAccountWallet, + TAccountCurrentOwnerAuthority, + TAccountNewOwnerAuthority, + TAccountSystemProgram, + TAccountAuthorizerSigner, + TAccountConfig, + TAccountTreasuryShard + >); +} + +export type ParsedTransferOwnershipInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Transaction payer */ + payer: TAccountMetas[0]; + /** Wallet PDA */ + wallet: TAccountMetas[1]; + /** Current owner authority PDA */ + currentOwnerAuthority: TAccountMetas[2]; + /** New owner authority PDA to be created */ + newOwnerAuthority: TAccountMetas[3]; + /** System Program */ + systemProgram: TAccountMetas[4]; + /** Optional signer for Ed25519 authentication */ + authorizerSigner?: TAccountMetas[5] | undefined; + /** Config PDA */ + config: TAccountMetas[6]; + /** Treasury Shard PDA */ + treasuryShard: TAccountMetas[7]; + }; + data: TransferOwnershipInstructionData; +}; + +export function parseTransferOwnershipInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedTransferOwnershipInstruction { + if (instruction.accounts.length < 8) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + const getNextOptionalAccount = () => { + const accountMeta = getNextAccount(); + return accountMeta.address === LAZORKIT_PROGRAM_PROGRAM_ADDRESS + ? undefined + : accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + payer: getNextAccount(), + wallet: getNextAccount(), + currentOwnerAuthority: getNextAccount(), + newOwnerAuthority: getNextAccount(), + systemProgram: getNextAccount(), + authorizerSigner: getNextOptionalAccount(), + config: getNextAccount(), + treasuryShard: getNextAccount(), + }, + data: getTransferOwnershipInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/instructions/updateConfig.ts b/sdk/codama-client/src/generated/instructions/updateConfig.ts new file mode 100644 index 0000000..23d7cad --- /dev/null +++ b/sdk/codama-client/src/generated/instructions/updateConfig.ts @@ -0,0 +1,161 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type AccountMeta, + type AccountSignerMeta, + type Address, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + type Instruction, + type InstructionWithAccounts, + type InstructionWithData, + type ReadonlySignerAccount, + type ReadonlyUint8Array, + type TransactionSigner, + type WritableAccount, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../programs"; +import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; + +export const UPDATE_CONFIG_DISCRIMINATOR = 7; + +export function getUpdateConfigDiscriminatorBytes() { + return getU8Encoder().encode(UPDATE_CONFIG_DISCRIMINATOR); +} + +export type UpdateConfigInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountAdmin extends string | AccountMeta = string, + TAccountConfig extends string | AccountMeta = string, + TRemainingAccounts extends readonly AccountMeta[] = [], +> = Instruction & + InstructionWithData & + InstructionWithAccounts< + [ + TAccountAdmin extends string + ? ReadonlySignerAccount & + AccountSignerMeta + : TAccountAdmin, + TAccountConfig extends string + ? WritableAccount + : TAccountConfig, + ...TRemainingAccounts, + ] + >; + +export type UpdateConfigInstructionData = { discriminator: number }; + +export type UpdateConfigInstructionDataArgs = {}; + +export function getUpdateConfigInstructionDataEncoder(): FixedSizeEncoder { + return transformEncoder( + getStructEncoder([["discriminator", getU8Encoder()]]), + (value) => ({ ...value, discriminator: UPDATE_CONFIG_DISCRIMINATOR }), + ); +} + +export function getUpdateConfigInstructionDataDecoder(): FixedSizeDecoder { + return getStructDecoder([["discriminator", getU8Decoder()]]); +} + +export function getUpdateConfigInstructionDataCodec(): FixedSizeCodec< + UpdateConfigInstructionDataArgs, + UpdateConfigInstructionData +> { + return combineCodec( + getUpdateConfigInstructionDataEncoder(), + getUpdateConfigInstructionDataDecoder(), + ); +} + +export type UpdateConfigInput< + TAccountAdmin extends string = string, + TAccountConfig extends string = string, +> = { + /** Current contract admin */ + admin: TransactionSigner; + /** Config PDA */ + config: Address; +}; + +export function getUpdateConfigInstruction< + TAccountAdmin extends string, + TAccountConfig extends string, + TProgramAddress extends Address = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +>( + input: UpdateConfigInput, + config?: { programAddress?: TProgramAddress }, +): UpdateConfigInstruction { + // Program address. + const programAddress = + config?.programAddress ?? LAZORKIT_PROGRAM_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + admin: { value: input.admin ?? null, isWritable: false }, + config: { value: input.config ?? null, isWritable: true }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); + return Object.freeze({ + accounts: [getAccountMeta(accounts.admin), getAccountMeta(accounts.config)], + data: getUpdateConfigInstructionDataEncoder().encode({}), + programAddress, + } as UpdateConfigInstruction); +} + +export type ParsedUpdateConfigInstruction< + TProgram extends string = typeof LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[], +> = { + programAddress: Address; + accounts: { + /** Current contract admin */ + admin: TAccountMetas[0]; + /** Config PDA */ + config: TAccountMetas[1]; + }; + data: UpdateConfigInstructionData; +}; + +export function parseUpdateConfigInstruction< + TProgram extends string, + TAccountMetas extends readonly AccountMeta[], +>( + instruction: Instruction & + InstructionWithAccounts & + InstructionWithData, +): ParsedUpdateConfigInstruction { + if (instruction.accounts.length < 2) { + // TODO: Coded error. + throw new Error("Not enough accounts"); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { admin: getNextAccount(), config: getNextAccount() }, + data: getUpdateConfigInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/sdk/codama-client/src/generated/programs/index.ts b/sdk/codama-client/src/generated/programs/index.ts new file mode 100644 index 0000000..f42c168 --- /dev/null +++ b/sdk/codama-client/src/generated/programs/index.ts @@ -0,0 +1,9 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +export * from "./lazorkitProgram"; diff --git a/sdk/codama-client/src/generated/programs/lazorkitProgram.ts b/sdk/codama-client/src/generated/programs/lazorkitProgram.ts new file mode 100644 index 0000000..6176b1e --- /dev/null +++ b/sdk/codama-client/src/generated/programs/lazorkitProgram.ts @@ -0,0 +1,248 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + assertIsInstructionWithAccounts, + containsBytes, + getU8Encoder, + type Address, + type Instruction, + type InstructionWithData, + type ReadonlyUint8Array, +} from "@solana/kit"; +import { + parseAddAuthorityInstruction, + parseCloseSessionInstruction, + parseCloseWalletInstruction, + parseCreateSessionInstruction, + parseCreateWalletInstruction, + parseExecuteInstruction, + parseInitializeConfigInstruction, + parseInitTreasuryShardInstruction, + parseRemoveAuthorityInstruction, + parseSweepTreasuryInstruction, + parseTransferOwnershipInstruction, + parseUpdateConfigInstruction, + type ParsedAddAuthorityInstruction, + type ParsedCloseSessionInstruction, + type ParsedCloseWalletInstruction, + type ParsedCreateSessionInstruction, + type ParsedCreateWalletInstruction, + type ParsedExecuteInstruction, + type ParsedInitializeConfigInstruction, + type ParsedInitTreasuryShardInstruction, + type ParsedRemoveAuthorityInstruction, + type ParsedSweepTreasuryInstruction, + type ParsedTransferOwnershipInstruction, + type ParsedUpdateConfigInstruction, +} from "../instructions"; + +export const LAZORKIT_PROGRAM_PROGRAM_ADDRESS = + "DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2" as Address<"DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2">; + +export enum LazorkitProgramAccount { + WalletAccount, + AuthorityAccount, + SessionAccount, +} + +export enum LazorkitProgramInstruction { + CreateWallet, + AddAuthority, + RemoveAuthority, + TransferOwnership, + Execute, + CreateSession, + InitializeConfig, + UpdateConfig, + CloseSession, + CloseWallet, + SweepTreasury, + InitTreasuryShard, +} + +export function identifyLazorkitProgramInstruction( + instruction: { data: ReadonlyUint8Array } | ReadonlyUint8Array, +): LazorkitProgramInstruction { + const data = "data" in instruction ? instruction.data : instruction; + if (containsBytes(data, getU8Encoder().encode(0), 0)) { + return LazorkitProgramInstruction.CreateWallet; + } + if (containsBytes(data, getU8Encoder().encode(1), 0)) { + return LazorkitProgramInstruction.AddAuthority; + } + if (containsBytes(data, getU8Encoder().encode(2), 0)) { + return LazorkitProgramInstruction.RemoveAuthority; + } + if (containsBytes(data, getU8Encoder().encode(3), 0)) { + return LazorkitProgramInstruction.TransferOwnership; + } + if (containsBytes(data, getU8Encoder().encode(4), 0)) { + return LazorkitProgramInstruction.Execute; + } + if (containsBytes(data, getU8Encoder().encode(5), 0)) { + return LazorkitProgramInstruction.CreateSession; + } + if (containsBytes(data, getU8Encoder().encode(6), 0)) { + return LazorkitProgramInstruction.InitializeConfig; + } + if (containsBytes(data, getU8Encoder().encode(7), 0)) { + return LazorkitProgramInstruction.UpdateConfig; + } + if (containsBytes(data, getU8Encoder().encode(8), 0)) { + return LazorkitProgramInstruction.CloseSession; + } + if (containsBytes(data, getU8Encoder().encode(9), 0)) { + return LazorkitProgramInstruction.CloseWallet; + } + if (containsBytes(data, getU8Encoder().encode(10), 0)) { + return LazorkitProgramInstruction.SweepTreasury; + } + if (containsBytes(data, getU8Encoder().encode(11), 0)) { + return LazorkitProgramInstruction.InitTreasuryShard; + } + throw new Error( + "The provided instruction could not be identified as a lazorkitProgram instruction.", + ); +} + +export type ParsedLazorkitProgramInstruction< + TProgram extends string = "DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2", +> = + | ({ + instructionType: LazorkitProgramInstruction.CreateWallet; + } & ParsedCreateWalletInstruction) + | ({ + instructionType: LazorkitProgramInstruction.AddAuthority; + } & ParsedAddAuthorityInstruction) + | ({ + instructionType: LazorkitProgramInstruction.RemoveAuthority; + } & ParsedRemoveAuthorityInstruction) + | ({ + instructionType: LazorkitProgramInstruction.TransferOwnership; + } & ParsedTransferOwnershipInstruction) + | ({ + instructionType: LazorkitProgramInstruction.Execute; + } & ParsedExecuteInstruction) + | ({ + instructionType: LazorkitProgramInstruction.CreateSession; + } & ParsedCreateSessionInstruction) + | ({ + instructionType: LazorkitProgramInstruction.InitializeConfig; + } & ParsedInitializeConfigInstruction) + | ({ + instructionType: LazorkitProgramInstruction.UpdateConfig; + } & ParsedUpdateConfigInstruction) + | ({ + instructionType: LazorkitProgramInstruction.CloseSession; + } & ParsedCloseSessionInstruction) + | ({ + instructionType: LazorkitProgramInstruction.CloseWallet; + } & ParsedCloseWalletInstruction) + | ({ + instructionType: LazorkitProgramInstruction.SweepTreasury; + } & ParsedSweepTreasuryInstruction) + | ({ + instructionType: LazorkitProgramInstruction.InitTreasuryShard; + } & ParsedInitTreasuryShardInstruction); + +export function parseLazorkitProgramInstruction( + instruction: Instruction & InstructionWithData, +): ParsedLazorkitProgramInstruction { + const instructionType = identifyLazorkitProgramInstruction(instruction); + switch (instructionType) { + case LazorkitProgramInstruction.CreateWallet: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.CreateWallet, + ...parseCreateWalletInstruction(instruction), + }; + } + case LazorkitProgramInstruction.AddAuthority: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.AddAuthority, + ...parseAddAuthorityInstruction(instruction), + }; + } + case LazorkitProgramInstruction.RemoveAuthority: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.RemoveAuthority, + ...parseRemoveAuthorityInstruction(instruction), + }; + } + case LazorkitProgramInstruction.TransferOwnership: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.TransferOwnership, + ...parseTransferOwnershipInstruction(instruction), + }; + } + case LazorkitProgramInstruction.Execute: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.Execute, + ...parseExecuteInstruction(instruction), + }; + } + case LazorkitProgramInstruction.CreateSession: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.CreateSession, + ...parseCreateSessionInstruction(instruction), + }; + } + case LazorkitProgramInstruction.InitializeConfig: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.InitializeConfig, + ...parseInitializeConfigInstruction(instruction), + }; + } + case LazorkitProgramInstruction.UpdateConfig: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.UpdateConfig, + ...parseUpdateConfigInstruction(instruction), + }; + } + case LazorkitProgramInstruction.CloseSession: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.CloseSession, + ...parseCloseSessionInstruction(instruction), + }; + } + case LazorkitProgramInstruction.CloseWallet: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.CloseWallet, + ...parseCloseWalletInstruction(instruction), + }; + } + case LazorkitProgramInstruction.SweepTreasury: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.SweepTreasury, + ...parseSweepTreasuryInstruction(instruction), + }; + } + case LazorkitProgramInstruction.InitTreasuryShard: { + assertIsInstructionWithAccounts(instruction); + return { + instructionType: LazorkitProgramInstruction.InitTreasuryShard, + ...parseInitTreasuryShardInstruction(instruction), + }; + } + default: + throw new Error( + `Unrecognized instruction type: ${instructionType as string}`, + ); + } +} diff --git a/sdk/codama-client/src/generated/shared/index.ts b/sdk/codama-client/src/generated/shared/index.ts new file mode 100644 index 0000000..634232f --- /dev/null +++ b/sdk/codama-client/src/generated/shared/index.ts @@ -0,0 +1,164 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + AccountRole, + isProgramDerivedAddress, + isTransactionSigner as kitIsTransactionSigner, + type AccountMeta, + type AccountSignerMeta, + type Address, + type ProgramDerivedAddress, + type TransactionSigner, + upgradeRoleToSigner, +} from "@solana/kit"; + +/** + * Asserts that the given value is not null or undefined. + * @internal + */ +export function expectSome(value: T | null | undefined): T { + if (value === null || value === undefined) { + throw new Error("Expected a value but received null or undefined."); + } + return value; +} + +/** + * Asserts that the given value is a PublicKey. + * @internal + */ +export function expectAddress( + value: + | Address + | ProgramDerivedAddress + | TransactionSigner + | null + | undefined, +): Address { + if (!value) { + throw new Error("Expected a Address."); + } + if (typeof value === "object" && "address" in value) { + return value.address; + } + if (Array.isArray(value)) { + return value[0] as Address; + } + return value as Address; +} + +/** + * Asserts that the given value is a PDA. + * @internal + */ +export function expectProgramDerivedAddress( + value: + | Address + | ProgramDerivedAddress + | TransactionSigner + | null + | undefined, +): ProgramDerivedAddress { + if (!value || !Array.isArray(value) || !isProgramDerivedAddress(value)) { + throw new Error("Expected a ProgramDerivedAddress."); + } + return value; +} + +/** + * Asserts that the given value is a TransactionSigner. + * @internal + */ +export function expectTransactionSigner( + value: + | Address + | ProgramDerivedAddress + | TransactionSigner + | null + | undefined, +): TransactionSigner { + if (!value || !isTransactionSigner(value)) { + throw new Error("Expected a TransactionSigner."); + } + return value; +} + +/** + * Defines an instruction account to resolve. + * @internal + */ +export type ResolvedAccount< + T extends string = string, + U extends + | Address + | ProgramDerivedAddress + | TransactionSigner + | null = + | Address + | ProgramDerivedAddress + | TransactionSigner + | null, +> = { + isWritable: boolean; + value: U; +}; + +/** + * Defines an instruction that stores additional bytes on-chain. + * @internal + */ +export type InstructionWithByteDelta = { + byteDelta: number; +}; + +/** + * Get account metas and signers from resolved accounts. + * @internal + */ +export function getAccountMetaFactory( + programAddress: Address, + optionalAccountStrategy: "omitted" | "programId", +) { + return ( + account: ResolvedAccount, + ): AccountMeta | AccountSignerMeta | undefined => { + if (!account.value) { + if (optionalAccountStrategy === "omitted") return; + return Object.freeze({ + address: programAddress, + role: AccountRole.READONLY, + }); + } + + const writableRole = account.isWritable + ? AccountRole.WRITABLE + : AccountRole.READONLY; + return Object.freeze({ + address: expectAddress(account.value), + role: isTransactionSigner(account.value) + ? upgradeRoleToSigner(writableRole) + : writableRole, + ...(isTransactionSigner(account.value) ? { signer: account.value } : {}), + }); + }; +} + +export function isTransactionSigner( + value: + | Address + | ProgramDerivedAddress + | TransactionSigner, +): value is TransactionSigner { + return ( + !!value && + typeof value === "object" && + "address" in value && + kitIsTransactionSigner(value) + ); +} diff --git a/sdk/codama-client/src/generated/types/authorityType.ts b/sdk/codama-client/src/generated/types/authorityType.ts new file mode 100644 index 0000000..5a14cbf --- /dev/null +++ b/sdk/codama-client/src/generated/types/authorityType.ts @@ -0,0 +1,38 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + getEnumDecoder, + getEnumEncoder, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, +} from "@solana/kit"; + +export enum AuthorityType { + Ed25519, + Secp256r1, +} + +export type AuthorityTypeArgs = AuthorityType; + +export function getAuthorityTypeEncoder(): FixedSizeEncoder { + return getEnumEncoder(AuthorityType); +} + +export function getAuthorityTypeDecoder(): FixedSizeDecoder { + return getEnumDecoder(AuthorityType); +} + +export function getAuthorityTypeCodec(): FixedSizeCodec< + AuthorityTypeArgs, + AuthorityType +> { + return combineCodec(getAuthorityTypeEncoder(), getAuthorityTypeDecoder()); +} diff --git a/sdk/codama-client/src/generated/types/index.ts b/sdk/codama-client/src/generated/types/index.ts new file mode 100644 index 0000000..584da40 --- /dev/null +++ b/sdk/codama-client/src/generated/types/index.ts @@ -0,0 +1,10 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +export * from "./authorityType"; +export * from "./role"; diff --git a/sdk/codama-client/src/generated/types/role.ts b/sdk/codama-client/src/generated/types/role.ts new file mode 100644 index 0000000..7b4c274 --- /dev/null +++ b/sdk/codama-client/src/generated/types/role.ts @@ -0,0 +1,36 @@ +/** + * This code was AUTOGENERATED using the Codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun Codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + getEnumDecoder, + getEnumEncoder, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, +} from "@solana/kit"; + +export enum Role { + Owner, + Admin, + Spender, +} + +export type RoleArgs = Role; + +export function getRoleEncoder(): FixedSizeEncoder { + return getEnumEncoder(Role); +} + +export function getRoleDecoder(): FixedSizeDecoder { + return getEnumDecoder(Role); +} + +export function getRoleCodec(): FixedSizeCodec { + return combineCodec(getRoleEncoder(), getRoleDecoder()); +} diff --git a/sdk/codama-client/src/index.ts b/sdk/codama-client/src/index.ts new file mode 100644 index 0000000..df5587d --- /dev/null +++ b/sdk/codama-client/src/index.ts @@ -0,0 +1,16 @@ +/** + * LazorKit TypeScript SDK + * + * Auto-generated instruction builders, account decoders, error types, and enums + * from the Shank IDL via Codama, plus handwritten utilities for PDA derivation, + * compact instruction packing, and the Execute instruction builder. + */ + +// Auto-generated from Codama (instructions, accounts, errors, types, program) +export * from "./generated"; + +// Handwritten utilities +export * from "./utils/pdas"; +export * from "./utils/packing"; +export { buildExecuteInstruction, type ExecuteInstructionBuilderParams } from "./utils/execute"; +export { LazorClient } from "./utils/client"; diff --git a/sdk/codama-client/src/utils/client.ts b/sdk/codama-client/src/utils/client.ts new file mode 100644 index 0000000..0674763 --- /dev/null +++ b/sdk/codama-client/src/utils/client.ts @@ -0,0 +1,726 @@ +/** + * LazorClient — Thin compatibility wrapper over manual instruction encoding. + * + * IMPLEMENTATION NOTE: + * We manually encode instruction data here because the generated Codama encoders + * use `u32` length prefixes for `bytes` fields, but the LazorKit contract expects + * raw fixed-size byte arrays (C-struct style) with specific alignment padding. + * + * Using the generated `get*Instruction` functions causes mismatched instruction data, + * leading to "InvalidInstructionData" errors in the contract. + */ + +import { + getCreateWalletInstruction, + getAddAuthorityInstruction, + getRemoveAuthorityInstruction, + getTransferOwnershipInstruction, + getCreateSessionInstruction, + getCloseSessionInstruction, + getCloseWalletInstruction, + getSweepTreasuryInstruction +} from "../generated"; + +import { + getAddressEncoder, + getAddressDecoder, + type Address, + type TransactionSigner, + type ReadonlyUint8Array, + type AccountMeta, + type AccountSignerMeta, + type ProgramDerivedAddress, + AccountRole, + upgradeRoleToSigner, + getBase58Decoder, +} from "@solana/kit"; + +import { + LAZORKIT_PROGRAM_PROGRAM_ADDRESS, +} from "../generated"; + +import { + fetchWalletAccount, + fetchAuthorityAccount, + fetchSessionAccount, + type WalletAccount, + type AuthorityAccount, + type SessionAccount, +} from "../generated/accounts"; + +import { packCompactInstructions, type CompactInstruction } from "./packing"; +import { findAuthorityPda } from "./pdas"; + +// Valid address input types +type AddressLike = Address | ProgramDerivedAddress; + +// Helper to resolve AddressLike to Address string +function resolveAddress(addr: AddressLike | TransactionSigner): Address { + if (Array.isArray(addr)) return addr[0]; + if (typeof addr === 'object' && 'address' in addr) return (addr as TransactionSigner).address; + return addr as Address; +} + +// Helper to create account meta +function meta( + address: AddressLike | TransactionSigner, + role: "r" | "w" | "rs" | "ws" | "s", +): AccountMeta | AccountSignerMeta { + const addr = resolveAddress(address); + const isSignerObj = typeof address === 'object' && ('signTransaction' in address || 'signTransactions' in address) && !Array.isArray(address); + + // Determine base role (Readonly or Writable) + let accountRole = role.includes('w') ? AccountRole.WRITABLE : AccountRole.READONLY; + + // Upgrade to signer if needed + // We strictly follow the requested role. + // If 's' is in role, we force it to be a signer role. + if (role.includes('s')) { + accountRole = upgradeRoleToSigner(accountRole); + } + + return { + address: addr, + role: accountRole, + ...(role.includes('s') && isSignerObj ? { signer: address as TransactionSigner } : {}), + } as any; +} + +export class LazorClient { + constructor(private rpc: any) { } + + private getAuthPayload(authType: number, authPubkey: ReadonlyUint8Array, credentialHash: ReadonlyUint8Array): Uint8Array { + if (authType === 1) { // Secp256r1 + // 32 bytes hash + 33 bytes key + const payload = new Uint8Array(65); + payload.set(credentialHash, 0); + payload.set(authPubkey, 32); + return payload; + } else { // Ed25519 + // 32 bytes key + return new Uint8Array(authPubkey); + } + } + + // Helper to strip the 4-byte length prefix added by Codama for 'bytes' type + private stripPayloadPrefix(data: ReadonlyUint8Array, payloadOffset: number): Uint8Array { + // [Head][Prefix(4)][Payload] -> [Head][Payload] + // data.length = payloadOffset + 4 + payloadLen + const payloadLen = data.length - payloadOffset - 4; + if (payloadLen < 0) return new Uint8Array(data); // Should not happen if correctly generated + + const fixed = new Uint8Array(data.length - 4); + fixed.set(data.slice(0, payloadOffset), 0); + fixed.set(data.slice(payloadOffset + 4), payloadOffset); + return fixed; + } + + createWallet(params: { + payer: TransactionSigner; + wallet: AddressLike; + vault: AddressLike; + authority: AddressLike; + config: AddressLike; + treasuryShard: AddressLike; + userSeed: ReadonlyUint8Array; + authType: number; + authBump?: number; + authPubkey: ReadonlyUint8Array; + credentialHash: ReadonlyUint8Array; + }) { + const authBump = params.authBump || 0; + const padding = new Uint8Array(6).fill(0); + const payload = this.getAuthPayload(params.authType, params.authPubkey, params.credentialHash); + + const instruction = getCreateWalletInstruction({ + payer: params.payer, + wallet: resolveAddress(params.wallet), + vault: resolveAddress(params.vault), + authority: resolveAddress(params.authority), + config: resolveAddress(params.config), + treasuryShard: resolveAddress(params.treasuryShard), + userSeed: params.userSeed, + authType: params.authType, + authBump, + padding, + payload + }); + + // Strip prefix at offset 41 (1 Disc + 32 Seed + 1 Type + 1 Bump + 6 Pad) + const data = this.stripPayloadPrefix(instruction.data, 41); + + const accounts = [ + meta(params.payer, "ws"), + meta(params.wallet, "w"), + meta(params.vault, "w"), + meta(params.authority, "w"), + meta("11111111111111111111111111111111" as Address, "r"), // SystemProgram + meta("SysvarRent111111111111111111111111111111111" as Address, "r"), // Rent + meta(params.config, "r"), + meta(params.treasuryShard, "w"), + ]; + + return { + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + accounts, + data + }; + } + + addAuthority(params: { + payer: TransactionSigner; + wallet: AddressLike; + adminAuthority: AddressLike; + newAuthority: AddressLike; + config: AddressLike; + treasuryShard: AddressLike; + authType: number; + newRole: number; + authPubkey: ReadonlyUint8Array; + credentialHash: ReadonlyUint8Array; + authorizerSigner?: TransactionSigner; + }) { + const padding = new Uint8Array(6).fill(0); + const payload = this.getAuthPayload(params.authType, params.authPubkey, params.credentialHash); + + const instruction = getAddAuthorityInstruction({ + payer: params.payer, + wallet: resolveAddress(params.wallet), + adminAuthority: resolveAddress(params.adminAuthority), + newAuthority: resolveAddress(params.newAuthority), + config: resolveAddress(params.config), + treasuryShard: resolveAddress(params.treasuryShard), + newType: params.authType, + newRole: params.newRole, + padding, + payload + }); + + // Strip prefix at offset 9 (1 Disc + 1 Type + 1 Role + 6 Pad) + const data = this.stripPayloadPrefix(instruction.data, 9); + + const accounts = [ + meta(params.payer, "ws"), + meta(params.wallet, "r"), + meta(params.adminAuthority, "w"), // Secp needs writable + meta(params.newAuthority, "w"), + meta("11111111111111111111111111111111" as Address, "r"), // System + ]; + + // Config + Treasury must come right after system_program (iterator order) + accounts.push(meta(params.config, "r")); + accounts.push(meta(params.treasuryShard, "w")); + + // Optional signer goes AFTER config/treasury (trailing account) + if (params.authorizerSigner) { + accounts.push(meta(params.authorizerSigner, "s")); + } + + return { + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + accounts, + data + }; + } + + removeAuthority(params: { + payer: TransactionSigner; + wallet: AddressLike; + adminAuthority: AddressLike; + targetAuthority: AddressLike; + refundDestination: AddressLike; + config: AddressLike; + treasuryShard: AddressLike; + authorizerSigner?: TransactionSigner; + }) { + const instruction = getRemoveAuthorityInstruction({ + payer: params.payer, + wallet: resolveAddress(params.wallet), + adminAuthority: resolveAddress(params.adminAuthority), + targetAuthority: resolveAddress(params.targetAuthority), + refundDestination: resolveAddress(params.refundDestination), + config: resolveAddress(params.config), + treasuryShard: resolveAddress(params.treasuryShard), + }); + + const accounts = [ + meta(params.payer, "ws"), + meta(params.wallet, "r"), + meta(params.adminAuthority, "w"), // Secp needs writable + meta(params.targetAuthority, "w"), // To close it + meta(params.refundDestination, "w"), // To receive rent + meta("11111111111111111111111111111111" as Address, "r"), // System + ]; + + // Config + Treasury must come right after system_program (iterator order) + accounts.push(meta(params.config, "r")); + accounts.push(meta(params.treasuryShard, "w")); + + // Optional signer goes AFTER config/treasury (trailing account) + if (params.authorizerSigner) { + accounts.push(meta(params.authorizerSigner, "s")); + } + + return { + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + accounts, + data: instruction.data + }; + } + + transferOwnership(params: { + payer: TransactionSigner; + wallet: AddressLike; + currentOwnerAuthority: AddressLike; + newOwnerAuthority: AddressLike; + config: AddressLike; + treasuryShard: AddressLike; + authType: number; + authPubkey: ReadonlyUint8Array; + credentialHash: ReadonlyUint8Array; + authorizerSigner?: TransactionSigner; + }) { + const payload = this.getAuthPayload(params.authType, params.authPubkey, params.credentialHash); + + const instruction = getTransferOwnershipInstruction({ + payer: params.payer, + wallet: resolveAddress(params.wallet), + currentOwnerAuthority: resolveAddress(params.currentOwnerAuthority), + newOwnerAuthority: resolveAddress(params.newOwnerAuthority), + config: resolveAddress(params.config), + treasuryShard: resolveAddress(params.treasuryShard), + newType: params.authType, + payload + }); + + // Strip prefix at offset 2 (1 Disc + 1 Type) + const data = this.stripPayloadPrefix(instruction.data, 2); + + const accounts = [ + meta(params.payer, "ws"), + meta(params.wallet, "r"), + meta(params.currentOwnerAuthority, "w"), // Secp needs writable + meta(params.newOwnerAuthority, "w"), + meta("11111111111111111111111111111111" as Address, "r"), + meta("SysvarRent111111111111111111111111111111111" as Address, "r"), // Rent + ]; + + // Config + Treasury must come right after rent sysvar (iterator order) + accounts.push(meta(params.config, "r")); + accounts.push(meta(params.treasuryShard, "w")); + + // Optional signer goes AFTER config/treasury (trailing account) + if (params.authorizerSigner) { + accounts.push(meta(params.authorizerSigner, "s")); + } + + return { + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + accounts, + data + }; + } + + execute(params: { + payer: TransactionSigner; + wallet: AddressLike; + authority: AddressLike; + vault: AddressLike; + config: AddressLike; + treasuryShard: AddressLike; + packedInstructions: Uint8Array; + authorizerSigner?: TransactionSigner; + sysvarInstructions?: AddressLike; + }) { + // Layout: [4, ...packedInstructions] + const totalSize = 1 + params.packedInstructions.length; + const data = new Uint8Array(totalSize); + data[0] = 4; + data.set(params.packedInstructions, 1); + + const finalAccounts = [ + meta(params.payer, "ws"), + meta(params.wallet, "r"), + meta(params.authority, "w"), // Secp needs writable + meta(params.vault, "w"), // Vault is signer (role 4 in compact), but parsed as readonly in instruction accounts + meta(params.config, "r"), + meta(params.treasuryShard, "w"), + meta("11111111111111111111111111111111" as Address, "r"), // System + ]; + + if (params.sysvarInstructions) { + finalAccounts.push(meta(params.sysvarInstructions, "r")); + } + + if (params.authorizerSigner) { + finalAccounts.push(meta(params.authorizerSigner, "s")); + } + + return { + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + accounts: finalAccounts, + data + }; + } + + buildExecute(params: { + payer: TransactionSigner; + wallet: AddressLike; + authority: AddressLike; + vault: AddressLike; + config: AddressLike; + treasuryShard: AddressLike; + innerInstructions: any[]; + authorizerSigner?: TransactionSigner; + signature?: Uint8Array; + sysvarInstructions?: AddressLike; + }) { + // Collect all unique accounts from inner instructions + const accountMap = new Map(); + + type AccInfo = { address: Address; role: AccountRole; signer?: TransactionSigner }; + + // Define role constants from kit + const READONLY_SIGNER = upgradeRoleToSigner(AccountRole.READONLY); + const WRITABLE_SIGNER = upgradeRoleToSigner(AccountRole.WRITABLE); + + const vaultAddr = resolveAddress(params.vault); + const walletAddr = resolveAddress(params.wallet); + + const allAccounts: AccInfo[] = [ + { address: params.payer.address, role: WRITABLE_SIGNER, signer: params.payer }, + { address: walletAddr, role: AccountRole.READONLY }, + { address: resolveAddress(params.authority), role: AccountRole.WRITABLE }, + { address: vaultAddr, role: AccountRole.READONLY }, + { address: resolveAddress(params.config), role: AccountRole.READONLY }, + { address: resolveAddress(params.treasuryShard), role: AccountRole.WRITABLE }, + { address: "11111111111111111111111111111111" as Address, role: AccountRole.READONLY }, + ]; + + // Helper to check standard accounts + const addAccount = (addr: Address, isSigner: boolean, isWritable: boolean, signerObj?: TransactionSigner) => { + // Protect PDAs from being marked as signers + if (addr === vaultAddr || addr === walletAddr) { + isSigner = false; + } + + if (!accountMap.has(addr)) { + accountMap.set(addr, allAccounts.length); + let role = isWritable ? AccountRole.WRITABLE : AccountRole.READONLY; + if (isSigner) role = upgradeRoleToSigner(role); + + allAccounts.push({ address: addr, role, signer: signerObj }); + } else { + // upgrade + const idx = accountMap.get(addr)!; + const current = allAccounts[idx]; + let role = current.role; + + // If current is readonly but new is writable, upgrade + if (isWritable && (role === AccountRole.READONLY || role === READONLY_SIGNER)) { + role = (role === READONLY_SIGNER) ? WRITABLE_SIGNER : AccountRole.WRITABLE; + } + + // If new is signer, upgrade (but PDAs are protected) + if (isSigner && (role === AccountRole.READONLY || role === AccountRole.WRITABLE)) { + role = upgradeRoleToSigner(role); + } + + allAccounts[idx].role = role; + if (signerObj && !allAccounts[idx].signer) { + allAccounts[idx].signer = signerObj; + } + } + return accountMap.get(addr)!; + }; + + // initialize map with standard accounts + allAccounts.forEach((a, i) => accountMap.set(a.address, i)); + + const compactIxs: CompactInstruction[] = []; + + for (const ix of params.innerInstructions) { + const programIdIndex = addAccount(resolveAddress(ix.programAddress), false, false); + + const accountIndexes: number[] = []; + for (const acc of (ix.accounts || [])) { + // Handle various role input formats safely + let isSigner = !!acc.isSigner; + let isWritable = !!acc.isWritable; + + if (typeof acc.role === 'string') { + if (acc.role.includes('s')) isSigner = true; + if (acc.role.includes('w')) isWritable = true; + } else if (typeof acc.role === 'number') { + if (acc.role === READONLY_SIGNER || acc.role === WRITABLE_SIGNER) isSigner = true; + if (acc.role === AccountRole.WRITABLE || acc.role === WRITABLE_SIGNER) isWritable = true; + } + + const idx = addAccount( + resolveAddress(acc.address), + isSigner, + isWritable, + ); + accountIndexes.push(idx); + } + + compactIxs.push({ + programIdIndex, + accountIndexes, + data: ix.data instanceof Uint8Array ? ix.data : new Uint8Array(ix.data), + }); + } + + const packed = packCompactInstructions(compactIxs); + + const sig = params.signature; + const totalSize = 1 + packed.length + (sig?.length || 0); + const data = new Uint8Array(totalSize); + data[0] = 4; // Execute + data.set(packed, 1); + if (sig) data.set(sig, 1 + packed.length); + + if (params.sysvarInstructions) { + addAccount(resolveAddress(params.sysvarInstructions), false, false); + } + + if (params.authorizerSigner) { + addAccount(params.authorizerSigner.address, true, false, params.authorizerSigner); + } + + // Convert allAccounts to AccountMeta + const accounts: (AccountMeta | AccountSignerMeta)[] = allAccounts.map(a => ({ + address: a.address, + role: a.role, + ...(a.signer ? { signer: a.signer } : {}) + })); + + return { + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + accounts, + data, + }; + } + + createSession(params: { + payer: TransactionSigner; + wallet: AddressLike; + adminAuthority: AddressLike; + session: AddressLike; + config: AddressLike; + treasuryShard: AddressLike; + sessionKey: ReadonlyUint8Array; + expiresAt: bigint | number; + authorizerSigner?: TransactionSigner; + }) { + const instruction = getCreateSessionInstruction({ + payer: params.payer, + wallet: resolveAddress(params.wallet), + adminAuthority: resolveAddress(params.adminAuthority), + session: resolveAddress(params.session), + config: resolveAddress(params.config), + treasuryShard: resolveAddress(params.treasuryShard), + sessionKey: params.sessionKey, + expiresAt: BigInt(params.expiresAt) + }); + + const accounts = [ + meta(params.payer, "ws"), + meta(params.wallet, "r"), + meta(params.adminAuthority, "w"), // Secp needs writable + meta(params.session, "w"), + meta("11111111111111111111111111111111" as Address, "r"), // System + meta("SysvarRent111111111111111111111111111111111" as Address, "r"), // Rent + ]; + + // Config + Treasury must come right after rent sysvar (iterator order) + accounts.push(meta(params.config, "r")); + accounts.push(meta(params.treasuryShard, "w")); + + // Optional signer goes AFTER config/treasury (trailing account) + if (params.authorizerSigner) { + accounts.push(meta(params.authorizerSigner, "s")); + } + + return { + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + accounts, + data: instruction.data + }; + } + + async getWallet(address: AddressLike): Promise { + const account = await fetchWalletAccount(this.rpc, resolveAddress(address)); + return account.data; + } + + async getAuthority(address: AddressLike): Promise { + const account = await fetchAuthorityAccount(this.rpc, resolveAddress(address)); + return account.data; + } + + async getSession(address: AddressLike): Promise { + const account = await fetchSessionAccount(this.rpc, resolveAddress(address)); + return account.data; + } + + sweepTreasury(params: { + admin: TransactionSigner; + config: AddressLike; + treasuryShard: AddressLike; + destination: AddressLike; + shardId: number; + }) { + return getSweepTreasuryInstruction({ + admin: params.admin, + config: resolveAddress(params.config), + treasuryShard: resolveAddress(params.treasuryShard), + destination: resolveAddress(params.destination), + shardId: params.shardId, + }); + } + + closeWallet(params: { + payer: TransactionSigner; + wallet: AddressLike; + vault: AddressLike; + ownerAuthority: AddressLike; + destination: AddressLike; + ownerSigner?: TransactionSigner; + sysvarInstructions?: AddressLike; + }) { + const instruction = getCloseWalletInstruction({ + payer: params.payer, + wallet: resolveAddress(params.wallet), + vault: resolveAddress(params.vault), + ownerAuthority: resolveAddress(params.ownerAuthority), + destination: resolveAddress(params.destination), + ownerSigner: params.ownerSigner, + sysvarInstructions: params.sysvarInstructions ? resolveAddress(params.sysvarInstructions) : undefined, + }); + + const accounts = [ + meta(params.payer, "ws"), + meta(params.wallet, "w"), + meta(params.vault, "w"), + meta(params.ownerAuthority, "r"), + meta(params.destination, "w"), + ]; + + if (params.ownerSigner) { + accounts.push(meta(params.ownerSigner, "s")); + } + + if (params.sysvarInstructions) { + accounts.push(meta(params.sysvarInstructions, "r")); + } + + // Always include System Program for the drain operation (Step 5) + accounts.push(meta("11111111111111111111111111111111" as Address, "r")); + + return { + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + accounts, + data: instruction.data + }; + } + + closeSession(params: { + payer: TransactionSigner; + wallet: AddressLike; + session: AddressLike; + config: AddressLike; + authorizer?: AddressLike; + authorizerSigner?: TransactionSigner; + sysvarInstructions?: AddressLike; + }) { + const instruction = getCloseSessionInstruction({ + payer: params.payer, + wallet: resolveAddress(params.wallet), + session: resolveAddress(params.session), + config: resolveAddress(params.config), + authorizer: params.authorizer ? resolveAddress(params.authorizer) : undefined, + authorizerSigner: params.authorizerSigner, + sysvarInstructions: params.sysvarInstructions ? resolveAddress(params.sysvarInstructions) : undefined, + }); + + const accounts = [ + meta(params.payer, "ws"), + meta(params.wallet, "r"), + meta(params.session, "w"), + meta(params.config, "r"), + ]; + + if (params.authorizer) { + accounts.push(meta(params.authorizer, "r")); + } + + if (params.authorizerSigner) { + accounts.push(meta(params.authorizerSigner, "s")); + } + + if (params.sysvarInstructions) { + accounts.push(meta(params.sysvarInstructions, "r")); + } + + // Include System Program just in case + accounts.push(meta("11111111111111111111111111111111" as Address, "r")); + + return { + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + accounts, + data: instruction.data + }; + } + + async getAuthorityByPublicKey(walletAddress: AddressLike, pubkey: Address | Uint8Array): Promise { + const pubkeyBytes = typeof pubkey === 'string' ? Uint8Array.from(getAddressEncoder().encode(pubkey)) : pubkey; + const [pda] = await findAuthorityPda(resolveAddress(walletAddress), pubkeyBytes); + try { + return await this.getAuthority(pda); + } catch { + return null; + } + } + + async getAuthorityByCredentialId(walletAddress: AddressLike, credentialIdHash: Uint8Array): Promise { + const [pda] = await findAuthorityPda(resolveAddress(walletAddress), credentialIdHash); + try { + return await this.getAuthority(pda); + } catch { + return null; + } + } + + /** + * Globally finds all Authority accounts belonging to this program that match a credentialIdHash. + * Useful for recovering a wallet when only the Passkey (Credential ID) is known. + */ + async findAllAuthoritiesByCredentialId(credentialIdHash: Uint8Array): Promise<{ authority: Address; wallet: Address; role: number; authorityType: number }[]> { + const base58Decoder = getBase58Decoder(); + const base58Hash = base58Decoder.decode(credentialIdHash); + const accounts = await this.rpc.getProgramAccounts(LAZORKIT_PROGRAM_PROGRAM_ADDRESS, { + encoding: 'base64', + filters: [ + { memcmp: { offset: 0, bytes: base58Decoder.decode(new Uint8Array([2])) } }, // Discriminator: Authority (2) + { memcmp: { offset: 48, bytes: base58Hash } } // credentialIdHash starts at header offset (48) + ] + }).send(); + + return accounts.map((acc: any) => { + const data = Buffer.from(acc.account.data[0], 'base64'); + // Offset 3 (Bump), 5 (Padding - skip 3), 8 (Counter - skip 8), 16 (Wallet - 32 bytes) + // Wallet starts at offset 16 of the data + const role = data[2]; + const authorityType = data[1]; + const wallet = getAddressDecoder().decode(new Uint8Array(data.slice(16, 48))); + + return { + authority: acc.pubkey as Address, + wallet, + role, + authorityType + }; + }); + } +} diff --git a/sdk/codama-client/src/utils/execute.ts b/sdk/codama-client/src/utils/execute.ts new file mode 100644 index 0000000..09c4898 --- /dev/null +++ b/sdk/codama-client/src/utils/execute.ts @@ -0,0 +1,138 @@ +/** + * High-level Execute instruction builder. + * + * Takes standard Solana instructions, deduplicates accounts, + * maps them to compact format, and returns a single Execute instruction. + */ + +import { + type Address, + type Instruction, + type TransactionSigner, + AccountRole, +} from "@solana/kit"; +import { + LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + EXECUTE_DISCRIMINATOR, +} from "../generated"; +import { type CompactInstruction, packCompactInstructions } from "./packing"; + +export interface ExecuteInstructionBuilderParams { + /** Transaction fee payer */ + payer: TransactionSigner; + /** Wallet PDA address */ + wallet: Address; + /** Authority or Session PDA address */ + authority: Address; + /** Vault PDA address */ + vault: Address; + /** Inner instructions to execute (e.g. SPL Token transfers) */ + innerInstructions: Instruction[]; + /** Required for Secp256r1 authentication */ + sysvarInstructions?: Address; + /** Ed25519 signer */ + authorizerSigner?: TransactionSigner; + /** Secp256r1 signature bytes */ + signature?: Uint8Array; +} + +/** + * Builds a complex Execute instruction from standard Solana instructions. + * + * This function: + * 1. Extracts all accounts from inner instructions + * 2. Deduplicates and merges account roles (promoting to highest privilege) + * 3. Maps inner instructions to compact format + * 4. Returns a standard LazorKit Execute instruction + */ +export function buildExecuteInstruction(params: ExecuteInstructionBuilderParams): Instruction { + const { payer, wallet, authority, vault, innerInstructions, sysvarInstructions, authorizerSigner, signature } = params; + + // Base accounts always present in Execute + const baseAccounts: Address[] = [ + payer.address, + wallet, + authority, + vault + ]; + + const accountMap = new Map(); + baseAccounts.forEach((addr, idx) => accountMap.set(addr, idx)); + + const extraAccounts: Address[] = []; + const accountRoles = new Map(); + accountRoles.set(payer.address, AccountRole.WRITABLE_SIGNER); + accountRoles.set(wallet, AccountRole.READONLY); + accountRoles.set(authority, AccountRole.WRITABLE); + accountRoles.set(vault, AccountRole.WRITABLE); + + const compactIxs: CompactInstruction[] = []; + + for (const ix of innerInstructions) { + // Ensure program ID is in the account list + if (!accountMap.has(ix.programAddress)) { + accountMap.set(ix.programAddress, baseAccounts.length + extraAccounts.length); + extraAccounts.push(ix.programAddress); + accountRoles.set(ix.programAddress, AccountRole.READONLY); + } + const programIdIndex = accountMap.get(ix.programAddress)!; + + // Map all instruction accounts + const accountIndexes: number[] = []; + const accountsToMap = ix.accounts || []; + for (const acc of accountsToMap) { + if (!accountMap.has(acc.address)) { + accountMap.set(acc.address, baseAccounts.length + extraAccounts.length); + extraAccounts.push(acc.address); + } + accountIndexes.push(accountMap.get(acc.address)!); + + // Promote account role to highest privilege + const currentRole = accountRoles.get(acc.address) || AccountRole.READONLY; + if (acc.role > currentRole) { + accountRoles.set(acc.address, acc.role); + } + } + + compactIxs.push({ + programIdIndex, + accountIndexes, + data: ix.data as Uint8Array, + }); + } + + const packedInstructions = packCompactInstructions(compactIxs); + + // Build the accounts list + const accounts: any[] = [ + { address: payer.address, role: AccountRole.WRITABLE_SIGNER, signer: payer }, + { address: wallet, role: AccountRole.READONLY }, + { address: authority, role: AccountRole.WRITABLE }, + { address: vault, role: AccountRole.WRITABLE }, + ...extraAccounts.map(addr => ({ + address: addr, + role: accountRoles.get(addr)!, + })), + ]; + + if (sysvarInstructions) { + accounts.push({ address: sysvarInstructions, role: AccountRole.READONLY }); + } + if (authorizerSigner) { + accounts.push({ address: authorizerSigner.address, role: AccountRole.READONLY_SIGNER, signer: authorizerSigner }); + } + + // Build instruction data: [discriminator(1)] [packed_instructions] [signature?] + const finalData = new Uint8Array(1 + packedInstructions.length + (signature?.length || 0)); + finalData[0] = EXECUTE_DISCRIMINATOR; + finalData.set(packedInstructions, 1); + if (signature) { + finalData.set(signature, 1 + packedInstructions.length); + } + + return { + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + accounts, + data: finalData, + }; +} diff --git a/sdk/codama-client/src/utils/packing.ts b/sdk/codama-client/src/utils/packing.ts new file mode 100644 index 0000000..6a5bd56 --- /dev/null +++ b/sdk/codama-client/src/utils/packing.ts @@ -0,0 +1,65 @@ +/** + * Utility to pack instructions into the compact format expected by LazorKit's Execute instruction. + * + * Format: + * [num_instructions: u8] + * for each instruction: + * [program_id_index: u8] + * [num_accounts: u8] + * [account_indexes: u8[]] + * [data_len: u16 LE] + * [data: u8[]] + */ + +export interface CompactInstruction { + programIdIndex: number; + accountIndexes: number[]; + data: Uint8Array; +} + +/** + * Packs a list of compact instructions into a single buffer. + * Used by the Execute instruction to encode inner instructions. + */ +export function packCompactInstructions(instructions: CompactInstruction[]): Uint8Array { + if (instructions.length > 255) { + throw new Error("Too many instructions (max 255)"); + } + + const buffers: Uint8Array[] = []; + + // 1. Number of instructions + buffers.push(new Uint8Array([instructions.length])); + + for (const ix of instructions) { + // 2. Program ID index + number of accounts + if (ix.accountIndexes.length > 255) { + throw new Error("Too many accounts in an instruction (max 255)"); + } + buffers.push(new Uint8Array([ix.programIdIndex, ix.accountIndexes.length])); + + // 3. Account indexes + buffers.push(new Uint8Array(ix.accountIndexes)); + + // 4. Data length (u16 LE) + const dataLen = ix.data.length; + if (dataLen > 65535) { + throw new Error("Instruction data too large (max 65535 bytes)"); + } + buffers.push(new Uint8Array([dataLen & 0xff, (dataLen >> 8) & 0xff])); + + // 5. Data + buffers.push(ix.data); + } + + // Concatenate all buffers + const totalLength = buffers.reduce((acc, b) => acc + b.length, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + for (const b of buffers) { + result.set(b, offset); + offset += b.length; + } + + return result; +} diff --git a/sdk/codama-client/src/utils/pdas.ts b/sdk/codama-client/src/utils/pdas.ts new file mode 100644 index 0000000..edc7674 --- /dev/null +++ b/sdk/codama-client/src/utils/pdas.ts @@ -0,0 +1,96 @@ +/** + * PDA derivation helpers for LazorKit accounts. + * + * These are not auto-generated because the Shank IDL + * doesn't include PDA seed definitions. + */ + +import { + getAddressEncoder, + getProgramDerivedAddress, + type Address, + type ProgramDerivedAddress, +} from "@solana/kit"; +import { LAZORKIT_PROGRAM_PROGRAM_ADDRESS } from "../generated"; + +const encoder = getAddressEncoder(); + +/** + * Derives the Wallet PDA. + * Seeds: ["wallet", user_seed] + */ +export async function findWalletPda( + userSeed: Uint8Array +): Promise { + return await getProgramDerivedAddress({ + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + seeds: ["wallet", userSeed], + }); +} + +/** + * Derives the Vault PDA. + * Seeds: ["vault", wallet_pubkey] + */ +export async function findVaultPda( + wallet: Address +): Promise { + return await getProgramDerivedAddress({ + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + seeds: ["vault", encoder.encode(wallet)], + }); +} + +/** + * Derives an Authority PDA. + * Seeds: ["authority", wallet_pubkey, id_seed] + * @param idSeed - For Ed25519 this is the 32-byte public key. For Secp256r1 this is the 32-byte SHA256 Hash of the `credential_id` (rawId). + */ +export async function findAuthorityPda( + wallet: Address, + idSeed: Uint8Array +): Promise { + return await getProgramDerivedAddress({ + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + seeds: ["authority", encoder.encode(wallet), idSeed], + }); +} + +/** + * Derives a Session PDA. + * Seeds: ["session", wallet_pubkey, session_key_pubkey] + */ +export async function findSessionPda( + wallet: Address, + sessionKey: Address +): Promise { + return await getProgramDerivedAddress({ + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + seeds: ["session", encoder.encode(wallet), encoder.encode(sessionKey)], + }); +} + +/** + * Derives the Config PDA. + * Seeds: ["config"] + */ +export async function findConfigPda(): Promise { + return await getProgramDerivedAddress({ + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + seeds: ["config"], + }); +} + +/** + * Derives a Treasury Shard PDA. + * Seeds: ["treasury", shard_id] + */ +export async function findTreasuryShardPda( + shardId: number +): Promise { + return await getProgramDerivedAddress({ + programAddress: LAZORKIT_PROGRAM_PROGRAM_ADDRESS, + seeds: ["treasury", new Uint8Array([shardId])], + }); +} + diff --git a/sdk/codama-client/test-kit.ts b/sdk/codama-client/test-kit.ts new file mode 100644 index 0000000..e0bc303 --- /dev/null +++ b/sdk/codama-client/test-kit.ts @@ -0,0 +1,2 @@ +import { signTransactionMessageWithSigners } from "@solana/kit"; +console.log(typeof signTransactionMessageWithSigners); diff --git a/sdk/codama-client/tsconfig.json b/sdk/codama-client/tsconfig.json new file mode 100644 index 0000000..269d491 --- /dev/null +++ b/sdk/codama-client/tsconfig.json @@ -0,0 +1,16 @@ + +{ + "compilerOptions": { + "target": "es2022", + "module": "esnext", + "moduleResolution": "bundler", + "declaration": true, + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src"] +} diff --git a/sdk/constants.ts b/sdk/constants.ts deleted file mode 100644 index 8a86766..0000000 --- a/sdk/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -// LAZOR.KIT PROGRAM -export const SMART_WALLET_SEQ_SEED = Buffer.from("smart_wallet_seq"); -export const SMART_WALLET_SEED = Buffer.from("smart_wallet"); -export const SMART_WALLET_CONFIG_SEED = Buffer.from("smart_wallet_config"); -export const WHITELIST_RULE_PROGRAMS_SEED = Buffer.from( - "whitelist_rule_programs" -); -export const RULE_DATA_SEED = Buffer.from("rule_data"); -export const MEMBER_SEED = Buffer.from("member"); -export const CONFIG_SEED = Buffer.from("config"); -export const AUTHORITY_SEED = Buffer.from("authority"); - -// DEFAULT RULE PROGRAM -export const RULE_SEED = Buffer.from("rule"); diff --git a/sdk/default-rule-program.ts b/sdk/default-rule-program.ts deleted file mode 100644 index a38ff2f..0000000 --- a/sdk/default-rule-program.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as anchor from "@coral-xyz/anchor"; -import { DefaultRule } from "../target/types/default_rule"; -import * as types from "./types"; -import * as constants from "./constants"; - -export class DefaultRuleProgram { - private connection: anchor.web3.Connection; - private Idl: anchor.Idl = require("../target/idl/default_rule.json"); - - constructor(connection: anchor.web3.Connection) { - this.connection = connection; - } - - get program(): anchor.Program { - return new anchor.Program(this.Idl, { - connection: this.connection, - }); - } - - get programId(): anchor.web3.PublicKey { - return this.program.programId; - } - - rule(smartWallet: anchor.web3.PublicKey): anchor.web3.PublicKey { - return anchor.web3.PublicKey.findProgramAddressSync( - [constants.RULE_SEED, smartWallet.toBuffer()], - this.programId - )[0]; - } - - get config(): anchor.web3.PublicKey { - return anchor.web3.PublicKey.findProgramAddressSync( - [constants.CONFIG_SEED], - this.programId - )[0]; - } - - async initRuleIns( - payer: anchor.web3.PublicKey, - smartWallet: anchor.web3.PublicKey, - smartWalletAuthenticator: anchor.web3.PublicKey - ) { - return await this.program.methods - .initRule() - .accountsPartial({ - payer, - smartWallet, - smartWalletAuthenticator, - rule: this.rule(smartWallet), - systemProgram: anchor.web3.SystemProgram.programId, - }) - .instruction(); - } - - async checkRuleIns( - smartWallet: anchor.web3.PublicKey, - smartWalletAuthenticator: anchor.web3.PublicKey - ) { - return await this.program.methods - .checkRule() - .accountsPartial({ - rule: this.rule(smartWallet), - smartWalletAuthenticator, - }) - .instruction(); - } - - async destroyIns( - payer: anchor.web3.PublicKey, - smartWallet: anchor.web3.PublicKey, - smartWalletAuthenticator: anchor.web3.PublicKey - ) { - return await this.program.methods - .destroy() - .accountsPartial({ - rule: this.rule(smartWallet), - smartWalletAuthenticator, - smartWallet, - }) - .instruction(); - } -} diff --git a/sdk/lazor-kit.ts b/sdk/lazor-kit.ts deleted file mode 100644 index 3aea839..0000000 --- a/sdk/lazor-kit.ts +++ /dev/null @@ -1,419 +0,0 @@ -import * as anchor from '@coral-xyz/anchor'; -import IDL from '../target/idl/lazorkit.json'; -import * as bs58 from 'bs58'; -import { Lazorkit } from '../target/types/lazorkit'; -import * as constants from './constants'; -import { - createSecp256r1Instruction, - hashSeeds, - instructionToAccountMetas, -} from './utils'; -import * as types from './types'; -import { sha256 } from 'js-sha256'; -import { DefaultRuleProgram } from './default-rule-program'; -import * as borsh from 'borsh'; -import { Buffer } from 'buffer'; - -// Polyfill for structuredClone (e.g. React Native/Expo) -if (typeof globalThis.structuredClone !== 'function') { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore – minimal polyfill for non-circular data - globalThis.structuredClone = (obj: unknown) => - JSON.parse(JSON.stringify(obj)); -} - -export class LazorKitProgram { - /** Network connection used by all RPC / account queries */ - readonly connection: anchor.web3.Connection; - - /** Cached IDL to avoid repeated JSON parsing */ - readonly Idl: anchor.Idl = IDL as Lazorkit; - - /** Lazily-instantiated (and cached) Anchor `Program` wrapper */ - private _program?: anchor.Program; - - /** Frequently-used PDA caches (network-independent, so safe to memoise) */ - private _smartWalletSeq?: anchor.web3.PublicKey; - private _whitelistRulePrograms?: anchor.web3.PublicKey; - private _config?: anchor.web3.PublicKey; - - /** Embedded helper for the on-chain default rule program */ - readonly defaultRuleProgram: DefaultRuleProgram; - - constructor(connection: anchor.web3.Connection) { - this.connection = connection; - this.defaultRuleProgram = new DefaultRuleProgram(connection); - } - - get program(): anchor.Program { - if (!this._program) { - this._program = new anchor.Program(this.Idl, { - connection: this.connection, - }); - } - return this._program; - } - - get programId(): anchor.web3.PublicKey { - return this.program.programId; - } - - get smartWalletSeq(): anchor.web3.PublicKey { - if (!this._smartWalletSeq) { - this._smartWalletSeq = anchor.web3.PublicKey.findProgramAddressSync( - [constants.SMART_WALLET_SEQ_SEED], - this.programId - )[0]; - } - return this._smartWalletSeq; - } - - get smartWalletSeqData(): Promise { - return this.program.account.smartWalletSeq.fetch(this.smartWalletSeq); - } - - async getLastestSmartWallet(): Promise { - const seqData = await this.program.account.smartWalletSeq.fetch( - this.smartWalletSeq - ); - return anchor.web3.PublicKey.findProgramAddressSync( - [constants.SMART_WALLET_SEED, seqData.seq.toArrayLike(Buffer, 'le', 8)], - this.programId - )[0]; - } - - async getSmartWalletConfigData( - smartWallet: anchor.web3.PublicKey - ): Promise { - return this.program.account.smartWalletConfig.fetch( - this.smartWalletConfig(smartWallet) - ); - } - - smartWalletAuthenticator( - passkey: number[], - smartWallet: anchor.web3.PublicKey - ): [anchor.web3.PublicKey, number] { - const hash = hashSeeds(passkey, smartWallet); - return anchor.web3.PublicKey.findProgramAddressSync([hash], this.programId); - } - - async getSmartWalletAuthenticatorData( - smartWalletAuthenticator: anchor.web3.PublicKey - ): Promise { - return this.program.account.smartWalletAuthenticator.fetch( - smartWalletAuthenticator - ); - } - - smartWalletConfig(smartWallet: anchor.web3.PublicKey): anchor.web3.PublicKey { - return anchor.web3.PublicKey.findProgramAddressSync( - [constants.SMART_WALLET_CONFIG_SEED, smartWallet.toBuffer()], - this.programId - )[0]; - } - - get whitelistRulePrograms(): anchor.web3.PublicKey { - if (!this._whitelistRulePrograms) { - this._whitelistRulePrograms = - anchor.web3.PublicKey.findProgramAddressSync( - [constants.WHITELIST_RULE_PROGRAMS_SEED], - this.programId - )[0]; - } - return this._whitelistRulePrograms; - } - - get config(): anchor.web3.PublicKey { - if (!this._config) { - this._config = anchor.web3.PublicKey.findProgramAddressSync( - [constants.CONFIG_SEED], - this.programId - )[0]; - } - return this._config; - } - - async initializeTxn( - payer: anchor.web3.PublicKey, - defaultRuleProgram: anchor.web3.PublicKey - ): Promise { - const ix = await this.program.methods - .initialize() - .accountsPartial({ - signer: payer, - config: this.config, - whitelistRulePrograms: this.whitelistRulePrograms, - smartWalletSeq: this.smartWalletSeq, - defaultRuleProgram, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .remainingAccounts([ - { - pubkey: anchor.web3.BPF_LOADER_PROGRAM_ID, - isWritable: false, - isSigner: false, - }, - ]) - .instruction(); - return new anchor.web3.Transaction().add(ix); - } - - async upsertWhitelistRuleProgramsTxn( - payer: anchor.web3.PublicKey, - ruleProgram: anchor.web3.PublicKey - ): Promise { - const ix = await this.program.methods - .upsertWhitelistRulePrograms(ruleProgram) - .accountsPartial({ - authority: payer, - config: this._config ?? this.config, - whitelistRulePrograms: this.whitelistRulePrograms, - }) - .instruction(); - return new anchor.web3.Transaction().add(ix); - } - - async createSmartWalletTxn( - passkeyPubkey: number[], - ruleIns: anchor.web3.TransactionInstruction | null, - payer: anchor.web3.PublicKey, - credentialId: string = '' - ): Promise { - const configData = await this.program.account.config.fetch(this.config); - const smartWallet = await this.getLastestSmartWallet(); - const [smartWalletAuthenticator] = this.smartWalletAuthenticator( - passkeyPubkey, - smartWallet - ); - - // If caller does not provide a rule instruction, default to initRule of DefaultRuleProgram - const ruleInstruction = - ruleIns || - (await this.defaultRuleProgram.initRuleIns( - payer, - smartWallet, - smartWalletAuthenticator - )); - - const remainingAccounts = instructionToAccountMetas(ruleInstruction, payer); - - const createSmartWalletIx = await this.program.methods - .createSmartWallet( - passkeyPubkey, - Buffer.from(credentialId, 'base64'), - ruleInstruction.data - ) - .accountsPartial({ - signer: payer, - smartWalletSeq: this.smartWalletSeq, - whitelistRulePrograms: this.whitelistRulePrograms, - smartWallet, - smartWalletConfig: this.smartWalletConfig(smartWallet), - smartWalletAuthenticator, - config: this.config, - defaultRuleProgram: configData.defaultRuleProgram, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .remainingAccounts(remainingAccounts) - .instruction(); - - const tx = new anchor.web3.Transaction().add(createSmartWalletIx); - tx.feePayer = payer; - tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash; - return tx; - } - - async executeInstructionTxn( - passkeyPubkey: number[], - clientDataJsonRaw: Buffer, - authenticatorDataRaw: Buffer, - signature: Buffer, - payer: anchor.web3.PublicKey, - smartWallet: anchor.web3.PublicKey, - ruleIns: anchor.web3.TransactionInstruction | null = null, - cpiIns: anchor.web3.TransactionInstruction | null = null, - executeAction: anchor.IdlTypes['action'] = types.ExecuteAction - .ExecuteCpi, - createNewAuthenticator: number[] = null, - verifyInstructionIndex: number = 1 - ): Promise { - const [smartWalletAuthenticator] = this.smartWalletAuthenticator( - passkeyPubkey, - smartWallet - ); - - const ruleInstruction = - ruleIns || - (await this.defaultRuleProgram.checkRuleIns( - smartWallet, - smartWalletAuthenticator - )); - - const ruleData: types.CpiData = { - data: ruleInstruction.data, - startIndex: 0, - length: ruleInstruction.keys.length, - }; - - let cpiData: types.CpiData | null = null; - - const remainingAccounts: anchor.web3.AccountMeta[] = []; - - if (cpiIns) { - cpiData = { - data: cpiIns.data, - startIndex: 0, - length: cpiIns.keys.length, - }; - - // The order matters: first CPI accounts, then rule accounts. - remainingAccounts.push(...instructionToAccountMetas(cpiIns, payer)); - - ruleData.startIndex = cpiIns.keys.length; - } - - // Rule program accounts always follow. - remainingAccounts.push( - ...instructionToAccountMetas(ruleInstruction, payer) - ); - - const message = Buffer.concat([ - authenticatorDataRaw, - Buffer.from(sha256.arrayBuffer(clientDataJsonRaw)), - ]); - - const verifySignatureIx = createSecp256r1Instruction( - message, - Buffer.from(passkeyPubkey), - signature - ); - - let newSmartWalletAuthenticator: anchor.web3.PublicKey | null = null; - if (createNewAuthenticator) { - [newSmartWalletAuthenticator] = this.smartWalletAuthenticator( - createNewAuthenticator, - smartWallet - ); - } - - const executeInstructionIx = await this.program.methods - .executeInstruction({ - passkeyPubkey, - signature, - clientDataJsonRaw, - authenticatorDataRaw, - verifyInstructionIndex, - ruleData: ruleData, - cpiData: cpiData, - action: executeAction, - createNewAuthenticator, - }) - .accountsPartial({ - payer, - config: this.config, - smartWallet, - smartWalletConfig: this.smartWalletConfig(smartWallet), - smartWalletAuthenticator, - whitelistRulePrograms: this.whitelistRulePrograms, - authenticatorProgram: ruleInstruction.programId, - ixSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY, - systemProgram: anchor.web3.SystemProgram.programId, - cpiProgram: cpiIns ? cpiIns.programId : anchor.web3.PublicKey.default, - newSmartWalletAuthenticator: newSmartWalletAuthenticator, - }) - .remainingAccounts(remainingAccounts) - .instruction(); - - const txn = new anchor.web3.Transaction() - .add( - anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({ - units: 300_000, - }) - ) - .add(verifySignatureIx) - .add(executeInstructionIx); - - txn.feePayer = payer; - txn.recentBlockhash = ( - await this.connection.getLatestBlockhash() - ).blockhash; - return txn; - } - - /** - * Query the chain for the smart-wallet associated with a passkey. - */ - async getSmartWalletByPasskey(passkeyPubkey: number[]): Promise<{ - smartWallet: anchor.web3.PublicKey | null; - smartWalletAuthenticator: anchor.web3.PublicKey | null; - }> { - const discriminator = (IDL as any).accounts.find( - (a: any) => a.name === 'SmartWalletAuthenticator' - )!.discriminator; - - const accounts = await this.connection.getProgramAccounts(this.programId, { - dataSlice: { - offset: 8, - length: 33, - }, - filters: [ - { memcmp: { offset: 0, bytes: bs58.encode(discriminator) } }, - { memcmp: { offset: 8, bytes: bs58.encode(passkeyPubkey) } }, - ], - }); - - if (accounts.length === 0) { - return { smartWalletAuthenticator: null, smartWallet: null }; - } - - const smartWalletAuthenticatorData = - await this.getSmartWalletAuthenticatorData(accounts[0].pubkey); - - return { - smartWalletAuthenticator: accounts[0].pubkey, - smartWallet: smartWalletAuthenticatorData.smartWallet, - }; - } - - /** - * Build the serialized Message struct used for signing requests. - */ - async getMessage(smartWallet: string): Promise { - const smartWalletData = await this.getSmartWalletConfigData( - new anchor.web3.PublicKey(smartWallet) - ); - - // NOTE: field name may differ; adjust to your IDL - const nonce = - (smartWalletData as any).nonce ?? (smartWalletData as any).lastNonce; - - class Message { - nonce: anchor.BN; - timestamp: anchor.BN; - constructor(fields: { nonce: anchor.BN; timestamp: anchor.BN }) { - this.nonce = fields.nonce; - this.timestamp = fields.timestamp; - } - } - - const schema = new Map([ - [ - Message, - { - kind: 'struct', - fields: [ - ['nonce', 'u64'], - ['timestamp', 'u64'], - ], - }, - ], - ]); - - const encoded = borsh.serialize( - schema, - new Message({ nonce, timestamp: new anchor.BN(Date.now()) }) - ); - return Buffer.from(encoded); - } -} diff --git a/sdk/package-lock.json b/sdk/package-lock.json new file mode 100644 index 0000000..5a48655 --- /dev/null +++ b/sdk/package-lock.json @@ -0,0 +1,3409 @@ +{ + "name": "lazorkit-sdk-workspace", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lazorkit-sdk-workspace", + "workspaces": [ + "codama-client", + "solita-client" + ] + }, + "codama-client": { + "name": "@lazorkit/codama-client", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@solana/kit": "^6.0.1" + }, + "devDependencies": { + "@codama/nodes-from-anchor": "^1.3.8", + "@codama/renderers-js": "^1.7.0", + "@types/node": "^25.2.3", + "codama": "^1.5.0", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + } + }, + "codama-client/node_modules/@codama/cli": { + "version": "1.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/nodes": "1.5.0", + "@codama/visitors": "1.5.0", + "@codama/visitors-core": "1.5.0", + "commander": "^14.0.2", + "picocolors": "^1.1.1", + "prompts": "^2.4.2" + }, + "bin": { + "codama": "bin/cli.cjs" + } + }, + "codama-client/node_modules/@codama/errors": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/node-types": "1.5.0", + "commander": "^14.0.2", + "picocolors": "^1.1.1" + }, + "bin": { + "errors": "bin/cli.cjs" + } + }, + "codama-client/node_modules/@codama/node-types": { + "version": "1.5.0", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/@codama/nodes": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/node-types": "1.5.0" + } + }, + "codama-client/node_modules/@codama/nodes-from-anchor": { + "version": "1.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "@codama/visitors": "1.5.0", + "@noble/hashes": "^2.0.1", + "@solana/codecs": "^5.1.0" + } + }, + "codama-client/node_modules/@codama/nodes-from-anchor/node_modules/@noble/hashes": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "codama-client/node_modules/@codama/nodes-from-anchor/node_modules/@solana/codecs": { + "version": "5.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/codecs-data-structures": "5.5.1", + "@solana/codecs-numbers": "5.5.1", + "@solana/codecs-strings": "5.5.1", + "@solana/options": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@codama/nodes-from-anchor/node_modules/@solana/codecs-core": { + "version": "5.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@codama/nodes-from-anchor/node_modules/@solana/codecs-data-structures": { + "version": "5.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/codecs-numbers": "5.5.1", + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@codama/nodes-from-anchor/node_modules/@solana/codecs-numbers": { + "version": "5.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@codama/nodes-from-anchor/node_modules/@solana/codecs-strings": { + "version": "5.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/codecs-numbers": "5.5.1", + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "fastestsmallesttextencoderdecoder": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@codama/nodes-from-anchor/node_modules/@solana/errors": { + "version": "5.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@codama/nodes-from-anchor/node_modules/@solana/options": { + "version": "5.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.5.1", + "@solana/codecs-data-structures": "5.5.1", + "@solana/codecs-numbers": "5.5.1", + "@solana/codecs-strings": "5.5.1", + "@solana/errors": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@codama/nodes-from-anchor/node_modules/commander": { + "version": "14.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "codama-client/node_modules/@codama/renderers-core": { + "version": "1.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "@codama/visitors-core": "1.5.0" + } + }, + "codama-client/node_modules/@codama/renderers-js": { + "version": "1.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "^1.4.4", + "@codama/nodes": "^1.4.4", + "@codama/renderers-core": "^1.3.4", + "@codama/visitors-core": "^1.4.4", + "@solana/codecs-strings": "^6.0.0", + "prettier": "^3.8.1", + "semver": "^7.7.3" + }, + "engines": { + "node": ">=20.18.0" + } + }, + "codama-client/node_modules/@codama/validators": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "@codama/visitors-core": "1.5.0" + } + }, + "codama-client/node_modules/@codama/visitors": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "@codama/visitors-core": "1.5.0" + } + }, + "codama-client/node_modules/@codama/visitors-core": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "json-stable-stringify": "^1.3.0" + } + }, + "codama-client/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "codama-client/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "codama-client/node_modules/@solana/accounts": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/rpc-spec": "6.0.1", + "@solana/rpc-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/addresses": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/assertions": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/nominal-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/assertions": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/codecs": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/codecs-data-structures": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/options": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/codecs-core": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/codecs-data-structures": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/codecs-numbers": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/codecs-strings": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "fastestsmallesttextencoderdecoder": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/errors": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.3" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/fast-stable-stringify": { + "version": "6.0.1", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/functional": { + "version": "6.0.1", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/instruction-plans": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/instructions": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/promises": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/instructions": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/keys": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/assertions": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/nominal-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/kit": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/accounts": "6.0.1", + "@solana/addresses": "6.0.1", + "@solana/codecs": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/instruction-plans": "6.0.1", + "@solana/instructions": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/offchain-messages": "6.0.1", + "@solana/plugin-core": "6.0.1", + "@solana/programs": "6.0.1", + "@solana/rpc": "6.0.1", + "@solana/rpc-api": "6.0.1", + "@solana/rpc-parsed-types": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "@solana/rpc-subscriptions": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/signers": "6.0.1", + "@solana/sysvars": "6.0.1", + "@solana/transaction-confirmation": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/nominal-types": { + "version": "6.0.1", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/offchain-messages": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-data-structures": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/nominal-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/options": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.0.1", + "@solana/codecs-data-structures": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/plugin-core": { + "version": "6.0.1", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/programs": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/promises": { + "version": "6.0.1", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/fast-stable-stringify": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/rpc-api": "6.0.1", + "@solana/rpc-spec": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "@solana/rpc-transformers": "6.0.1", + "@solana/rpc-transport-http": "6.0.1", + "@solana/rpc-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-api": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/rpc-parsed-types": "6.0.1", + "@solana/rpc-spec": "6.0.1", + "@solana/rpc-transformers": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-parsed-types": { + "version": "6.0.1", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-spec": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/rpc-spec-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-spec-types": { + "version": "6.0.1", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-subscriptions": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/fast-stable-stringify": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/promises": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "@solana/rpc-subscriptions-api": "6.0.1", + "@solana/rpc-subscriptions-channel-websocket": "6.0.1", + "@solana/rpc-subscriptions-spec": "6.0.1", + "@solana/rpc-transformers": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/subscribable": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-subscriptions-api": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/rpc-subscriptions-spec": "6.0.1", + "@solana/rpc-transformers": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-subscriptions-channel-websocket": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/rpc-subscriptions-spec": "6.0.1", + "@solana/subscribable": "6.0.1", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-subscriptions-spec": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/promises": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "@solana/subscribable": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-transformers": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/nominal-types": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "@solana/rpc-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-transport-http": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1", + "@solana/rpc-spec": "6.0.1", + "@solana/rpc-spec-types": "6.0.1", + "undici-types": "^7.20.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/rpc-types": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/nominal-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/signers": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/instructions": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/nominal-types": "6.0.1", + "@solana/offchain-messages": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/subscribable": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/sysvars": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/accounts": "6.0.1", + "@solana/codecs": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/rpc-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/transaction-confirmation": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/promises": "6.0.1", + "@solana/rpc": "6.0.1", + "@solana/rpc-subscriptions": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/transaction-messages": "6.0.1", + "@solana/transactions": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/transaction-messages": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-data-structures": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/instructions": "6.0.1", + "@solana/nominal-types": "6.0.1", + "@solana/rpc-types": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@solana/transactions": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.0.1", + "@solana/codecs-core": "6.0.1", + "@solana/codecs-data-structures": "6.0.1", + "@solana/codecs-numbers": "6.0.1", + "@solana/codecs-strings": "6.0.1", + "@solana/errors": "6.0.1", + "@solana/functional": "6.0.1", + "@solana/instructions": "6.0.1", + "@solana/keys": "6.0.1", + "@solana/nominal-types": "6.0.1", + "@solana/rpc-types": "6.0.1", + "@solana/transaction-messages": "6.0.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "codama-client/node_modules/@standard-schema/spec": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/@types/chai": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "codama-client/node_modules/@types/deep-eql": { + "version": "4.0.2", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/@types/node": { + "version": "25.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "codama-client/node_modules/@types/node/node_modules/undici-types": { + "version": "7.16.0", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/@vitest/expect": { + "version": "4.0.18", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "codama-client/node_modules/@vitest/mocker": { + "version": "4.0.18", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "codama-client/node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "codama-client/node_modules/@vitest/runner": { + "version": "4.0.18", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "codama-client/node_modules/@vitest/snapshot": { + "version": "4.0.18", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "codama-client/node_modules/@vitest/spy": { + "version": "4.0.18", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "codama-client/node_modules/@vitest/utils": { + "version": "4.0.18", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "codama-client/node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "codama-client/node_modules/chai": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "codama-client/node_modules/codama": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@codama/cli": "1.4.4", + "@codama/errors": "1.5.0", + "@codama/nodes": "1.5.0", + "@codama/validators": "1.5.0", + "@codama/visitors": "1.5.0" + }, + "bin": { + "codama": "bin/cli.cjs" + } + }, + "codama-client/node_modules/es-module-lexer": { + "version": "1.7.0", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/esbuild": { + "version": "0.27.3", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "codama-client/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "codama-client/node_modules/expect-type": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "codama-client/node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "codama-client/node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "codama-client/node_modules/isarray": { + "version": "2.0.5", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/json-stable-stringify": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "codama-client/node_modules/jsonify": { + "version": "0.0.1", + "dev": true, + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "codama-client/node_modules/kleur": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "codama-client/node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "codama-client/node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "codama-client/node_modules/obug": { + "version": "2.1.1", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "codama-client/node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "codama-client/node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "codama-client/node_modules/postcss": { + "version": "8.5.6", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "codama-client/node_modules/prettier": { + "version": "3.8.1", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "codama-client/node_modules/prompts": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "codama-client/node_modules/rollup": { + "version": "4.57.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "codama-client/node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "codama-client/node_modules/sisteransi": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "codama-client/node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/std-env": { + "version": "3.10.0", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "codama-client/node_modules/tinyexec": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "codama-client/node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "codama-client/node_modules/tinyrainbow": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "codama-client/node_modules/undici-types": { + "version": "7.21.0", + "license": "MIT" + }, + "codama-client/node_modules/vite": { + "version": "7.3.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "codama-client/node_modules/vitest": { + "version": "4.0.18", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "codama-client/node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "codama-client/node_modules/ws": { + "version": "8.19.0", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@lazorkit/codama-client": { + "resolved": "codama-client", + "link": true + }, + "node_modules/@lazorkit/solita-client": { + "resolved": "solita-client", + "link": true + }, + "node_modules/@metaplex-foundation/beet": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@metaplex-foundation/beet/-/beet-0.7.2.tgz", + "integrity": "sha512-K+g3WhyFxKPc0xIvcIjNyV1eaTVJTiuaHZpig7Xx0MuYRMoJLLvhLTnUXhFdR5Tu2l2QSyKwfyXDgZlzhULqFg==", + "license": "Apache-2.0", + "dependencies": { + "ansicolors": "^0.3.2", + "assert": "^2.1.0", + "bn.js": "^5.2.0", + "debug": "^4.3.3" + } + }, + "node_modules/@metaplex-foundation/beet-solana": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@metaplex-foundation/beet-solana/-/beet-solana-0.3.1.tgz", + "integrity": "sha512-tgyEl6dvtLln8XX81JyBvWjIiEcjTkUwZbrM5dIobTmoqMuGewSyk9CClno8qsMsFdB5T3jC91Rjeqmu/6xk2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@metaplex-foundation/beet": ">=0.1.0", + "@solana/web3.js": "^1.56.2", + "bs58": "^5.0.0", + "debug": "^4.3.4" + } + }, + "node_modules/@metaplex-foundation/rustbin": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@metaplex-foundation/rustbin/-/rustbin-0.3.5.tgz", + "integrity": "sha512-m0wkRBEQB/8krwMwKBvFugufZtYwMXiGHud2cTDAv+aGXK4M90y0Hx67/wpu+AqqoQfdV8VM9YezUOHKD+Z5kA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.3.3", + "semver": "^7.3.7", + "text-table": "^0.2.0", + "toml": "^3.0.0" + } + }, + "node_modules/@metaplex-foundation/solita": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@metaplex-foundation/solita/-/solita-0.20.1.tgz", + "integrity": "sha512-E2bHGzT6wA/sXWBLgJ50ZQNvukPnQlH6kRU6m6lmatJdEOjNWhR1lLI7ESIk/i4ZiSdHZkc/Q6ile8eIlXOzNQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@metaplex-foundation/beet": "^0.7.1", + "@metaplex-foundation/beet-solana": "^0.3.1", + "@metaplex-foundation/rustbin": "^0.3.0", + "@solana/web3.js": "^1.56.2", + "ansi-colors": "^4.1.3", + "camelcase": "^6.2.1", + "debug": "^4.3.3", + "js-sha256": "^0.9.0", + "prettier": "^2.5.1", + "snake-case": "^3.0.4", + "spok": "^1.4.3" + }, + "bin": { + "solita": "dist/src/cli/solita.js" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana/buffer-layout": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", + "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", + "license": "MIT", + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/codecs-core": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", + "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-numbers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", + "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/errors": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", + "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^14.0.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/web3.js": { + "version": "1.98.4", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", + "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "@noble/curves": "^1.4.2", + "@noble/hashes": "^1.4.0", + "@solana/buffer-layout": "^4.0.1", + "@solana/codecs-numbers": "^2.1.0", + "agentkeepalive": "^4.5.0", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.1", + "node-fetch": "^2.7.0", + "rpc-websockets": "^9.0.2", + "superstruct": "^2.0.2" + } + }, + "node_modules/@solana/web3.js/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@solana/web3.js/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz", + "integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "license": "MIT" + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base-x": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.1.tgz", + "integrity": "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "license": "MIT" + }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "node_modules/borsh/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/borsh/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", + "license": "MIT" + }, + "node_modules/find-process": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.11.tgz", + "integrity": "sha512-mAOh9gGk9WZ4ip5UjV0o6Vb4SrfnAmtsFNzkMRH9HQiFXVQnDyQFrSHTK5UoG6E+KV+s+cIznbtwpfN41l2nFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "~4.1.2", + "commander": "^12.1.0", + "loglevel": "^1.9.2" + }, + "bin": { + "find-process": "bin/find-process.js" + } + }, + "node_modules/find-process/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/find-process/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jayson": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.3.0.tgz", + "integrity": "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==", + "license": "MIT", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "stream-json": "^1.9.1", + "uuid": "^8.3.2", + "ws": "^7.5.10" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jayson/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/rpc-websockets": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.5.tgz", + "integrity": "sha512-4mAmr+AEhPYJ9TmDtxF3r3ZcbWy7W8kvZ4PoZYw/Xgp2J7WixjwTgiQZsoTDvch5nimmg3Ay6/0Kuh9oIvVs9A==", + "license": "LGPL-3.0-only", + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/uuid": "^10.0.0", + "@types/ws": "^8.2.2", + "buffer": "^6.0.3", + "eventemitter3": "^5.0.1", + "uuid": "^11.0.0", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^6.0.0" + } + }, + "node_modules/rpc-websockets/node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/rpc-websockets/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/rpc-websockets/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/spok": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/spok/-/spok-1.5.5.tgz", + "integrity": "sha512-IrJIXY54sCNFASyHPOY+jEirkiJ26JDqsGiI0Dvhwcnkl0PEWi1PSsrkYql0rzDw8LFVTcA7rdUCAJdE2HE+2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansicolors": "~0.3.2", + "find-process": "^1.4.7" + } + }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "solita-client": { + "name": "@lazorkit/solita-client", + "version": "1.0.0", + "dependencies": { + "@metaplex-foundation/beet": "^0.7.2", + "@solana/web3.js": "^1.95.4" + }, + "devDependencies": { + "@metaplex-foundation/solita": "^0.20.1", + "typescript": "^5.0.0" + } + } + } +} diff --git a/sdk/package.json b/sdk/package.json new file mode 100644 index 0000000..277572f --- /dev/null +++ b/sdk/package.json @@ -0,0 +1,12 @@ +{ + "name": "lazorkit-sdk-workspace", + "private": true, + "workspaces": [ + "codama-client", + "solita-client" + ], + "scripts": { + "generate:all": "npm run generate --workspace=@lazorkit/codama-client && npm run generate --workspace=@lazorkit/solita-client", + "build:all": "npm run build --workspace=@lazorkit/codama-client && npm run build --workspace=@lazorkit/solita-client" + } +} diff --git a/sdk/solita-client/.crates/.crates.toml b/sdk/solita-client/.crates/.crates.toml new file mode 100644 index 0000000..3f6d1d4 --- /dev/null +++ b/sdk/solita-client/.crates/.crates.toml @@ -0,0 +1,2 @@ +[v1] +"shank-cli 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = ["shank"] diff --git a/sdk/solita-client/.crates/.crates2.json b/sdk/solita-client/.crates/.crates2.json new file mode 100644 index 0000000..da845a0 --- /dev/null +++ b/sdk/solita-client/.crates/.crates2.json @@ -0,0 +1 @@ +{"installs":{"shank-cli 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)":{"version_req":"=0.4.2","bins":["shank"],"features":[],"all_features":false,"no_default_features":false,"profile":"release","target":"aarch64-apple-darwin","rustc":"rustc 1.89.0 (29483883e 2025-08-04)\nbinary: rustc\ncommit-hash: 29483883eed69d5fb4db01964cdf2af4d86e9cb2\ncommit-date: 2025-08-04\nhost: aarch64-apple-darwin\nrelease: 1.89.0\nLLVM version: 20.1.7\n"}}} \ No newline at end of file diff --git a/sdk/solita-client/.crates/bin/shank b/sdk/solita-client/.crates/bin/shank new file mode 100755 index 0000000..100ba88 Binary files /dev/null and b/sdk/solita-client/.crates/bin/shank differ diff --git a/sdk/solita-client/.solitarc.js b/sdk/solita-client/.solitarc.js new file mode 100644 index 0000000..8735895 --- /dev/null +++ b/sdk/solita-client/.solitarc.js @@ -0,0 +1,15 @@ +const path = require('path'); +const programDir = path.join(__dirname, '..', '..', 'program'); +const idlDir = path.join(__dirname, '..', '..', 'program'); +const binaryInstallDir = path.join(__dirname, '.crates'); + +module.exports = { + // idlGenerator: 'shank', + programName: 'lazor_kit', + idlDir, + sdkDir: path.join(__dirname, 'src', 'generated'), + binaryInstallDir, + programDir, + removeExistingIdl: false, + binaryArgs: '-p DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2', +}; diff --git a/sdk/solita-client/README.md b/sdk/solita-client/README.md new file mode 100644 index 0000000..0e81c83 --- /dev/null +++ b/sdk/solita-client/README.md @@ -0,0 +1,280 @@ + +# LazorKit Solita TypeScript SDK + +> Modern, robust TypeScript SDK for the LazorKit smart wallet protocol on Solana. Extreme detail: usage for every exported API, account mapping, batching, advanced cryptography, test/dev flows, and full RBAC. Everything below is tested and production-ready. + +--- + +## :package: Installation + +```bash +npm install @lazorkit/solita-client +# or + +``` + +--- + +## :rocket: Instant Bootstrapping Example + +```ts +import { LazorClient, AuthType, Role } from "@lazorkit/solita-client"; +import { Keypair, Connection } from "@solana/web3.js"; + +const connection = new Connection("https://api.devnet.solana.com"); +const payer = Keypair.generate(); +const owner = Keypair.generate(); +const client = new LazorClient(connection); + +// --- 1. Create Ed25519 wallet --- +const { ix, walletPda, authorityPda, userSeed } = await client.createWallet({ + payer, + authType: AuthType.Ed25519, + owner: owner.publicKey, +}); +// ...compose/send transaction + +// --- 2. Add Admin Authority --- +const newAdmin = Keypair.generate(); +await client.addAuthority({ + payer, + walletPda, + newAuthPubkey: newAdmin.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Admin, + adminType: AuthType.Ed25519, + adminSigner: owner, +}); +``` + +--- + +## :page_facing_up: SDK API – Full Usage for All Functions + +### LazorClient (high-level API) + +> See also [tests-v1-rpc/tests/](../../tests-v1-rpc/tests/) for real flows with all functions in action. +All methods return objects with instructions (ix) and all derived addresses. + +#### `createWallet(params)` + +Create a new wallet with either Ed25519 or Passkey (Secp256r1) owner. + +```ts +// Ed25519 owner +await client.createWallet({ payer, authType: AuthType.Ed25519, owner: pubkey }); + +// Passkey/Secp256r1 owner +await client.createWallet({ payer, authType: AuthType.Secp256r1, pubkey, credentialHash }); +``` + +**Returns:** `{ ix, walletPda, authorityPda, userSeed }` + +#### `addAuthority(params)` / `removeAuthority(params)` / `changeRole(params)` +Add/remove/upgrade admin, owner, spender. Both Ed25519 and Passkey. Requires admin/owner signature or credential. + +```ts +await client.addAuthority({ payer, walletPda, newAuthPubkey, role: Role.Admin, adminType: AuthType.Ed25519, adminSigner }); +``` + +#### `createSession(params)` / `closeSession(params)` +Programmatic sessions/child key, slot-tied expiry, reclaim rent after closing. +```ts +await client.createSession({ payer, walletPda, authorityPda, sessionKey, expiresAt }); +await client.closeSession({ payer, walletPda, sessionPda, configPda, adminSigner }); +``` + +#### `execute(params)` +Run any instructions (single or compact/batched, CU-efficient, full RBAC/slot checking under the hood). +```ts +await client.execute({ payer, walletPda, authorityPda, innerInstructions: [ /* . . . */ ], signer }); +``` + +#### PDA & Derivation Helpers + +```ts +client.getWalletPda(userSeed) +client.getVaultPda(walletPda) +client.getAuthorityPda(walletPda, idSeed) +client.getSessionPda(walletPda, sessionKey) +client.getConfigPda() +client.getTreasuryShardPda(shardId) +``` + +#### Accessing raw instruction builders +If you need lowest level control, use `client.builder`: All contract instructions prepared for direct assembly/deep composition. + +--- + +### find* PDAs - account derivation + +All functions take (seed,[programId?]). Returns `[PublicKey, bump]` arrays for direct PDA math. + +```ts +findWalletPda(userSeed) +findVaultPda(walletPubkey) +findAuthorityPda(walletPubkey,idSeed) +findSessionPda(walletPubkey,sessionKey) +findConfigPda() +findTreasuryShardPda(shardId) +``` + +--- + +### Compact Packing (Batching) + +Fully supported for highest-throughput; contract expects this layout in Execute. + +#### `packCompactInstructions(instructions: CompactInstruction[])` + +```ts +import { packCompactInstructions } from "@lazorkit/solita-client"; +// instructions: array of {programIdIndex, accountIndexes, data} +const packed = packCompactInstructions([ ... ]); +``` + +#### `computeAccountsHash(accountMetas, instructions)` + +Strict matching with on-chain. Supply full AccountMeta[] as per your actual call. +```ts +const hash = await computeAccountsHash(accountMetas, instructions); +``` + +--- + +### Secp256r1 & WebAuthn Tools + +- `Secp256r1Signer` interface: implement for custom signers (browser WebAuthn, hardware) +- `appendSecp256r1Sysvars(ix)` – auto-injects correct sysvar accounts for secp/slot +- `buildAuthPayload(...)` – builds proof-of-liveness WebAuthn payload +- `readCurrentSlot(connection)` – load latest slot for nonce checks + +--- + +### Account Layout Types & Constants + +- `AUTHORITY_ACCOUNT_HEADER_SIZE`, `AUTHORITY_ACCOUNT_ED25519_SIZE`, `AUTHORITY_ACCOUNT_SECP256R1_SIZE` - memory mapping helpers. +- `Role` and `AuthType` enums. + +--- + +## :triangular_flag_on_post: Reference: Real-world End-to-End + +- [tests-v1-rpc/tests/02-wallet.test.ts](../../tests-v1-rpc/tests/02-wallet.test.ts) (main flows) +- [03-authority.test.ts](../../tests-v1-rpc/tests/03-authority.test.ts) (admin/owner mgmt) +- [04-execute.test.ts](../../tests-v1-rpc/tests/04-execute.test.ts) (multi-tx/cross-auth) + +--- + +## Security/Dev Notes + +- All calls strictly respect contract's RBAC logic and zero-copy struct boundaries +- Passkey/web crypto flows require correct signature prep; see helper/test for padding/hash details +- Packing code and address layout is 1:1 with the Rust contract; you can even print struct offsets for debugging + +--- + +## License +MIT + +--- + +## :construction: Core API (LazorClient) + +### Wallet & Authority +- `createWallet(params)` — Deploys all 3: Wallet, Vault, initial Owner authority. Ed25519 or Secp256r1 (Passkey) supported. Returns PDAs + actual seed. +- `addAuthority(params)` — (Owner/Admin only) Add additional roles: Owner, Admin, Spender. Ed25519 and Passkey supported. +- `removeAuthority(params)` — Remove an authority PDA (role drop/delegation) +- `changeRole(params)` — Upgrade/downgrade roles for a given PDA + +### Sessions / Temp Keys +- `createSession(params)` — Spawn a session sub-key, set expiry. (Authority may be Ed25519/Secp256r1) +- `closeSession(params)` — Closes session, returns rent to payer (authorized via admin or expiry) + +### Execution +- `execute(params)` — Core instruction executor. Accepts a batch (compact packed or regular) and authority/session parameters. All role/slot/auth checks enforced by program. + +### Advanced/Batch +- Use `packCompactInstructions` and related utilities for batch/multi-call flows (see `packing.ts`) + +### PDA Helpers (Everywhere) +- `findWalletPda(userSeed)` +- `findAuthorityPda(walletPda, idSeed)` +- `findSessionPda(walletPda, sessionKey)` +- `findVaultPda(walletPda)` +- `findConfigPda()` +- `findTreasuryShardPda(shardId)` + +--- + +## :key: Account & Memory Model (On-chain Mapping) + +All account layouts map **exactly** to Rust structs (see [../program/src/state/](../../program/src/state)): zero-copy, no Borsh/Serde. See [Architecture.md](../../docs/Architecture.md) for diagrams. + +Quick summary: + +- **WalletAccount**: { discriminator, bump, version, _padding[5] } +- **AuthorityAccountHeader**: { discriminator, authority_type, role, bump, version, _padding[3], counter, wallet } +- **SessionAccount**: { discriminator, bump, version, _padding[5], wallet, session_key, expires_at } +- **ConfigAccount**: { discriminator, bump, version, num_shards, _padding[4], admin, wallet_fee, action_fee } + +*Do not attempt to read/write account memory using other layouts—always use the auto-generated classes in `generated/` (e.g., `AuthorityAccount.fromAccountAddress(connection, authorityPda)`).* + +--- + +## :chart_with_upwards_trend: Example: WebAuthn/Passkey Authority + +```ts +// Generate secp256r1 authority (browser or Node.js; see test Suite) +// 1. Generate credential ID, get 32-byte hash +const credentialIdHash = sha256(...); // depends on your WebAuthn lib +const compressedPubKey = ...; // Uint8Array, 33 bytes, from crypto API + +const { ix, walletPda, authorityPda } = await client.createWallet({ + payer, + authType: AuthType.Secp256r1, + pubkey: compressedPubKey, + credentialHash: credentialIdHash, +}); +``` + +For E2E usage, see [tests-v1-rpc/tests/03-authority.test.ts](../../tests-v1-rpc/tests/03-authority.test.ts) and [src/utils/secp256r1.ts](src/utils/secp256r1.ts) for P-256 flows, including precompile instruction helpers, payload encoding, and more. + +--- + +## :hammer_and_wrench: Advanced: Batching & Compaction + +- For maximum CU efficiency, batch multiple instructions using `packCompactInstructions()` before `.execute()` +- See [04-execute.test.ts](../../tests-v1-rpc/tests/04-execute.test.ts) for real examples with SOL/SPL transfers, and full invocation of compacted instructions. + +--- + +## :rocket: Testing & Development + +- E2E test suite: [tests-v1-rpc/tests/](../../tests-v1-rpc/tests/) +- Contract core: [../program/src/](../../program/src/) +- Architecture, diagrams: [../docs/Architecture.md](../../docs/Architecture.md) + +--- + +## :triangular_flag_on_post: Security & Gotchas + +- All account memory must use correct layout/encoding (NoPadding), or on-chain checks will fail +- Pay attention to role/authority order — only Authority PDAs tied to the correct role can invoke privileged flows +- Session expiry is **absolute** slot-based (epoch/slot math may differ DevNet/MainNet) +- secp256r1 (Passkey) precompile flows require extra sysvar and signature preload; see helper and test examples +- Shard selection for fees is done via payer pubkey mod numShards (uses direct bytes) + +--- + +## :link: Resources + +- [Architecture.md](../../docs/Architecture.md) +- [Contract logic reference (program/src/)](../../program/src/) +- [Tests / E2E integration](../../tests-v1-rpc/tests/) +- [Solana web3.js](https://solana-labs.github.io/solana-web3.js/) + +--- + +## License +MIT diff --git a/sdk/solita-client/generate.js b/sdk/solita-client/generate.js new file mode 100644 index 0000000..87a236b --- /dev/null +++ b/sdk/solita-client/generate.js @@ -0,0 +1,38 @@ +const { Solita } = require('@metaplex-foundation/solita'); +const path = require('path'); +const fs = require('fs'); + +const idlPath = path.join(__dirname, '..', '..', 'program', 'lazor_kit.json'); + +if (!fs.existsSync(idlPath)) { + console.error(`❌ IDL not found at ${idlPath}`); + process.exit(1); +} + +const idl = JSON.parse(fs.readFileSync(idlPath, 'utf8')); + +// Ensure metadata.address is set (or fallback to something) +if (!idl.metadata || !idl.metadata.address) { + console.warn(`⚠️ Warning: idl.metadata.address is missing. Setting default.`); + idl.metadata = idl.metadata || {}; + idl.metadata.address = 'DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2'; +} + +const outputDir = path.join(__dirname, 'src', 'generated'); + +console.log(`--- 🔨 Generating Solita SDK from ${idlPath} ---`); +console.log(`Output: ${outputDir}`); + +const solita = new Solita(idl, { + formatCode: true, +}); + +solita.renderAndWriteTo(outputDir) + .then(() => { + console.log('--- ✅ Solita SDK Generated Successfully! ---'); + process.exit(0); + }) + .catch(err => { + console.error('❌ Generation Failed:', err); + process.exit(1); + }); diff --git a/sdk/solita-client/package.json b/sdk/solita-client/package.json new file mode 100644 index 0000000..65b32ab --- /dev/null +++ b/sdk/solita-client/package.json @@ -0,0 +1,18 @@ +{ + "name": "@lazorkit/solita-client", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "generate": "node generate.js", + "build": "tsc" + }, + "dependencies": { + "@solana/web3.js": "^1.95.4", + "@metaplex-foundation/beet": "^0.7.2" + }, + "devDependencies": { + "@metaplex-foundation/solita": "^0.20.1", + "typescript": "^5.0.0" + } +} diff --git a/sdk/solita-client/pnpm-lock.yaml b/sdk/solita-client/pnpm-lock.yaml new file mode 100644 index 0000000..4dc97bb --- /dev/null +++ b/sdk/solita-client/pnpm-lock.yaml @@ -0,0 +1,1045 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@metaplex-foundation/beet': + specifier: ^0.7.2 + version: 0.7.2 + '@solana/web3.js': + specifier: ^1.95.4 + version: 1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) + devDependencies: + '@metaplex-foundation/solita': + specifier: ^0.20.1 + version: 0.20.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) + typescript: + specifier: ^5.0.0 + version: 5.9.3 + +packages: + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@metaplex-foundation/beet-solana@0.3.1': + resolution: {integrity: sha512-tgyEl6dvtLln8XX81JyBvWjIiEcjTkUwZbrM5dIobTmoqMuGewSyk9CClno8qsMsFdB5T3jC91Rjeqmu/6xk2g==} + + '@metaplex-foundation/beet@0.7.2': + resolution: {integrity: sha512-K+g3WhyFxKPc0xIvcIjNyV1eaTVJTiuaHZpig7Xx0MuYRMoJLLvhLTnUXhFdR5Tu2l2QSyKwfyXDgZlzhULqFg==} + + '@metaplex-foundation/rustbin@0.3.5': + resolution: {integrity: sha512-m0wkRBEQB/8krwMwKBvFugufZtYwMXiGHud2cTDAv+aGXK4M90y0Hx67/wpu+AqqoQfdV8VM9YezUOHKD+Z5kA==} + + '@metaplex-foundation/solita@0.20.1': + resolution: {integrity: sha512-E2bHGzT6wA/sXWBLgJ50ZQNvukPnQlH6kRU6m6lmatJdEOjNWhR1lLI7ESIk/i4ZiSdHZkc/Q6ile8eIlXOzNQ==} + hasBin: true + + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@solana/buffer-layout@4.0.1': + resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} + engines: {node: '>=5.10'} + + '@solana/codecs-core@2.3.0': + resolution: {integrity: sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/codecs-numbers@2.3.0': + resolution: {integrity: sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.3.3' + + '@solana/errors@2.3.0': + resolution: {integrity: sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==} + engines: {node: '>=20.18.0'} + hasBin: true + peerDependencies: + typescript: '>=5.3.3' + + '@solana/web3.js@1.98.4': + resolution: {integrity: sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==} + + '@swc/helpers@0.5.19': + resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + '@types/node@25.5.0': + resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + + '@types/ws@7.4.7': + resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansicolors@0.3.2: + resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} + + assert@2.1.0: + resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + base-x@3.0.11: + resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==} + + base-x@4.0.1: + resolution: {integrity: sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bn.js@5.2.3: + resolution: {integrity: sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==} + + borsh@0.7.0: + resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} + + bs58@4.0.1: + resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} + + bs58@5.0.0: + resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bufferutil@4.1.0: + resolution: {integrity: sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==} + engines: {node: '>=6.14.2'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delay@5.0.0: + resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} + engines: {node: '>=10'} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} + + es6-promisify@5.0.0: + resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + eyes@0.1.8: + resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} + engines: {node: '> 0.1.90'} + + fast-stable-stringify@1.0.0: + resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} + + find-process@1.4.11: + resolution: {integrity: sha512-mAOh9gGk9WZ4ip5UjV0o6Vb4SrfnAmtsFNzkMRH9HQiFXVQnDyQFrSHTK5UoG6E+KV+s+cIznbtwpfN41l2nFA==} + hasBin: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-nan@1.3.2: + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + engines: {node: '>= 0.4'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + isomorphic-ws@4.0.1: + resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} + peerDependencies: + ws: '*' + + jayson@4.3.0: + resolution: {integrity: sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==} + engines: {node: '>=8'} + hasBin: true + + js-sha256@0.9.0: + resolution: {integrity: sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + engines: {node: '>= 0.6.0'} + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + rpc-websockets@9.3.6: + resolution: {integrity: sha512-RzuOQDGd+EtR/cBYQAH/0jjaBzhyvXXGROhxigGJPf+q3XKyvtelZCucylzxiq5MaGlfBx1075djTsxFsFDgrA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + + spok@1.5.5: + resolution: {integrity: sha512-IrJIXY54sCNFASyHPOY+jEirkiJ26JDqsGiI0Dvhwcnkl0PEWi1PSsrkYql0rzDw8LFVTcA7rdUCAJdE2HE+2Q==} + + stream-chain@2.2.5: + resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} + + stream-json@1.9.1: + resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} + + superstruct@2.0.2: + resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} + engines: {node: '>=14.0.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + text-encoding-utf-8@1.0.2: + resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + utf-8-validate@6.0.6: + resolution: {integrity: sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==} + engines: {node: '>=6.14.2'} + + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + +snapshots: + + '@babel/runtime@7.29.2': {} + + '@metaplex-foundation/beet-solana@0.3.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': + dependencies: + '@metaplex-foundation/beet': 0.7.2 + '@solana/web3.js': 1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) + bs58: 5.0.0 + debug: 4.4.3 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - typescript + - utf-8-validate + + '@metaplex-foundation/beet@0.7.2': + dependencies: + ansicolors: 0.3.2 + assert: 2.1.0 + bn.js: 5.2.3 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@metaplex-foundation/rustbin@0.3.5': + dependencies: + debug: 4.4.3 + semver: 7.7.4 + text-table: 0.2.0 + toml: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@metaplex-foundation/solita@0.20.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': + dependencies: + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.3.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) + '@metaplex-foundation/rustbin': 0.3.5 + '@solana/web3.js': 1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) + ansi-colors: 4.1.3 + camelcase: 6.3.0 + debug: 4.4.3 + js-sha256: 0.9.0 + prettier: 2.8.8 + snake-case: 3.0.4 + spok: 1.5.5 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - typescript + - utf-8-validate + + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + + '@solana/buffer-layout@4.0.1': + dependencies: + buffer: 6.0.3 + + '@solana/codecs-core@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + + '@solana/codecs-numbers@2.3.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 2.3.0(typescript@5.9.3) + '@solana/errors': 2.3.0(typescript@5.9.3) + typescript: 5.9.3 + + '@solana/errors@2.3.0(typescript@5.9.3)': + dependencies: + chalk: 5.6.2 + commander: 14.0.3 + typescript: 5.9.3 + + '@solana/web3.js@1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': + dependencies: + '@babel/runtime': 7.29.2 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + '@solana/buffer-layout': 4.0.1 + '@solana/codecs-numbers': 2.3.0(typescript@5.9.3) + agentkeepalive: 4.6.0 + bn.js: 5.2.3 + borsh: 0.7.0 + bs58: 4.0.1 + buffer: 6.0.3 + fast-stable-stringify: 1.0.0 + jayson: 4.3.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + node-fetch: 2.7.0 + rpc-websockets: 9.3.6 + superstruct: 2.0.2 + transitivePeerDependencies: + - bufferutil + - encoding + - typescript + - utf-8-validate + + '@swc/helpers@0.5.19': + dependencies: + tslib: 2.8.1 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 12.20.55 + + '@types/node@12.20.55': {} + + '@types/node@25.5.0': + dependencies: + undici-types: 7.18.2 + + '@types/uuid@10.0.0': {} + + '@types/ws@7.4.7': + dependencies: + '@types/node': 12.20.55 + + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.5.0 + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + ansi-colors@4.1.3: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansicolors@0.3.2: {} + + assert@2.1.0: + dependencies: + call-bind: 1.0.8 + is-nan: 1.3.2 + object-is: 1.1.6 + object.assign: 4.1.7 + util: 0.12.5 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + base-x@3.0.11: + dependencies: + safe-buffer: 5.2.1 + + base-x@4.0.1: {} + + base64-js@1.5.1: {} + + bn.js@5.2.3: {} + + borsh@0.7.0: + dependencies: + bn.js: 5.2.3 + bs58: 4.0.1 + text-encoding-utf-8: 1.0.2 + + bs58@4.0.1: + dependencies: + base-x: 3.0.11 + + bs58@5.0.0: + dependencies: + base-x: 4.0.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bufferutil@4.1.0: + dependencies: + node-gyp-build: 4.8.4 + optional: true + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + camelcase@6.3.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@12.1.0: {} + + commander@14.0.3: {} + + commander@2.20.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delay@5.0.0: {} + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es6-promise@4.2.8: {} + + es6-promisify@5.0.0: + dependencies: + es6-promise: 4.2.8 + + eventemitter3@5.0.4: {} + + eyes@0.1.8: {} + + fast-stable-stringify@1.0.0: {} + + find-process@1.4.11: + dependencies: + chalk: 4.1.2 + commander: 12.1.0 + loglevel: 1.9.2 + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + function-bind@1.1.2: {} + + generator-function@2.0.1: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-nan@1.3.2: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6)): + dependencies: + ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6) + + jayson@4.3.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): + dependencies: + '@types/connect': 3.4.38 + '@types/node': 12.20.55 + '@types/ws': 7.4.7 + commander: 2.20.3 + delay: 5.0.0 + es6-promisify: 5.0.0 + eyes: 0.1.8 + isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + json-stringify-safe: 5.0.1 + stream-json: 1.9.1 + uuid: 8.3.2 + ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + js-sha256@0.9.0: {} + + json-stringify-safe@5.0.1: {} + + loglevel@1.9.2: {} + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + math-intrinsics@1.1.0: {} + + ms@2.1.3: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-gyp-build@4.8.4: + optional: true + + object-is@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + possible-typed-array-names@1.1.0: {} + + prettier@2.8.8: {} + + rpc-websockets@9.3.6: + dependencies: + '@swc/helpers': 0.5.19 + '@types/uuid': 10.0.0 + '@types/ws': 8.18.1 + buffer: 6.0.3 + eventemitter3: 5.0.4 + uuid: 11.1.0 + ws: 8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 6.0.6 + + safe-buffer@5.2.1: {} + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + semver@7.7.4: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + spok@1.5.5: + dependencies: + ansicolors: 0.3.2 + find-process: 1.4.11 + + stream-chain@2.2.5: {} + + stream-json@1.9.1: + dependencies: + stream-chain: 2.2.5 + + superstruct@2.0.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + text-encoding-utf-8@1.0.2: {} + + text-table@0.2.0: {} + + toml@3.0.0: {} + + tr46@0.0.3: {} + + tslib@2.8.1: {} + + typescript@5.9.3: {} + + undici-types@7.18.2: {} + + utf-8-validate@6.0.6: + dependencies: + node-gyp-build: 4.8.4 + optional: true + + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.2 + is-typed-array: 1.1.15 + which-typed-array: 1.1.20 + + uuid@11.1.0: {} + + uuid@8.3.2: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 6.0.6 + + ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): + optionalDependencies: + bufferutil: 4.1.0 + utf-8-validate: 6.0.6 diff --git a/sdk/solita-client/src/generated/accounts/AuthorityAccount.ts b/sdk/solita-client/src/generated/accounts/AuthorityAccount.ts new file mode 100644 index 0000000..3005bf2 --- /dev/null +++ b/sdk/solita-client/src/generated/accounts/AuthorityAccount.ts @@ -0,0 +1,204 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import * as beetSolana from '@metaplex-foundation/beet-solana' + +/** + * Arguments used to create {@link AuthorityAccount} + * @category Accounts + * @category generated + */ +export type AuthorityAccountArgs = { + discriminator: number + authorityType: number + role: number + bump: number + version: number + padding: number[] /* size: 3 */ + counter: beet.bignum + wallet: web3.PublicKey +} +/** + * Holds the data for the {@link AuthorityAccount} Account and provides de/serialization + * functionality for that data + * + * @category Accounts + * @category generated + */ +export class AuthorityAccount implements AuthorityAccountArgs { + private constructor( + readonly discriminator: number, + readonly authorityType: number, + readonly role: number, + readonly bump: number, + readonly version: number, + readonly padding: number[] /* size: 3 */, + readonly counter: beet.bignum, + readonly wallet: web3.PublicKey + ) {} + + /** + * Creates a {@link AuthorityAccount} instance from the provided args. + */ + static fromArgs(args: AuthorityAccountArgs) { + return new AuthorityAccount( + args.discriminator, + args.authorityType, + args.role, + args.bump, + args.version, + args.padding, + args.counter, + args.wallet + ) + } + + /** + * Deserializes the {@link AuthorityAccount} from the data of the provided {@link web3.AccountInfo}. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static fromAccountInfo( + accountInfo: web3.AccountInfo, + offset = 0 + ): [AuthorityAccount, number] { + return AuthorityAccount.deserialize(accountInfo.data, offset) + } + + /** + * Retrieves the account info from the provided address and deserializes + * the {@link AuthorityAccount} from its data. + * + * @throws Error if no account info is found at the address or if deserialization fails + */ + static async fromAccountAddress( + connection: web3.Connection, + address: web3.PublicKey, + commitmentOrConfig?: web3.Commitment | web3.GetAccountInfoConfig + ): Promise { + const accountInfo = await connection.getAccountInfo( + address, + commitmentOrConfig + ) + if (accountInfo == null) { + throw new Error(`Unable to find AuthorityAccount account at ${address}`) + } + return AuthorityAccount.fromAccountInfo(accountInfo, 0)[0] + } + + /** + * Provides a {@link web3.Connection.getProgramAccounts} config builder, + * to fetch accounts matching filters that can be specified via that builder. + * + * @param programId - the program that owns the accounts we are filtering + */ + static gpaBuilder( + programId: web3.PublicKey = new web3.PublicKey( + 'DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2' + ) + ) { + return beetSolana.GpaBuilder.fromStruct(programId, authorityAccountBeet) + } + + /** + * Deserializes the {@link AuthorityAccount} from the provided data Buffer. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static deserialize(buf: Buffer, offset = 0): [AuthorityAccount, number] { + return authorityAccountBeet.deserialize(buf, offset) + } + + /** + * Serializes the {@link AuthorityAccount} into a Buffer. + * @returns a tuple of the created Buffer and the offset up to which the buffer was written to store it. + */ + serialize(): [Buffer, number] { + return authorityAccountBeet.serialize(this) + } + + /** + * Returns the byteSize of a {@link Buffer} holding the serialized data of + * {@link AuthorityAccount} + */ + static get byteSize() { + return authorityAccountBeet.byteSize + } + + /** + * Fetches the minimum balance needed to exempt an account holding + * {@link AuthorityAccount} data from rent + * + * @param connection used to retrieve the rent exemption information + */ + static async getMinimumBalanceForRentExemption( + connection: web3.Connection, + commitment?: web3.Commitment + ): Promise { + return connection.getMinimumBalanceForRentExemption( + AuthorityAccount.byteSize, + commitment + ) + } + + /** + * Determines if the provided {@link Buffer} has the correct byte size to + * hold {@link AuthorityAccount} data. + */ + static hasCorrectByteSize(buf: Buffer, offset = 0) { + return buf.byteLength - offset === AuthorityAccount.byteSize + } + + /** + * Returns a readable version of {@link AuthorityAccount} properties + * and can be used to convert to JSON and/or logging + */ + pretty() { + return { + discriminator: this.discriminator, + authorityType: this.authorityType, + role: this.role, + bump: this.bump, + version: this.version, + padding: this.padding, + counter: (() => { + const x = <{ toNumber: () => number }>this.counter + if (typeof x.toNumber === 'function') { + try { + return x.toNumber() + } catch (_) { + return x + } + } + return x + })(), + wallet: this.wallet.toBase58(), + } + } +} + +/** + * @category Accounts + * @category generated + */ +export const authorityAccountBeet = new beet.BeetStruct< + AuthorityAccount, + AuthorityAccountArgs +>( + [ + ['discriminator', beet.u8], + ['authorityType', beet.u8], + ['role', beet.u8], + ['bump', beet.u8], + ['version', beet.u8], + ['padding', beet.uniformFixedSizeArray(beet.u8, 3)], + ['counter', beet.u64], + ['wallet', beetSolana.publicKey], + ], + AuthorityAccount.fromArgs, + 'AuthorityAccount' +) diff --git a/sdk/solita-client/src/generated/accounts/SessionAccount.ts b/sdk/solita-client/src/generated/accounts/SessionAccount.ts new file mode 100644 index 0000000..18c988b --- /dev/null +++ b/sdk/solita-client/src/generated/accounts/SessionAccount.ts @@ -0,0 +1,199 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as web3 from '@solana/web3.js' +import * as beet from '@metaplex-foundation/beet' +import * as beetSolana from '@metaplex-foundation/beet-solana' + +/** + * Arguments used to create {@link SessionAccount} + * @category Accounts + * @category generated + */ +export type SessionAccountArgs = { + discriminator: number + bump: number + version: number + padding: number[] /* size: 5 */ + wallet: web3.PublicKey + sessionKey: web3.PublicKey + expiresAt: beet.bignum +} +/** + * Holds the data for the {@link SessionAccount} Account and provides de/serialization + * functionality for that data + * + * @category Accounts + * @category generated + */ +export class SessionAccount implements SessionAccountArgs { + private constructor( + readonly discriminator: number, + readonly bump: number, + readonly version: number, + readonly padding: number[] /* size: 5 */, + readonly wallet: web3.PublicKey, + readonly sessionKey: web3.PublicKey, + readonly expiresAt: beet.bignum + ) {} + + /** + * Creates a {@link SessionAccount} instance from the provided args. + */ + static fromArgs(args: SessionAccountArgs) { + return new SessionAccount( + args.discriminator, + args.bump, + args.version, + args.padding, + args.wallet, + args.sessionKey, + args.expiresAt + ) + } + + /** + * Deserializes the {@link SessionAccount} from the data of the provided {@link web3.AccountInfo}. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static fromAccountInfo( + accountInfo: web3.AccountInfo, + offset = 0 + ): [SessionAccount, number] { + return SessionAccount.deserialize(accountInfo.data, offset) + } + + /** + * Retrieves the account info from the provided address and deserializes + * the {@link SessionAccount} from its data. + * + * @throws Error if no account info is found at the address or if deserialization fails + */ + static async fromAccountAddress( + connection: web3.Connection, + address: web3.PublicKey, + commitmentOrConfig?: web3.Commitment | web3.GetAccountInfoConfig + ): Promise { + const accountInfo = await connection.getAccountInfo( + address, + commitmentOrConfig + ) + if (accountInfo == null) { + throw new Error(`Unable to find SessionAccount account at ${address}`) + } + return SessionAccount.fromAccountInfo(accountInfo, 0)[0] + } + + /** + * Provides a {@link web3.Connection.getProgramAccounts} config builder, + * to fetch accounts matching filters that can be specified via that builder. + * + * @param programId - the program that owns the accounts we are filtering + */ + static gpaBuilder( + programId: web3.PublicKey = new web3.PublicKey( + 'DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2' + ) + ) { + return beetSolana.GpaBuilder.fromStruct(programId, sessionAccountBeet) + } + + /** + * Deserializes the {@link SessionAccount} from the provided data Buffer. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static deserialize(buf: Buffer, offset = 0): [SessionAccount, number] { + return sessionAccountBeet.deserialize(buf, offset) + } + + /** + * Serializes the {@link SessionAccount} into a Buffer. + * @returns a tuple of the created Buffer and the offset up to which the buffer was written to store it. + */ + serialize(): [Buffer, number] { + return sessionAccountBeet.serialize(this) + } + + /** + * Returns the byteSize of a {@link Buffer} holding the serialized data of + * {@link SessionAccount} + */ + static get byteSize() { + return sessionAccountBeet.byteSize + } + + /** + * Fetches the minimum balance needed to exempt an account holding + * {@link SessionAccount} data from rent + * + * @param connection used to retrieve the rent exemption information + */ + static async getMinimumBalanceForRentExemption( + connection: web3.Connection, + commitment?: web3.Commitment + ): Promise { + return connection.getMinimumBalanceForRentExemption( + SessionAccount.byteSize, + commitment + ) + } + + /** + * Determines if the provided {@link Buffer} has the correct byte size to + * hold {@link SessionAccount} data. + */ + static hasCorrectByteSize(buf: Buffer, offset = 0) { + return buf.byteLength - offset === SessionAccount.byteSize + } + + /** + * Returns a readable version of {@link SessionAccount} properties + * and can be used to convert to JSON and/or logging + */ + pretty() { + return { + discriminator: this.discriminator, + bump: this.bump, + version: this.version, + padding: this.padding, + wallet: this.wallet.toBase58(), + sessionKey: this.sessionKey.toBase58(), + expiresAt: (() => { + const x = <{ toNumber: () => number }>this.expiresAt + if (typeof x.toNumber === 'function') { + try { + return x.toNumber() + } catch (_) { + return x + } + } + return x + })(), + } + } +} + +/** + * @category Accounts + * @category generated + */ +export const sessionAccountBeet = new beet.BeetStruct< + SessionAccount, + SessionAccountArgs +>( + [ + ['discriminator', beet.u8], + ['bump', beet.u8], + ['version', beet.u8], + ['padding', beet.uniformFixedSizeArray(beet.u8, 5)], + ['wallet', beetSolana.publicKey], + ['sessionKey', beetSolana.publicKey], + ['expiresAt', beet.u64], + ], + SessionAccount.fromArgs, + 'SessionAccount' +) diff --git a/sdk/solita-client/src/generated/accounts/WalletAccount.ts b/sdk/solita-client/src/generated/accounts/WalletAccount.ts new file mode 100644 index 0000000..9f9828c --- /dev/null +++ b/sdk/solita-client/src/generated/accounts/WalletAccount.ts @@ -0,0 +1,174 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' +import * as beetSolana from '@metaplex-foundation/beet-solana' + +/** + * Arguments used to create {@link WalletAccount} + * @category Accounts + * @category generated + */ +export type WalletAccountArgs = { + discriminator: number + bump: number + version: number + padding: number[] /* size: 5 */ +} +/** + * Holds the data for the {@link WalletAccount} Account and provides de/serialization + * functionality for that data + * + * @category Accounts + * @category generated + */ +export class WalletAccount implements WalletAccountArgs { + private constructor( + readonly discriminator: number, + readonly bump: number, + readonly version: number, + readonly padding: number[] /* size: 5 */ + ) {} + + /** + * Creates a {@link WalletAccount} instance from the provided args. + */ + static fromArgs(args: WalletAccountArgs) { + return new WalletAccount( + args.discriminator, + args.bump, + args.version, + args.padding + ) + } + + /** + * Deserializes the {@link WalletAccount} from the data of the provided {@link web3.AccountInfo}. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static fromAccountInfo( + accountInfo: web3.AccountInfo, + offset = 0 + ): [WalletAccount, number] { + return WalletAccount.deserialize(accountInfo.data, offset) + } + + /** + * Retrieves the account info from the provided address and deserializes + * the {@link WalletAccount} from its data. + * + * @throws Error if no account info is found at the address or if deserialization fails + */ + static async fromAccountAddress( + connection: web3.Connection, + address: web3.PublicKey, + commitmentOrConfig?: web3.Commitment | web3.GetAccountInfoConfig + ): Promise { + const accountInfo = await connection.getAccountInfo( + address, + commitmentOrConfig + ) + if (accountInfo == null) { + throw new Error(`Unable to find WalletAccount account at ${address}`) + } + return WalletAccount.fromAccountInfo(accountInfo, 0)[0] + } + + /** + * Provides a {@link web3.Connection.getProgramAccounts} config builder, + * to fetch accounts matching filters that can be specified via that builder. + * + * @param programId - the program that owns the accounts we are filtering + */ + static gpaBuilder( + programId: web3.PublicKey = new web3.PublicKey( + 'DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2' + ) + ) { + return beetSolana.GpaBuilder.fromStruct(programId, walletAccountBeet) + } + + /** + * Deserializes the {@link WalletAccount} from the provided data Buffer. + * @returns a tuple of the account data and the offset up to which the buffer was read to obtain it. + */ + static deserialize(buf: Buffer, offset = 0): [WalletAccount, number] { + return walletAccountBeet.deserialize(buf, offset) + } + + /** + * Serializes the {@link WalletAccount} into a Buffer. + * @returns a tuple of the created Buffer and the offset up to which the buffer was written to store it. + */ + serialize(): [Buffer, number] { + return walletAccountBeet.serialize(this) + } + + /** + * Returns the byteSize of a {@link Buffer} holding the serialized data of + * {@link WalletAccount} + */ + static get byteSize() { + return walletAccountBeet.byteSize + } + + /** + * Fetches the minimum balance needed to exempt an account holding + * {@link WalletAccount} data from rent + * + * @param connection used to retrieve the rent exemption information + */ + static async getMinimumBalanceForRentExemption( + connection: web3.Connection, + commitment?: web3.Commitment + ): Promise { + return connection.getMinimumBalanceForRentExemption( + WalletAccount.byteSize, + commitment + ) + } + + /** + * Determines if the provided {@link Buffer} has the correct byte size to + * hold {@link WalletAccount} data. + */ + static hasCorrectByteSize(buf: Buffer, offset = 0) { + return buf.byteLength - offset === WalletAccount.byteSize + } + + /** + * Returns a readable version of {@link WalletAccount} properties + * and can be used to convert to JSON and/or logging + */ + pretty() { + return { + discriminator: this.discriminator, + bump: this.bump, + version: this.version, + padding: this.padding, + } + } +} + +/** + * @category Accounts + * @category generated + */ +export const walletAccountBeet = new beet.BeetStruct< + WalletAccount, + WalletAccountArgs +>( + [ + ['discriminator', beet.u8], + ['bump', beet.u8], + ['version', beet.u8], + ['padding', beet.uniformFixedSizeArray(beet.u8, 5)], + ], + WalletAccount.fromArgs, + 'WalletAccount' +) diff --git a/sdk/solita-client/src/generated/accounts/index.ts b/sdk/solita-client/src/generated/accounts/index.ts new file mode 100644 index 0000000..9e203e2 --- /dev/null +++ b/sdk/solita-client/src/generated/accounts/index.ts @@ -0,0 +1,13 @@ +export * from './AuthorityAccount' +export * from './SessionAccount' +export * from './WalletAccount' + +import { WalletAccount } from './WalletAccount' +import { AuthorityAccount } from './AuthorityAccount' +import { SessionAccount } from './SessionAccount' + +export const accountProviders = { + WalletAccount, + AuthorityAccount, + SessionAccount, +} diff --git a/sdk/solita-client/src/generated/errors/index.ts b/sdk/solita-client/src/generated/errors/index.ts new file mode 100644 index 0000000..a3eac5e --- /dev/null +++ b/sdk/solita-client/src/generated/errors/index.ts @@ -0,0 +1,325 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +type ErrorWithCode = Error & { code: number } +type MaybeErrorWithCode = ErrorWithCode | null | undefined + +const createErrorFromCodeLookup: Map ErrorWithCode> = new Map() +const createErrorFromNameLookup: Map ErrorWithCode> = new Map() + +/** + * InvalidAuthorityPayload: 'Invalid authority payload' + * + * @category Errors + * @category generated + */ +export class InvalidAuthorityPayloadError extends Error { + readonly code: number = 0xbb9 + readonly name: string = 'InvalidAuthorityPayload' + constructor() { + super('Invalid authority payload') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, InvalidAuthorityPayloadError) + } + } +} + +createErrorFromCodeLookup.set(0xbb9, () => new InvalidAuthorityPayloadError()) +createErrorFromNameLookup.set( + 'InvalidAuthorityPayload', + () => new InvalidAuthorityPayloadError() +) + +/** + * PermissionDenied: 'Permission denied' + * + * @category Errors + * @category generated + */ +export class PermissionDeniedError extends Error { + readonly code: number = 0xbba + readonly name: string = 'PermissionDenied' + constructor() { + super('Permission denied') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, PermissionDeniedError) + } + } +} + +createErrorFromCodeLookup.set(0xbba, () => new PermissionDeniedError()) +createErrorFromNameLookup.set( + 'PermissionDenied', + () => new PermissionDeniedError() +) + +/** + * InvalidInstruction: 'Invalid instruction' + * + * @category Errors + * @category generated + */ +export class InvalidInstructionError extends Error { + readonly code: number = 0xbbb + readonly name: string = 'InvalidInstruction' + constructor() { + super('Invalid instruction') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, InvalidInstructionError) + } + } +} + +createErrorFromCodeLookup.set(0xbbb, () => new InvalidInstructionError()) +createErrorFromNameLookup.set( + 'InvalidInstruction', + () => new InvalidInstructionError() +) + +/** + * InvalidPubkey: 'Invalid public key' + * + * @category Errors + * @category generated + */ +export class InvalidPubkeyError extends Error { + readonly code: number = 0xbbc + readonly name: string = 'InvalidPubkey' + constructor() { + super('Invalid public key') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, InvalidPubkeyError) + } + } +} + +createErrorFromCodeLookup.set(0xbbc, () => new InvalidPubkeyError()) +createErrorFromNameLookup.set('InvalidPubkey', () => new InvalidPubkeyError()) + +/** + * InvalidMessageHash: 'Invalid message hash' + * + * @category Errors + * @category generated + */ +export class InvalidMessageHashError extends Error { + readonly code: number = 0xbbd + readonly name: string = 'InvalidMessageHash' + constructor() { + super('Invalid message hash') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, InvalidMessageHashError) + } + } +} + +createErrorFromCodeLookup.set(0xbbd, () => new InvalidMessageHashError()) +createErrorFromNameLookup.set( + 'InvalidMessageHash', + () => new InvalidMessageHashError() +) + +/** + * SignatureReused: 'Signature has already been used' + * + * @category Errors + * @category generated + */ +export class SignatureReusedError extends Error { + readonly code: number = 0xbbe + readonly name: string = 'SignatureReused' + constructor() { + super('Signature has already been used') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, SignatureReusedError) + } + } +} + +createErrorFromCodeLookup.set(0xbbe, () => new SignatureReusedError()) +createErrorFromNameLookup.set( + 'SignatureReused', + () => new SignatureReusedError() +) + +/** + * InvalidSignatureAge: 'Invalid signature age' + * + * @category Errors + * @category generated + */ +export class InvalidSignatureAgeError extends Error { + readonly code: number = 0xbbf + readonly name: string = 'InvalidSignatureAge' + constructor() { + super('Invalid signature age') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, InvalidSignatureAgeError) + } + } +} + +createErrorFromCodeLookup.set(0xbbf, () => new InvalidSignatureAgeError()) +createErrorFromNameLookup.set( + 'InvalidSignatureAge', + () => new InvalidSignatureAgeError() +) + +/** + * InvalidSessionDuration: 'Invalid session duration' + * + * @category Errors + * @category generated + */ +export class InvalidSessionDurationError extends Error { + readonly code: number = 0xbc0 + readonly name: string = 'InvalidSessionDuration' + constructor() { + super('Invalid session duration') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, InvalidSessionDurationError) + } + } +} + +createErrorFromCodeLookup.set(0xbc0, () => new InvalidSessionDurationError()) +createErrorFromNameLookup.set( + 'InvalidSessionDuration', + () => new InvalidSessionDurationError() +) + +/** + * SessionExpired: 'Session expired' + * + * @category Errors + * @category generated + */ +export class SessionExpiredError extends Error { + readonly code: number = 0xbc1 + readonly name: string = 'SessionExpired' + constructor() { + super('Session expired') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, SessionExpiredError) + } + } +} + +createErrorFromCodeLookup.set(0xbc1, () => new SessionExpiredError()) +createErrorFromNameLookup.set('SessionExpired', () => new SessionExpiredError()) + +/** + * AuthorityDoesNotSupportSession: 'Authority type does not support sessions' + * + * @category Errors + * @category generated + */ +export class AuthorityDoesNotSupportSessionError extends Error { + readonly code: number = 0xbc2 + readonly name: string = 'AuthorityDoesNotSupportSession' + constructor() { + super('Authority type does not support sessions') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, AuthorityDoesNotSupportSessionError) + } + } +} + +createErrorFromCodeLookup.set( + 0xbc2, + () => new AuthorityDoesNotSupportSessionError() +) +createErrorFromNameLookup.set( + 'AuthorityDoesNotSupportSession', + () => new AuthorityDoesNotSupportSessionError() +) + +/** + * InvalidAuthenticationKind: 'Invalid authentication kind' + * + * @category Errors + * @category generated + */ +export class InvalidAuthenticationKindError extends Error { + readonly code: number = 0xbc3 + readonly name: string = 'InvalidAuthenticationKind' + constructor() { + super('Invalid authentication kind') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, InvalidAuthenticationKindError) + } + } +} + +createErrorFromCodeLookup.set(0xbc3, () => new InvalidAuthenticationKindError()) +createErrorFromNameLookup.set( + 'InvalidAuthenticationKind', + () => new InvalidAuthenticationKindError() +) + +/** + * InvalidMessage: 'Invalid message' + * + * @category Errors + * @category generated + */ +export class InvalidMessageError extends Error { + readonly code: number = 0xbc4 + readonly name: string = 'InvalidMessage' + constructor() { + super('Invalid message') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, InvalidMessageError) + } + } +} + +createErrorFromCodeLookup.set(0xbc4, () => new InvalidMessageError()) +createErrorFromNameLookup.set('InvalidMessage', () => new InvalidMessageError()) + +/** + * SelfReentrancyNotAllowed: 'Self-reentrancy is not allowed' + * + * @category Errors + * @category generated + */ +export class SelfReentrancyNotAllowedError extends Error { + readonly code: number = 0xbc5 + readonly name: string = 'SelfReentrancyNotAllowed' + constructor() { + super('Self-reentrancy is not allowed') + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, SelfReentrancyNotAllowedError) + } + } +} + +createErrorFromCodeLookup.set(0xbc5, () => new SelfReentrancyNotAllowedError()) +createErrorFromNameLookup.set( + 'SelfReentrancyNotAllowed', + () => new SelfReentrancyNotAllowedError() +) + +/** + * Attempts to resolve a custom program error from the provided error code. + * @category Errors + * @category generated + */ +export function errorFromCode(code: number): MaybeErrorWithCode { + const createError = createErrorFromCodeLookup.get(code) + return createError != null ? createError() : null +} + +/** + * Attempts to resolve a custom program error from the provided error name, i.e. 'Unauthorized'. + * @category Errors + * @category generated + */ +export function errorFromName(name: string): MaybeErrorWithCode { + const createError = createErrorFromNameLookup.get(name) + return createError != null ? createError() : null +} diff --git a/sdk/solita-client/src/generated/index.ts b/sdk/solita-client/src/generated/index.ts new file mode 100644 index 0000000..c909c12 --- /dev/null +++ b/sdk/solita-client/src/generated/index.ts @@ -0,0 +1,21 @@ +import { PublicKey } from '@solana/web3.js' +export * from './accounts' +export * from './errors' +export * from './instructions' +export * from './types' + +/** + * Program address + * + * @category constants + * @category generated + */ +export const PROGRAM_ADDRESS = 'FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao' + +/** + * Program public key + * + * @category constants + * @category generated + */ +export const PROGRAM_ID = new PublicKey(PROGRAM_ADDRESS) diff --git a/sdk/solita-client/src/generated/instructions/AddAuthority.ts b/sdk/solita-client/src/generated/instructions/AddAuthority.ts new file mode 100644 index 0000000..0709007 --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/AddAuthority.ts @@ -0,0 +1,139 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category AddAuthority + * @category generated + */ +export type AddAuthorityInstructionArgs = { + newType: number + newRole: number + padding: number[] /* size: 6 */ + payload: Uint8Array +} +/** + * @category Instructions + * @category AddAuthority + * @category generated + */ +export const AddAuthorityStruct = new beet.FixableBeetArgsStruct< + AddAuthorityInstructionArgs & { + instructionDiscriminator: number + } +>( + [ + ['instructionDiscriminator', beet.u8], + ['newType', beet.u8], + ['newRole', beet.u8], + ['padding', beet.uniformFixedSizeArray(beet.u8, 6)], + ['payload', beet.bytes], + ], + 'AddAuthorityInstructionArgs' +) +/** + * Accounts required by the _AddAuthority_ instruction + * + * @property [_writable_, **signer**] payer + * @property [] wallet + * @property [] adminAuthority + * @property [_writable_] newAuthority + * @property [] config + * @property [_writable_] treasuryShard + * @property [**signer**] authorizerSigner (optional) + * @category Instructions + * @category AddAuthority + * @category generated + */ +export type AddAuthorityInstructionAccounts = { + payer: web3.PublicKey + wallet: web3.PublicKey + adminAuthority: web3.PublicKey + newAuthority: web3.PublicKey + systemProgram?: web3.PublicKey + config: web3.PublicKey + treasuryShard: web3.PublicKey + authorizerSigner?: web3.PublicKey +} + +export const addAuthorityInstructionDiscriminator = 1 + +/** + * Creates a _AddAuthority_ instruction. + * + * Optional accounts that are not provided default to the program ID since + * this was indicated in the IDL from which this instruction was generated. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category AddAuthority + * @category generated + */ +export function createAddAuthorityInstruction( + accounts: AddAuthorityInstructionAccounts, + args: AddAuthorityInstructionArgs, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = AddAuthorityStruct.serialize({ + instructionDiscriminator: addAuthorityInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.payer, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.wallet, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.adminAuthority, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.newAuthority, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.systemProgram ?? web3.SystemProgram.programId, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.config, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.treasuryShard, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.authorizerSigner ?? programId, + isWritable: false, + isSigner: accounts.authorizerSigner != null, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/CloseSession.ts b/sdk/solita-client/src/generated/instructions/CloseSession.ts new file mode 100644 index 0000000..9261e79 --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/CloseSession.ts @@ -0,0 +1,107 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category CloseSession + * @category generated + */ +export const CloseSessionStruct = new beet.BeetArgsStruct<{ + instructionDiscriminator: number +}>([['instructionDiscriminator', beet.u8]], 'CloseSessionInstructionArgs') +/** + * Accounts required by the _CloseSession_ instruction + * + * @property [_writable_, **signer**] payer + * @property [] wallet + * @property [_writable_] session + * @property [] config + * @property [] authorizer (optional) + * @property [**signer**] authorizerSigner (optional) + * @property [] sysvarInstructions (optional) + * @category Instructions + * @category CloseSession + * @category generated + */ +export type CloseSessionInstructionAccounts = { + payer: web3.PublicKey + wallet: web3.PublicKey + session: web3.PublicKey + config: web3.PublicKey + authorizer?: web3.PublicKey + authorizerSigner?: web3.PublicKey + sysvarInstructions?: web3.PublicKey +} + +export const closeSessionInstructionDiscriminator = 8 + +/** + * Creates a _CloseSession_ instruction. + * + * Optional accounts that are not provided default to the program ID since + * this was indicated in the IDL from which this instruction was generated. + * + * @param accounts that will be accessed while the instruction is processed + * @category Instructions + * @category CloseSession + * @category generated + */ +export function createCloseSessionInstruction( + accounts: CloseSessionInstructionAccounts, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = CloseSessionStruct.serialize({ + instructionDiscriminator: closeSessionInstructionDiscriminator, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.payer, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.wallet, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.session, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.config, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.authorizer ?? programId, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.authorizerSigner ?? programId, + isWritable: false, + isSigner: accounts.authorizerSigner != null, + }, + { + pubkey: accounts.sysvarInstructions ?? programId, + isWritable: false, + isSigner: false, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/CloseWallet.ts b/sdk/solita-client/src/generated/instructions/CloseWallet.ts new file mode 100644 index 0000000..5b2486e --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/CloseWallet.ts @@ -0,0 +1,107 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category CloseWallet + * @category generated + */ +export const CloseWalletStruct = new beet.BeetArgsStruct<{ + instructionDiscriminator: number +}>([['instructionDiscriminator', beet.u8]], 'CloseWalletInstructionArgs') +/** + * Accounts required by the _CloseWallet_ instruction + * + * @property [_writable_, **signer**] payer + * @property [_writable_] wallet + * @property [_writable_] vault + * @property [] ownerAuthority + * @property [_writable_] destination + * @property [**signer**] ownerSigner (optional) + * @property [] sysvarInstructions (optional) + * @category Instructions + * @category CloseWallet + * @category generated + */ +export type CloseWalletInstructionAccounts = { + payer: web3.PublicKey + wallet: web3.PublicKey + vault: web3.PublicKey + ownerAuthority: web3.PublicKey + destination: web3.PublicKey + ownerSigner?: web3.PublicKey + sysvarInstructions?: web3.PublicKey +} + +export const closeWalletInstructionDiscriminator = 9 + +/** + * Creates a _CloseWallet_ instruction. + * + * Optional accounts that are not provided default to the program ID since + * this was indicated in the IDL from which this instruction was generated. + * + * @param accounts that will be accessed while the instruction is processed + * @category Instructions + * @category CloseWallet + * @category generated + */ +export function createCloseWalletInstruction( + accounts: CloseWalletInstructionAccounts, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = CloseWalletStruct.serialize({ + instructionDiscriminator: closeWalletInstructionDiscriminator, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.payer, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.wallet, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.vault, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.ownerAuthority, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.destination, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.ownerSigner ?? programId, + isWritable: false, + isSigner: accounts.ownerSigner != null, + }, + { + pubkey: accounts.sysvarInstructions ?? programId, + isWritable: false, + isSigner: false, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/CreateSession.ts b/sdk/solita-client/src/generated/instructions/CreateSession.ts new file mode 100644 index 0000000..45d4cf9 --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/CreateSession.ts @@ -0,0 +1,141 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category CreateSession + * @category generated + */ +export type CreateSessionInstructionArgs = { + sessionKey: number[] /* size: 32 */ + expiresAt: beet.bignum +} +/** + * @category Instructions + * @category CreateSession + * @category generated + */ +export const CreateSessionStruct = new beet.BeetArgsStruct< + CreateSessionInstructionArgs & { + instructionDiscriminator: number + } +>( + [ + ['instructionDiscriminator', beet.u8], + ['sessionKey', beet.uniformFixedSizeArray(beet.u8, 32)], + ['expiresAt', beet.i64], + ], + 'CreateSessionInstructionArgs' +) +/** + * Accounts required by the _CreateSession_ instruction + * + * @property [_writable_, **signer**] payer + * @property [] wallet + * @property [] adminAuthority + * @property [_writable_] session + * @property [] config + * @property [_writable_] treasuryShard + * @property [**signer**] authorizerSigner (optional) + * @category Instructions + * @category CreateSession + * @category generated + */ +export type CreateSessionInstructionAccounts = { + payer: web3.PublicKey + wallet: web3.PublicKey + adminAuthority: web3.PublicKey + session: web3.PublicKey + systemProgram?: web3.PublicKey + rent?: web3.PublicKey + config: web3.PublicKey + treasuryShard: web3.PublicKey + authorizerSigner?: web3.PublicKey +} + +export const createSessionInstructionDiscriminator = 5 + +/** + * Creates a _CreateSession_ instruction. + * + * Optional accounts that are not provided default to the program ID since + * this was indicated in the IDL from which this instruction was generated. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category CreateSession + * @category generated + */ +export function createCreateSessionInstruction( + accounts: CreateSessionInstructionAccounts, + args: CreateSessionInstructionArgs, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = CreateSessionStruct.serialize({ + instructionDiscriminator: createSessionInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.payer, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.wallet, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.adminAuthority, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.session, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.systemProgram ?? web3.SystemProgram.programId, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.rent ?? web3.SYSVAR_RENT_PUBKEY, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.config, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.treasuryShard, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.authorizerSigner ?? programId, + isWritable: false, + isSigner: accounts.authorizerSigner != null, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/CreateWallet.ts b/sdk/solita-client/src/generated/instructions/CreateWallet.ts new file mode 100644 index 0000000..0711db6 --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/CreateWallet.ts @@ -0,0 +1,137 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category CreateWallet + * @category generated + */ +export type CreateWalletInstructionArgs = { + userSeed: number[] /* size: 32 */ + authType: number + authBump: number + padding: number[] /* size: 6 */ + payload: Uint8Array +} +/** + * @category Instructions + * @category CreateWallet + * @category generated + */ +export const CreateWalletStruct = new beet.FixableBeetArgsStruct< + CreateWalletInstructionArgs & { + instructionDiscriminator: number + } +>( + [ + ['instructionDiscriminator', beet.u8], + ['userSeed', beet.uniformFixedSizeArray(beet.u8, 32)], + ['authType', beet.u8], + ['authBump', beet.u8], + ['padding', beet.uniformFixedSizeArray(beet.u8, 6)], + ['payload', beet.bytes], + ], + 'CreateWalletInstructionArgs' +) +/** + * Accounts required by the _CreateWallet_ instruction + * + * @property [_writable_, **signer**] payer + * @property [_writable_] wallet + * @property [_writable_] vault + * @property [_writable_] authority + * @property [] config + * @property [_writable_] treasuryShard + * @category Instructions + * @category CreateWallet + * @category generated + */ +export type CreateWalletInstructionAccounts = { + payer: web3.PublicKey + wallet: web3.PublicKey + vault: web3.PublicKey + authority: web3.PublicKey + systemProgram?: web3.PublicKey + rent?: web3.PublicKey + config: web3.PublicKey + treasuryShard: web3.PublicKey +} + +export const createWalletInstructionDiscriminator = 0 + +/** + * Creates a _CreateWallet_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category CreateWallet + * @category generated + */ +export function createCreateWalletInstruction( + accounts: CreateWalletInstructionAccounts, + args: CreateWalletInstructionArgs, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = CreateWalletStruct.serialize({ + instructionDiscriminator: createWalletInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.payer, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.wallet, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.vault, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.authority, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.systemProgram ?? web3.SystemProgram.programId, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.rent ?? web3.SYSVAR_RENT_PUBKEY, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.config, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.treasuryShard, + isWritable: true, + isSigner: false, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/Execute.ts b/sdk/solita-client/src/generated/instructions/Execute.ts new file mode 100644 index 0000000..3014f19 --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/Execute.ts @@ -0,0 +1,133 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category Execute + * @category generated + */ +export type ExecuteInstructionArgs = { + instructions: Uint8Array +} +/** + * @category Instructions + * @category Execute + * @category generated + */ +export const ExecuteStruct = new beet.FixableBeetArgsStruct< + ExecuteInstructionArgs & { + instructionDiscriminator: number + } +>( + [ + ['instructionDiscriminator', beet.u8], + ['instructions', beet.bytes], + ], + 'ExecuteInstructionArgs' +) +/** + * Accounts required by the _Execute_ instruction + * + * @property [_writable_, **signer**] payer + * @property [] wallet + * @property [] authority + * @property [_writable_] vault + * @property [] config + * @property [_writable_] treasuryShard + * @property [] sysvarInstructions (optional) + * @category Instructions + * @category Execute + * @category generated + */ +export type ExecuteInstructionAccounts = { + payer: web3.PublicKey + wallet: web3.PublicKey + authority: web3.PublicKey + vault: web3.PublicKey + config: web3.PublicKey + treasuryShard: web3.PublicKey + systemProgram?: web3.PublicKey + sysvarInstructions?: web3.PublicKey +} + +export const executeInstructionDiscriminator = 4 + +/** + * Creates a _Execute_ instruction. + * + * Optional accounts that are not provided default to the program ID since + * this was indicated in the IDL from which this instruction was generated. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category Execute + * @category generated + */ +export function createExecuteInstruction( + accounts: ExecuteInstructionAccounts, + args: ExecuteInstructionArgs, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = ExecuteStruct.serialize({ + instructionDiscriminator: executeInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.payer, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.wallet, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.authority, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.vault, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.config, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.treasuryShard, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.systemProgram ?? web3.SystemProgram.programId, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.sysvarInstructions ?? programId, + isWritable: false, + isSigner: false, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/InitTreasuryShard.ts b/sdk/solita-client/src/generated/instructions/InitTreasuryShard.ts new file mode 100644 index 0000000..f1f65a7 --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/InitTreasuryShard.ts @@ -0,0 +1,108 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category InitTreasuryShard + * @category generated + */ +export type InitTreasuryShardInstructionArgs = { + shardId: number +} +/** + * @category Instructions + * @category InitTreasuryShard + * @category generated + */ +export const InitTreasuryShardStruct = new beet.BeetArgsStruct< + InitTreasuryShardInstructionArgs & { + instructionDiscriminator: number + } +>( + [ + ['instructionDiscriminator', beet.u8], + ['shardId', beet.u8], + ], + 'InitTreasuryShardInstructionArgs' +) +/** + * Accounts required by the _InitTreasuryShard_ instruction + * + * @property [_writable_, **signer**] payer + * @property [] config + * @property [_writable_] treasuryShard + * @category Instructions + * @category InitTreasuryShard + * @category generated + */ +export type InitTreasuryShardInstructionAccounts = { + payer: web3.PublicKey + config: web3.PublicKey + treasuryShard: web3.PublicKey + systemProgram?: web3.PublicKey + rent?: web3.PublicKey +} + +export const initTreasuryShardInstructionDiscriminator = 11 + +/** + * Creates a _InitTreasuryShard_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category InitTreasuryShard + * @category generated + */ +export function createInitTreasuryShardInstruction( + accounts: InitTreasuryShardInstructionAccounts, + args: InitTreasuryShardInstructionArgs, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = InitTreasuryShardStruct.serialize({ + instructionDiscriminator: initTreasuryShardInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.payer, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.config, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.treasuryShard, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.systemProgram ?? web3.SystemProgram.programId, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.rent ?? web3.SYSVAR_RENT_PUBKEY, + isWritable: false, + isSigner: false, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/InitializeConfig.ts b/sdk/solita-client/src/generated/instructions/InitializeConfig.ts new file mode 100644 index 0000000..a82d17c --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/InitializeConfig.ts @@ -0,0 +1,105 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category InitializeConfig + * @category generated + */ +export type InitializeConfigInstructionArgs = { + walletFee: beet.bignum + actionFee: beet.bignum + numShards: number +} +/** + * @category Instructions + * @category InitializeConfig + * @category generated + */ +export const InitializeConfigStruct = new beet.BeetArgsStruct< + InitializeConfigInstructionArgs & { + instructionDiscriminator: number + } +>( + [ + ['instructionDiscriminator', beet.u8], + ['walletFee', beet.u64], + ['actionFee', beet.u64], + ['numShards', beet.u8], + ], + 'InitializeConfigInstructionArgs' +) +/** + * Accounts required by the _InitializeConfig_ instruction + * + * @property [_writable_, **signer**] admin + * @property [_writable_] config + * @category Instructions + * @category InitializeConfig + * @category generated + */ +export type InitializeConfigInstructionAccounts = { + admin: web3.PublicKey + config: web3.PublicKey + systemProgram?: web3.PublicKey + rent?: web3.PublicKey +} + +export const initializeConfigInstructionDiscriminator = 6 + +/** + * Creates a _InitializeConfig_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category InitializeConfig + * @category generated + */ +export function createInitializeConfigInstruction( + accounts: InitializeConfigInstructionAccounts, + args: InitializeConfigInstructionArgs, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = InitializeConfigStruct.serialize({ + instructionDiscriminator: initializeConfigInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.admin, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.config, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.systemProgram ?? web3.SystemProgram.programId, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.rent ?? web3.SYSVAR_RENT_PUBKEY, + isWritable: false, + isSigner: false, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/RemoveAuthority.ts b/sdk/solita-client/src/generated/instructions/RemoveAuthority.ts new file mode 100644 index 0000000..e006123 --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/RemoveAuthority.ts @@ -0,0 +1,120 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category RemoveAuthority + * @category generated + */ +export const RemoveAuthorityStruct = new beet.BeetArgsStruct<{ + instructionDiscriminator: number +}>([['instructionDiscriminator', beet.u8]], 'RemoveAuthorityInstructionArgs') +/** + * Accounts required by the _RemoveAuthority_ instruction + * + * @property [_writable_, **signer**] payer + * @property [] wallet + * @property [_writable_] adminAuthority + * @property [_writable_] targetAuthority + * @property [_writable_] refundDestination + * @property [] config + * @property [_writable_] treasuryShard + * @property [**signer**] authorizerSigner (optional) + * @category Instructions + * @category RemoveAuthority + * @category generated + */ +export type RemoveAuthorityInstructionAccounts = { + payer: web3.PublicKey + wallet: web3.PublicKey + adminAuthority: web3.PublicKey + targetAuthority: web3.PublicKey + refundDestination: web3.PublicKey + systemProgram?: web3.PublicKey + config: web3.PublicKey + treasuryShard: web3.PublicKey + authorizerSigner?: web3.PublicKey +} + +export const removeAuthorityInstructionDiscriminator = 2 + +/** + * Creates a _RemoveAuthority_ instruction. + * + * Optional accounts that are not provided default to the program ID since + * this was indicated in the IDL from which this instruction was generated. + * + * @param accounts that will be accessed while the instruction is processed + * @category Instructions + * @category RemoveAuthority + * @category generated + */ +export function createRemoveAuthorityInstruction( + accounts: RemoveAuthorityInstructionAccounts, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = RemoveAuthorityStruct.serialize({ + instructionDiscriminator: removeAuthorityInstructionDiscriminator, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.payer, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.wallet, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.adminAuthority, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.targetAuthority, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.refundDestination, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.systemProgram ?? web3.SystemProgram.programId, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.config, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.treasuryShard, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.authorizerSigner ?? programId, + isWritable: false, + isSigner: accounts.authorizerSigner != null, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/SweepTreasury.ts b/sdk/solita-client/src/generated/instructions/SweepTreasury.ts new file mode 100644 index 0000000..13d6d16 --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/SweepTreasury.ts @@ -0,0 +1,103 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category SweepTreasury + * @category generated + */ +export type SweepTreasuryInstructionArgs = { + shardId: number +} +/** + * @category Instructions + * @category SweepTreasury + * @category generated + */ +export const SweepTreasuryStruct = new beet.BeetArgsStruct< + SweepTreasuryInstructionArgs & { + instructionDiscriminator: number + } +>( + [ + ['instructionDiscriminator', beet.u8], + ['shardId', beet.u8], + ], + 'SweepTreasuryInstructionArgs' +) +/** + * Accounts required by the _SweepTreasury_ instruction + * + * @property [**signer**] admin + * @property [] config + * @property [_writable_] treasuryShard + * @property [_writable_] destination + * @category Instructions + * @category SweepTreasury + * @category generated + */ +export type SweepTreasuryInstructionAccounts = { + admin: web3.PublicKey + config: web3.PublicKey + treasuryShard: web3.PublicKey + destination: web3.PublicKey +} + +export const sweepTreasuryInstructionDiscriminator = 10 + +/** + * Creates a _SweepTreasury_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category SweepTreasury + * @category generated + */ +export function createSweepTreasuryInstruction( + accounts: SweepTreasuryInstructionAccounts, + args: SweepTreasuryInstructionArgs, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = SweepTreasuryStruct.serialize({ + instructionDiscriminator: sweepTreasuryInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.admin, + isWritable: false, + isSigner: true, + }, + { + pubkey: accounts.config, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.treasuryShard, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.destination, + isWritable: true, + isSigner: false, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/TransferOwnership.ts b/sdk/solita-client/src/generated/instructions/TransferOwnership.ts new file mode 100644 index 0000000..e69d5b1 --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/TransferOwnership.ts @@ -0,0 +1,135 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category TransferOwnership + * @category generated + */ +export type TransferOwnershipInstructionArgs = { + newType: number + payload: Uint8Array +} +/** + * @category Instructions + * @category TransferOwnership + * @category generated + */ +export const TransferOwnershipStruct = new beet.FixableBeetArgsStruct< + TransferOwnershipInstructionArgs & { + instructionDiscriminator: number + } +>( + [ + ['instructionDiscriminator', beet.u8], + ['newType', beet.u8], + ['payload', beet.bytes], + ], + 'TransferOwnershipInstructionArgs' +) +/** + * Accounts required by the _TransferOwnership_ instruction + * + * @property [_writable_, **signer**] payer + * @property [] wallet + * @property [_writable_] currentOwnerAuthority + * @property [_writable_] newOwnerAuthority + * @property [**signer**] authorizerSigner (optional) + * @property [] config + * @property [_writable_] treasuryShard + * @category Instructions + * @category TransferOwnership + * @category generated + */ +export type TransferOwnershipInstructionAccounts = { + payer: web3.PublicKey + wallet: web3.PublicKey + currentOwnerAuthority: web3.PublicKey + newOwnerAuthority: web3.PublicKey + systemProgram?: web3.PublicKey + authorizerSigner?: web3.PublicKey + config: web3.PublicKey + treasuryShard: web3.PublicKey +} + +export const transferOwnershipInstructionDiscriminator = 3 + +/** + * Creates a _TransferOwnership_ instruction. + * + * Optional accounts that are not provided default to the program ID since + * this was indicated in the IDL from which this instruction was generated. + * + * @param accounts that will be accessed while the instruction is processed + * @param args to provide as instruction data to the program + * + * @category Instructions + * @category TransferOwnership + * @category generated + */ +export function createTransferOwnershipInstruction( + accounts: TransferOwnershipInstructionAccounts, + args: TransferOwnershipInstructionArgs, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = TransferOwnershipStruct.serialize({ + instructionDiscriminator: transferOwnershipInstructionDiscriminator, + ...args, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.payer, + isWritable: true, + isSigner: true, + }, + { + pubkey: accounts.wallet, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.currentOwnerAuthority, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.newOwnerAuthority, + isWritable: true, + isSigner: false, + }, + { + pubkey: accounts.systemProgram ?? web3.SystemProgram.programId, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.authorizerSigner ?? programId, + isWritable: false, + isSigner: accounts.authorizerSigner != null, + }, + { + pubkey: accounts.config, + isWritable: false, + isSigner: false, + }, + { + pubkey: accounts.treasuryShard, + isWritable: true, + isSigner: false, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/UpdateConfig.ts b/sdk/solita-client/src/generated/instructions/UpdateConfig.ts new file mode 100644 index 0000000..89df70f --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/UpdateConfig.ts @@ -0,0 +1,69 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +import * as web3 from '@solana/web3.js' + +/** + * @category Instructions + * @category UpdateConfig + * @category generated + */ +export const UpdateConfigStruct = new beet.BeetArgsStruct<{ + instructionDiscriminator: number +}>([['instructionDiscriminator', beet.u8]], 'UpdateConfigInstructionArgs') +/** + * Accounts required by the _UpdateConfig_ instruction + * + * @property [**signer**] admin + * @property [_writable_] config + * @category Instructions + * @category UpdateConfig + * @category generated + */ +export type UpdateConfigInstructionAccounts = { + admin: web3.PublicKey + config: web3.PublicKey +} + +export const updateConfigInstructionDiscriminator = 7 + +/** + * Creates a _UpdateConfig_ instruction. + * + * @param accounts that will be accessed while the instruction is processed + * @category Instructions + * @category UpdateConfig + * @category generated + */ +export function createUpdateConfigInstruction( + accounts: UpdateConfigInstructionAccounts, + programId = new web3.PublicKey('DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2') +) { + const [data] = UpdateConfigStruct.serialize({ + instructionDiscriminator: updateConfigInstructionDiscriminator, + }) + const keys: web3.AccountMeta[] = [ + { + pubkey: accounts.admin, + isWritable: false, + isSigner: true, + }, + { + pubkey: accounts.config, + isWritable: true, + isSigner: false, + }, + ] + + const ix = new web3.TransactionInstruction({ + programId, + keys, + data, + }) + return ix +} diff --git a/sdk/solita-client/src/generated/instructions/index.ts b/sdk/solita-client/src/generated/instructions/index.ts new file mode 100644 index 0000000..d1cd1e7 --- /dev/null +++ b/sdk/solita-client/src/generated/instructions/index.ts @@ -0,0 +1,12 @@ +export * from './AddAuthority' +export * from './CloseSession' +export * from './CloseWallet' +export * from './CreateSession' +export * from './CreateWallet' +export * from './Execute' +export * from './InitTreasuryShard' +export * from './InitializeConfig' +export * from './RemoveAuthority' +export * from './SweepTreasury' +export * from './TransferOwnership' +export * from './UpdateConfig' diff --git a/sdk/solita-client/src/generated/types/AuthorityType.ts b/sdk/solita-client/src/generated/types/AuthorityType.ts new file mode 100644 index 0000000..5bf92c6 --- /dev/null +++ b/sdk/solita-client/src/generated/types/AuthorityType.ts @@ -0,0 +1,24 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +/** + * @category enums + * @category generated + */ +export enum AuthorityType { + Ed25519, + Secp256r1, +} + +/** + * @category userTypes + * @category generated + */ +export const authorityTypeBeet = beet.fixedScalarEnum( + AuthorityType +) as beet.FixedSizeBeet diff --git a/sdk/solita-client/src/generated/types/Role.ts b/sdk/solita-client/src/generated/types/Role.ts new file mode 100644 index 0000000..f308b70 --- /dev/null +++ b/sdk/solita-client/src/generated/types/Role.ts @@ -0,0 +1,26 @@ +/** + * This code was GENERATED using the solita package. + * Please DO NOT EDIT THIS FILE, instead rerun solita to update it or write a wrapper to add functionality. + * + * See: https://github.com/metaplex-foundation/solita + */ + +import * as beet from '@metaplex-foundation/beet' +/** + * @category enums + * @category generated + */ +export enum Role { + Owner, + Admin, + Spender, +} + +/** + * @category userTypes + * @category generated + */ +export const roleBeet = beet.fixedScalarEnum(Role) as beet.FixedSizeBeet< + Role, + Role +> diff --git a/sdk/solita-client/src/generated/types/index.ts b/sdk/solita-client/src/generated/types/index.ts new file mode 100644 index 0000000..c6d63f5 --- /dev/null +++ b/sdk/solita-client/src/generated/types/index.ts @@ -0,0 +1,2 @@ +export * from './AuthorityType' +export * from './Role' diff --git a/sdk/solita-client/src/index.ts b/sdk/solita-client/src/index.ts new file mode 100644 index 0000000..7c79a3b --- /dev/null +++ b/sdk/solita-client/src/index.ts @@ -0,0 +1,24 @@ +/** + * LazorKit TypeScript SDK — web3.js v1 (Legacy) Edition + * + * Auto-generated instruction builders from Solita, + * plus handwritten utilities for PDA derivation, + * compact instruction packing, and the LazorWeb3Client wrapper. + */ + +// Auto-generated from Solita (instructions, program constants) +export * from './generated'; + +// Handwritten utilities +export { + findWalletPda, + findVaultPda, + findAuthorityPda, + findSessionPda, + findConfigPda, + findTreasuryShardPda, +} from './utils/pdas'; +export * from './utils/packing'; +export { LazorInstructionBuilder } from './utils/client'; +export * from './utils/wrapper'; +export * from './utils/secp256r1'; diff --git a/sdk/solita-client/src/utils/client.ts b/sdk/solita-client/src/utils/client.ts new file mode 100644 index 0000000..fbec57d --- /dev/null +++ b/sdk/solita-client/src/utils/client.ts @@ -0,0 +1,654 @@ +/** + * LazorInstructionBuilder — Low-level wrapper for LazorKit instructions using @solana/web3.js v1. + * + * IMPLEMENTATION NOTE: + * We manually encode instruction data for instructions with `bytes` fields (CreateWallet, + * AddAuthority, TransferOwnership, Execute) because the Solita-generated serializers + * use `beet.bytes` which adds a 4-byte length prefix, but the LazorKit contract + * expects raw fixed-size byte arrays (C-struct style). + */ + +import { + PublicKey, + TransactionInstruction, + SystemProgram, + SYSVAR_RENT_PUBKEY, + type AccountMeta, +} from '@solana/web3.js'; + +import { + createCloseWalletInstruction, + createCreateSessionInstruction, + createCloseSessionInstruction, + createRemoveAuthorityInstruction, + createInitializeConfigInstruction, + createInitTreasuryShardInstruction, + createSweepTreasuryInstruction, + PROGRAM_ID, +} from '../generated'; + +import { packCompactInstructions, type CompactInstruction } from './packing'; +import { findAuthorityPda } from './pdas'; + +export class LazorInstructionBuilder { + constructor(private programId: PublicKey = PROGRAM_ID) {} + + private getAuthPayload( + authType: number, + authPubkey: Uint8Array, + credentialHash: Uint8Array, + ): Uint8Array { + if (authType === 1) { + // Secp256r1 + // 32 bytes hash + 33 bytes key + const payload = new Uint8Array(65); + payload.set(credentialHash.slice(0, 32), 0); + payload.set(authPubkey.slice(0, 33), 32); + return payload; + } else { + // Ed25519 + // 32 bytes key + return new Uint8Array(authPubkey.slice(0, 32)); + } + } + + // ─── Wallet ────────────────────────────────────────────────────── + + /** + * CreateWallet — manually serializes instruction data to avoid Solita's bytes prefix. + * + * On-chain layout: + * [disc: u8(0)] // offset 0 + * [userSeed: 32] // offset 1 + * [authType: u8] // offset 33 + * [authBump: u8] // offset 34 + * [padding: 6] // offset 35 (6 bytes to align to 8-byte boundary for AuthPayload enum) + * [payload: bytes] // offset 41 (Ed25519=32 bytes, Secp256r1=65 bytes) + */ + createWallet(params: { + payer: PublicKey; + wallet: PublicKey; + vault: PublicKey; + authority: PublicKey; + config: PublicKey; + treasuryShard: PublicKey; + userSeed: Uint8Array; + authType: number; + authBump?: number; + authPubkey: Uint8Array; + credentialHash?: Uint8Array; + }): TransactionInstruction { + const authBump = params.authBump || 0; + const padding = new Uint8Array(6).fill(0); + const payload = this.getAuthPayload( + params.authType, + params.authPubkey, + params.credentialHash || new Uint8Array(32), + ); + + const data = Buffer.alloc(1 + 32 + 1 + 1 + 6 + payload.length); + let offset = 0; + + data.writeUInt8(0, offset); + offset += 1; // disc + data.set(params.userSeed.slice(0, 32), offset); + offset += 32; + data.writeUInt8(params.authType, offset); + offset += 1; + data.writeUInt8(authBump, offset); + offset += 1; + data.set(padding, offset); + offset += 6; + data.set(payload, offset); + + const keys: AccountMeta[] = [ + { pubkey: params.payer, isWritable: true, isSigner: true }, + { pubkey: params.wallet, isWritable: true, isSigner: false }, + { pubkey: params.vault, isWritable: true, isSigner: false }, + { pubkey: params.authority, isWritable: true, isSigner: false }, + { pubkey: SystemProgram.programId, isWritable: false, isSigner: false }, + { pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false }, + { pubkey: params.config, isWritable: false, isSigner: false }, + { pubkey: params.treasuryShard, isWritable: true, isSigner: false }, + ]; + + return new TransactionInstruction({ + programId: this.programId, + keys, + data, + }); + } + + closeWallet(params: { + payer: PublicKey; + wallet: PublicKey; + vault: PublicKey; + ownerAuthority: PublicKey; + destination: PublicKey; + ownerSigner?: PublicKey; + sysvarInstructions?: PublicKey; + }): TransactionInstruction { + return createCloseWalletInstruction( + { + payer: params.payer, + wallet: params.wallet, + vault: params.vault, + ownerAuthority: params.ownerAuthority, + destination: params.destination, + ownerSigner: params.ownerSigner, + sysvarInstructions: params.sysvarInstructions, + }, + this.programId, + ); + } + + // ─── Authority ─────────────────────────────────────────────────── + + /** + * AddAuthority — manually serializes to avoid prefix. + * + * On-chain layout: + * [disc: u8(1)] // offset 0 + * [newAuthType: u8] // offset 1 + * [newRole: u8] // offset 2 + * [padding: 6] // offset 3 + * [payload: bytes] // offset 9 (Ed25519=32 bytes, Secp256r1=65 bytes) + */ + addAuthority(params: { + payer: PublicKey; + wallet: PublicKey; + adminAuthority: PublicKey; + newAuthority: PublicKey; + config: PublicKey; + treasuryShard: PublicKey; + newAuthType: number; + newRole: number; + newAuthPubkey: Uint8Array; + newCredentialHash?: Uint8Array; + authorizerSigner?: PublicKey; + }): TransactionInstruction { + const padding = new Uint8Array(6).fill(0); + const payload = this.getAuthPayload( + params.newAuthType, + params.newAuthPubkey, + params.newCredentialHash || new Uint8Array(32), + ); + + const data = Buffer.alloc(1 + 1 + 1 + 6 + payload.length); + let offset = 0; + + data.writeUInt8(1, offset); + offset += 1; // disc + data.writeUInt8(params.newAuthType, offset); + offset += 1; + data.writeUInt8(params.newRole, offset); + offset += 1; + data.set(padding, offset); + offset += 6; + data.set(payload, offset); + + const keys: AccountMeta[] = [ + { pubkey: params.payer, isWritable: true, isSigner: true }, + { pubkey: params.wallet, isWritable: false, isSigner: false }, + { pubkey: params.adminAuthority, isWritable: true, isSigner: false }, + { pubkey: params.newAuthority, isWritable: true, isSigner: false }, + { pubkey: SystemProgram.programId, isWritable: false, isSigner: false }, + { pubkey: params.config, isWritable: false, isSigner: false }, + { pubkey: params.treasuryShard, isWritable: true, isSigner: false }, + ]; + + if (params.authorizerSigner) { + keys.push({ + pubkey: params.authorizerSigner, + isWritable: false, + isSigner: true, + }); + } + + return new TransactionInstruction({ + programId: this.programId, + keys, + data, + }); + } + + removeAuthority(params: { + payer: PublicKey; + wallet: PublicKey; + adminAuthority: PublicKey; + targetAuthority: PublicKey; + refundDestination: PublicKey; + config: PublicKey; + treasuryShard: PublicKey; + authorizerSigner?: PublicKey; + }): TransactionInstruction { + return createRemoveAuthorityInstruction( + { + payer: params.payer, + wallet: params.wallet, + adminAuthority: params.adminAuthority, + targetAuthority: params.targetAuthority, + refundDestination: params.refundDestination, + config: params.config, + treasuryShard: params.treasuryShard, + systemProgram: SystemProgram.programId, + authorizerSigner: params.authorizerSigner, + }, + this.programId, + ); + } + + /** + * TransferOwnership — manually serializes to avoid prefix. + * + * On-chain layout: + * [disc: u8(3)] // offset 0 + * [newAuthType: u8] // offset 1 + * [payload: bytes] // offset 2 (Ed25519=32 bytes, Secp256r1=65 bytes) + * Note: No padding is used here because the Rust struct `TransferOwnershipArgs` + * packs `new_owner_authority` (`AuthPayload` enum) right after `new_auth_type`. + */ + transferOwnership(params: { + payer: PublicKey; + wallet: PublicKey; + currentOwnerAuthority: PublicKey; + newOwnerAuthority: PublicKey; + config: PublicKey; + treasuryShard: PublicKey; + newAuthType: number; + newAuthPubkey: Uint8Array; + newCredentialHash?: Uint8Array; + authorizerSigner?: PublicKey; + }): TransactionInstruction { + const payload = this.getAuthPayload( + params.newAuthType, + params.newAuthPubkey, + params.newCredentialHash || new Uint8Array(32), + ); + + const data = Buffer.alloc(1 + 1 + payload.length); + let offset = 0; + + data.writeUInt8(3, offset); + offset += 1; // disc + data.writeUInt8(params.newAuthType, offset); + offset += 1; + data.set(payload, offset); + + const keys: AccountMeta[] = [ + { pubkey: params.payer, isWritable: true, isSigner: true }, + { pubkey: params.wallet, isWritable: false, isSigner: false }, + { + pubkey: params.currentOwnerAuthority, + isWritable: true, + isSigner: false, + }, + { pubkey: params.newOwnerAuthority, isWritable: true, isSigner: false }, + { pubkey: SystemProgram.programId, isWritable: false, isSigner: false }, + { pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false }, + { pubkey: params.config, isWritable: false, isSigner: false }, + { pubkey: params.treasuryShard, isWritable: true, isSigner: false }, + ]; + + if (params.authorizerSigner) { + keys.push({ + pubkey: params.authorizerSigner, + isWritable: false, + isSigner: true, + }); + } + + return new TransactionInstruction({ + programId: this.programId, + keys, + data, + }); + } + + // ─── Session ───────────────────────────────────────────────────── + + createSession(params: { + payer: PublicKey; + wallet: PublicKey; + adminAuthority: PublicKey; + session: PublicKey; + config: PublicKey; + treasuryShard: PublicKey; + sessionKey: Uint8Array | number[]; + expiresAt: bigint | number; + authorizerSigner?: PublicKey; + }): TransactionInstruction { + const sessionKeyArr = Array.isArray(params.sessionKey) + ? params.sessionKey + : Array.from(params.sessionKey); + + return createCreateSessionInstruction( + { + payer: params.payer, + wallet: params.wallet, + adminAuthority: params.adminAuthority, + session: params.session, + config: params.config, + treasuryShard: params.treasuryShard, + systemProgram: SystemProgram.programId, + authorizerSigner: params.authorizerSigner, + }, + { + sessionKey: sessionKeyArr as number[], + expiresAt: BigInt(params.expiresAt), + }, + this.programId, + ); + } + + closeSession(params: { + payer: PublicKey; + wallet: PublicKey; + session: PublicKey; + config: PublicKey; + authorizer?: PublicKey; + authorizerSigner?: PublicKey; + sysvarInstructions?: PublicKey; + }): TransactionInstruction { + return createCloseSessionInstruction( + { + payer: params.payer, + wallet: params.wallet, + session: params.session, + config: params.config, + authorizer: params.authorizer, + authorizerSigner: params.authorizerSigner, + sysvarInstructions: params.sysvarInstructions, + }, + this.programId, + ); + } + + // ─── Execute ───────────────────────────────────────────────────── + + /** + * Execute — manually builds instruction data. + * Layout: [disc: u8(4)] [instructions: PackedBytes] + */ + execute(params: { + payer: PublicKey; + wallet: PublicKey; + authority: PublicKey; + vault: PublicKey; + config: PublicKey; + treasuryShard: PublicKey; + packedInstructions: Uint8Array; + authorizerSigner?: PublicKey; + sysvarInstructions?: PublicKey; + remainingAccounts?: AccountMeta[]; + }): TransactionInstruction { + const data = Buffer.alloc(1 + params.packedInstructions.length); + data[0] = 4; // disc + data.set(params.packedInstructions, 1); + + const keys: AccountMeta[] = [ + { pubkey: params.payer, isWritable: true, isSigner: true }, + { pubkey: params.wallet, isWritable: false, isSigner: false }, + { pubkey: params.authority, isWritable: true, isSigner: false }, + { pubkey: params.vault, isWritable: true, isSigner: false }, + { pubkey: params.config, isWritable: false, isSigner: false }, + { pubkey: params.treasuryShard, isWritable: true, isSigner: false }, + { pubkey: SystemProgram.programId, isWritable: false, isSigner: false }, + ]; + + if (params.sysvarInstructions) { + keys.push({ + pubkey: params.sysvarInstructions, + isWritable: false, + isSigner: false, + }); + } + + if (params.remainingAccounts) { + keys.push(...params.remainingAccounts); + } + + if (params.authorizerSigner) { + keys.push({ + pubkey: params.authorizerSigner, + isWritable: false, + isSigner: true, + }); + } + + return new TransactionInstruction({ + programId: this.programId, + keys, + data, + }); + } + + /** + * buildExecute — High-level builder that deduplicates and maps accounts. + */ + buildExecute(params: { + payer: PublicKey; + wallet: PublicKey; + authority: PublicKey; + vault: PublicKey; + config: PublicKey; + treasuryShard: PublicKey; + innerInstructions: TransactionInstruction[]; + authorizerSigner?: PublicKey; + signature?: Uint8Array; + sysvarInstructions?: PublicKey; + }): TransactionInstruction { + const baseAccounts: PublicKey[] = [ + params.payer, + params.wallet, + params.authority, + params.vault, + params.config, + params.treasuryShard, + SystemProgram.programId, + ]; + + const accountMap = new Map(); + const accountMetas: AccountMeta[] = []; + + baseAccounts.forEach((pk, idx) => { + accountMap.set(pk.toBase58(), idx); + accountMetas.push({ + pubkey: pk, + isWritable: idx === 0 || idx === 2 || idx === 3 || idx === 5, + isSigner: idx === 0, + }); + }); + + const vaultKey = params.vault.toBase58(); + const walletKey = params.wallet.toBase58(); + + const addAccount = ( + pubkey: PublicKey, + isSigner: boolean, + isWritable: boolean, + ): number => { + const key = pubkey.toBase58(); + if (key === vaultKey || key === walletKey) isSigner = false; + + if (!accountMap.has(key)) { + const idx = accountMetas.length; + accountMap.set(key, idx); + accountMetas.push({ pubkey, isWritable, isSigner }); + return idx; + } else { + const idx = accountMap.get(key)!; + const existing = accountMetas[idx]; + if (isWritable) existing.isWritable = true; + if (isSigner) existing.isSigner = true; + return idx; + } + }; + + const compactIxs: CompactInstruction[] = []; + for (const ix of params.innerInstructions) { + const programIdIndex = addAccount(ix.programId, false, false); + const accountIndexes: number[] = []; + for (const acc of ix.keys) { + accountIndexes.push( + addAccount(acc.pubkey, acc.isSigner, acc.isWritable), + ); + } + compactIxs.push({ programIdIndex, accountIndexes, data: ix.data }); + } + + const packed = packCompactInstructions(compactIxs); + const sig = params.signature; + const dataSize = 1 + packed.length + (sig ? sig.length : 0); + const data = Buffer.alloc(dataSize); + data[0] = 4; // disc + data.set(packed, 1); + if (sig) data.set(sig, 1 + packed.length); + + if (params.sysvarInstructions) { + addAccount(params.sysvarInstructions, false, false); + } + + if (params.authorizerSigner) { + addAccount(params.authorizerSigner, true, false); + } + + return new TransactionInstruction({ + programId: this.programId, + keys: accountMetas, + data, + }); + } + + // ─── Admin ─────────────────────────────────────────────────────── + + initializeConfig(params: { + admin: PublicKey; + config: PublicKey; + walletFee: bigint | number; + actionFee: bigint | number; + numShards: number; + }): TransactionInstruction { + return createInitializeConfigInstruction( + { + admin: params.admin, + config: params.config, + systemProgram: SystemProgram.programId, + rent: SYSVAR_RENT_PUBKEY, + }, + { + walletFee: BigInt(params.walletFee), + actionFee: BigInt(params.actionFee), + numShards: params.numShards, + }, + this.programId, + ); + } + + updateConfig(params: { + admin: PublicKey; + config: PublicKey; + walletFee?: bigint | number; + actionFee?: bigint | number; + numShards?: number; + newAdmin?: PublicKey; + }): TransactionInstruction { + const data = Buffer.alloc(57); + data[0] = 7; // UpdateConfig discriminator + + const updateWalletFee = params.walletFee !== undefined ? 1 : 0; + const updateActionFee = params.actionFee !== undefined ? 1 : 0; + const updateNumShards = params.numShards !== undefined ? 1 : 0; + const updateAdmin = params.newAdmin !== undefined ? 1 : 0; + const numShards = params.numShards ?? 0; + + data.writeUInt8(updateWalletFee, 1); + data.writeUInt8(updateActionFee, 2); + data.writeUInt8(updateNumShards, 3); + data.writeUInt8(updateAdmin, 4); + data.writeUInt8(numShards, 5); + // Padding 3 bytes (indices 6, 7, 8) are already 0 from alloc + + const view = new DataView(data.buffer, data.byteOffset, data.byteLength); + view.setBigUint64(9, BigInt(params.walletFee ?? 0), true); + view.setBigUint64(17, BigInt(params.actionFee ?? 0), true); + + if (params.newAdmin) { + data.set(params.newAdmin.toBytes(), 25); + } + + const keys: AccountMeta[] = [ + { pubkey: params.admin, isWritable: false, isSigner: true }, + { pubkey: params.config, isWritable: true, isSigner: false }, + ]; + + return new TransactionInstruction({ + programId: this.programId, + keys, + data, + }); + } + + initTreasuryShard(params: { + payer: PublicKey; + config: PublicKey; + treasuryShard: PublicKey; + shardId: number; + }): TransactionInstruction { + return createInitTreasuryShardInstruction( + { + payer: params.payer, + config: params.config, + treasuryShard: params.treasuryShard, + systemProgram: SystemProgram.programId, + rent: SYSVAR_RENT_PUBKEY, + }, + { + shardId: params.shardId, + }, + this.programId, + ); + } + + sweepTreasury(params: { + admin: PublicKey; + config: PublicKey; + treasuryShard: PublicKey; + destination: PublicKey; + shardId: number; + }): TransactionInstruction { + return createSweepTreasuryInstruction( + { + admin: params.admin, + config: params.config, + treasuryShard: params.treasuryShard, + destination: params.destination, + }, + { + shardId: params.shardId, + }, + this.programId, + ); + } + + // ─── Utility helpers ───────────────────────────────────────────── + + async getAuthorityByPublicKey( + connection: import('@solana/web3.js').Connection, + walletAddress: PublicKey, + pubkey: PublicKey, + ): Promise<{ address: PublicKey; data: Buffer } | null> { + const [pda] = findAuthorityPda( + walletAddress, + pubkey.toBytes(), + this.programId, + ); + try { + const accountInfo = await connection.getAccountInfo(pda); + if (!accountInfo) return null; + return { address: pda, data: accountInfo.data }; + } catch { + return null; + } + } +} diff --git a/sdk/solita-client/src/utils/packing.ts b/sdk/solita-client/src/utils/packing.ts new file mode 100644 index 0000000..aca7b1e --- /dev/null +++ b/sdk/solita-client/src/utils/packing.ts @@ -0,0 +1,108 @@ +/** + * Utility to pack instructions into the compact format expected by LazorKit's Execute instruction. + * + * Format: + * [num_instructions: u8] + * for each instruction: + * [program_id_index: u8] + * [num_accounts: u8] + * [account_indexes: u8[]] + * [data_len: u16 LE] + * [data: u8[]] + */ + +import { type AccountMeta } from '@solana/web3.js'; + +export interface CompactInstruction { + programIdIndex: number; + accountIndexes: number[]; + data: Uint8Array; +} + +/** + * Packs a list of compact instructions into a single buffer. + * Used by the Execute instruction to encode inner instructions. + */ +export function packCompactInstructions( + instructions: CompactInstruction[], +): Uint8Array { + if (instructions.length > 255) { + throw new Error('Too many instructions (max 255)'); + } + + const buffers: Uint8Array[] = []; + + // 1. Number of instructions + buffers.push(new Uint8Array([instructions.length])); + + for (const ix of instructions) { + // 2. Program ID index + number of accounts + if (ix.accountIndexes.length > 255) { + throw new Error('Too many accounts in an instruction (max 255)'); + } + buffers.push(new Uint8Array([ix.programIdIndex, ix.accountIndexes.length])); + + // 3. Account indexes + buffers.push(new Uint8Array(ix.accountIndexes)); + + // 4. Data length (u16 LE) + const dataLen = ix.data.length; + if (dataLen > 65535) { + throw new Error('Instruction data too large (max 65535 bytes)'); + } + buffers.push(new Uint8Array([dataLen & 0xff, (dataLen >> 8) & 0xff])); + + // 5. Data + buffers.push(ix.data); + } + + // Concatenate all buffers + const totalLength = buffers.reduce((acc, b) => acc + b.length, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + for (const b of buffers) { + result.set(b, offset); + offset += b.length; + } + + return result; +} + +/** + * Computes the SHA-256 hash of all account pubkeys referenced by compact instructions. + * This MUST match the contract's `compute_accounts_hash` exactly. + * + * @param accountMetas Array of absolute account metas used by the parent Execute instruction + * @param instructions List of packed compact instructions + */ +export async function computeAccountsHash( + accountMetas: AccountMeta[], + instructions: CompactInstruction[], +): Promise { + const pubkeysData: Uint8Array[] = []; + + for (const ix of instructions) { + const programId = accountMetas[ix.programIdIndex].pubkey; + pubkeysData.push(programId.toBytes()); + + for (const idx of ix.accountIndexes) { + if (idx >= accountMetas.length) { + throw new Error(`Account index out of bounds: ${idx}`); + } + pubkeysData.push(accountMetas[idx].pubkey.toBytes()); + } + } + + // Concatenate all pubkeys + const totalLength = pubkeysData.reduce((acc, b) => acc + b.length, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + for (const b of pubkeysData) { + result.set(b, offset); + offset += b.length; + } + + // Compute SHA-256 hash using Web Crypto API + const hashBuffer = await crypto.subtle.digest('SHA-256', result); + return new Uint8Array(hashBuffer); +} diff --git a/sdk/solita-client/src/utils/pdas.ts b/sdk/solita-client/src/utils/pdas.ts new file mode 100644 index 0000000..abdd29c --- /dev/null +++ b/sdk/solita-client/src/utils/pdas.ts @@ -0,0 +1,96 @@ +/** + * PDA derivation helpers for LazorKit accounts. + * + * Uses @solana/web3.js v1 PublicKey.findProgramAddressSync(). + * Same seed patterns as the codama-client v2 version. + */ + +import { PublicKey } from '@solana/web3.js'; + +export const PROGRAM_ID = new PublicKey( + 'FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao', +); + +/** + * Derives the Wallet PDA. + * Seeds: ["wallet", user_seed] + */ +export function findWalletPda( + userSeed: Uint8Array, + programId: PublicKey = PROGRAM_ID, +): [PublicKey, number] { + return PublicKey.findProgramAddressSync( + [Buffer.from('wallet'), userSeed], + programId, + ); +} + +/** + * Derives the Vault PDA. + * Seeds: ["vault", wallet_pubkey] + */ +export function findVaultPda( + wallet: PublicKey, + programId: PublicKey = PROGRAM_ID, +): [PublicKey, number] { + return PublicKey.findProgramAddressSync( + [Buffer.from('vault'), wallet.toBuffer()], + programId, + ); +} + +/** + * Derives an Authority PDA. + * Seeds: ["authority", wallet_pubkey, id_seed] + * @param idSeed - For Ed25519 this is the 32-byte public key. + * For Secp256r1 this is the 32-byte SHA256 Hash of the credential_id (rawId). + */ +export function findAuthorityPda( + wallet: PublicKey, + idSeed: Uint8Array, + programId: PublicKey = PROGRAM_ID, +): [PublicKey, number] { + return PublicKey.findProgramAddressSync( + [Buffer.from('authority'), wallet.toBuffer(), idSeed], + programId, + ); +} + +/** + * Derives a Session PDA. + * Seeds: ["session", wallet_pubkey, session_key_pubkey] + */ +export function findSessionPda( + wallet: PublicKey, + sessionKey: PublicKey, + programId: PublicKey = PROGRAM_ID, +): [PublicKey, number] { + return PublicKey.findProgramAddressSync( + [Buffer.from('session'), wallet.toBuffer(), sessionKey.toBuffer()], + programId, + ); +} + +/** + * Derives the Config PDA. + * Seeds: ["config"] + */ +export function findConfigPda( + programId: PublicKey = PROGRAM_ID, +): [PublicKey, number] { + return PublicKey.findProgramAddressSync([Buffer.from('config')], programId); +} + +/** + * Derives a Treasury Shard PDA. + * Seeds: ["treasury", shard_id] + */ +export function findTreasuryShardPda( + shardId: number, + programId: PublicKey = PROGRAM_ID, +): [PublicKey, number] { + return PublicKey.findProgramAddressSync( + [Buffer.from('treasury'), new Uint8Array([shardId])], + programId, + ); +} diff --git a/sdk/solita-client/src/utils/secp256r1.ts b/sdk/solita-client/src/utils/secp256r1.ts new file mode 100644 index 0000000..448842b --- /dev/null +++ b/sdk/solita-client/src/utils/secp256r1.ts @@ -0,0 +1,292 @@ +/** + * Secp256r1 utility helpers for LazorKit SDK. + * + * This module provides pure cryptographic building blocks for constructing + * Secp256r1 precompile instructions and auth payloads used by LazorKit's + * on-chain authentication flow. + * + * **These are NOT mocking utilities** — the `Secp256r1Signer` interface + * is designed to be implemented by callers with the real signing mechanism + * (hardware key, WebAuthn, etc.). For testing, see the test-specific + * `generateMockSecp256r1Signer` helper in the test suite. + */ + +import { PublicKey, TransactionInstruction, Connection } from '@solana/web3.js'; +// Remove node:crypto import +async function sha256(data: Uint8Array): Promise { + const hashBuffer = await crypto.subtle.digest( + 'SHA-256', + data as unknown as BufferSource, + ); + return new Uint8Array(hashBuffer); +} + +// ─── Types ─────────────────────────────────────────────────────────────────── + +/** + * Minimal interface a Secp256r1 signer must implement. + * The SDK does not depend on any specific crypto library. + */ +export interface Secp256r1Signer { + /** 33-byte compressed P-256 public key */ + publicKeyBytes: Uint8Array; + /** 32-byte SHA-256 hash of the WebAuthn credential ID */ + credentialIdHash: Uint8Array; + /** Sign a message, returning a 64-byte raw r‖s signature (low-S enforced) */ + sign(message: Uint8Array): Promise; +} + +/** Sysvar public keys used by LazorKit's Secp256r1 auth flow */ +export const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey( + 'Sysvar1nstructions1111111111111111111111111', +); +export const SYSVAR_SLOT_HASHES_PUBKEY = new PublicKey( + 'SysvarS1otHashes111111111111111111111111111', +); + +// ─── Sysvar helpers ─────────────────────────────────────────────────────────── + +/** + * Appends the two sysvars required by LazorKit's Secp256r1 auth to an + * instruction's account list. + * + * @returns The mutated instruction plus the indices of the two sysvars, + * which are needed when building the auth payload. + */ +export function appendSecp256r1Sysvars(ix: TransactionInstruction): { + ix: TransactionInstruction; + sysvarIxIndex: number; + sysvarSlotIndex: number; +} { + const sysvarIxIndex = ix.keys.length; + const sysvarSlotIndex = ix.keys.length + 1; + + ix.keys.push( + { pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false }, + { pubkey: SYSVAR_SLOT_HASHES_PUBKEY, isSigner: false, isWritable: false }, + ); + + return { ix, sysvarIxIndex, sysvarSlotIndex }; +} + +/** + * Reads the current slot number from the `SlotHashes` sysvar on-chain. + */ +export async function readCurrentSlot(connection: Connection): Promise { + const accountInfo = await connection.getAccountInfo( + SYSVAR_SLOT_HASHES_PUBKEY, + ); + if (!accountInfo) throw new Error('SlotHashes sysvar not found'); + const data = accountInfo.data; + return new DataView( + data.buffer, + data.byteOffset, + data.byteLength, + ).getBigUint64(8, true); +} + +// ─── Payload builders ───────────────────────────────────────────────────────── + +/** + * Builds the `AuthPayload` that encodes the Secp256r1 liveness proof context. + * + * The payload layout is: + * `[slot(8)] [sysvarIxIndex(1)] [sysvarSlotIndex(1)] [flags(1)] [rpIdLen(1)] [rpId(N)] [authenticatorData(M)]` + * + * @param sysvarIxIndex Account index of SysvarInstructions in the instruction's account list + * @param sysvarSlotIndex Account index of SysvarSlotHashes in the instruction's account list + * @param authenticatorData 37-byte WebAuthn authenticator data (rpIdHash + flags + counter) + * @param slot The current slot (from SlotHashes). Default: 0n + * @param rpId Relying party ID. Default: "example.com" + */ +export function buildAuthPayload(params: { + sysvarIxIndex: number; + sysvarSlotIndex: number; + authenticatorData: Uint8Array; + slot?: bigint; + rpId?: string; +}): Uint8Array { + const { sysvarIxIndex, sysvarSlotIndex, authenticatorData } = params; + const slot = params.slot ?? 0n; + const rpId = params.rpId ?? 'example.com'; + const rpIdBytes = new TextEncoder().encode(rpId); + + const payloadLen = 12 + rpIdBytes.length + authenticatorData.length; + const payload = new Uint8Array(payloadLen); + const view = new DataView( + payload.buffer, + payload.byteOffset, + payload.byteLength, + ); + + view.setBigUint64(0, slot, true); + payload[8] = sysvarIxIndex; + payload[9] = sysvarSlotIndex; + payload[10] = 0x10; // webauthn.get | https scheme flag + payload[11] = rpIdBytes.length; + payload.set(rpIdBytes, 12); + payload.set(authenticatorData, 12 + rpIdBytes.length); + + return payload; +} + +/** + * Builds a standard 37-byte WebAuthn authenticator data structure. + * + * @param rpId Relying party ID. Default: "example.com" + * @param counter Monotonically increasing counter to prevent replay attacks (big-endian u32). Default: 1 + */ +export async function buildAuthenticatorData( + rpId = 'example.com', + counter = 1, +): Promise { + const rpIdBytes = new TextEncoder().encode(rpId); + const rpIdHash = await sha256(rpIdBytes); + const data = new Uint8Array(37); + data.set(rpIdHash, 0); // 32 bytes: rpIdHash + data[32] = 0x01; // User Present flag + // bytes 33-36: counter (big-endian u32, must be > 0 to prevent replay) + const counterView = new DataView(data.buffer, 33, 4); + counterView.setUint32(0, counter, false); // false = big-endian + return data; +} + +/** + * Computes the raw 69-byte message that gets signed by the Secp256r1 key. + * This consists of the 37-byte authenticator data and the 32-byte SHA-256 hash of the clientDataJSON. + * + * The contract verifies this exact message construction on-chain. + */ +export async function buildSecp256r1Message(params: { + /** Instruction discriminator byte (e.g. 0=CreateWallet, 1=AddAuthority, …) */ + discriminator: number; + authPayload: Uint8Array; + /** Instruction-specific signed data (varies per instruction) */ + signedPayload: Uint8Array; + payer: PublicKey; + programId: PublicKey; + slot: bigint; + /** Origin of the website requesting the signature (e.g. "https://my-dapp.com"). Defaults to "https://example.com" */ + origin?: string; + /** Relying party ID used to build authenticatorData. Must match authPayload rpId. */ + rpId?: string; + /** Counter value for authenticatorData. Must match counter used in authPayload. */ + counter?: number; +}): Promise { + const { + discriminator, + authPayload, + signedPayload, + payer, + programId, + slot, + origin, + rpId, + counter, + } = params; + + const slotBytes = new Uint8Array(8); + new DataView(slotBytes.buffer).setBigUint64(0, slot, true); + + // Concatenate all parts for challenge hashing + const totalLen = 1 + authPayload.length + signedPayload.length + 8 + 32 + 32; + const combined = new Uint8Array(totalLen); + let offset = 0; + + combined[0] = discriminator; + offset += 1; + combined.set(authPayload, offset); + offset += authPayload.length; + combined.set(signedPayload, offset); + offset += signedPayload.length; + combined.set(slotBytes, offset); + offset += 8; + combined.set(payer.toBytes(), offset); + offset += 32; + combined.set(programId.toBytes(), offset); + + const challengeHash = await sha256(combined); + + // Encode challenge as base64url (no padding) + const challengeB64 = Buffer.from(challengeHash) + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); + + const clientDataJson = JSON.stringify({ + type: 'webauthn.get', + challenge: challengeB64, + origin: origin ?? 'https://example.com', + crossOrigin: false, + }); + + const authenticatorData = await buildAuthenticatorData(rpId ?? 'example.com', counter ?? 1); + const clientDataHash = await sha256(new TextEncoder().encode(clientDataJson)); + + const message = new Uint8Array( + authenticatorData.length + clientDataHash.length, + ); + message.set(authenticatorData, 0); + message.set(clientDataHash, authenticatorData.length); + return message; +} + +// ─── Precompile instruction builder ────────────────────────────────────────── + +export const SECP256R1_PROGRAM_ID = new PublicKey( + 'Secp256r1SigVerify1111111111111111111111111', +); + +/** + * Builds a Secp256r1 precompile instruction that verifies one signature. + * + * The message is first signed via `signer.sign(message)` (caller-provided), + * then the full precompile instruction is constructed with proper offsets. + * + * @param signer Implements `Secp256r1Signer` — provides sign() and key bytes + * @param message The raw 69-byte message: `authenticatorData ‖ sha256(clientDataJSON)` + */ +export async function buildSecp256r1PrecompileIx( + signer: Secp256r1Signer, + message: Uint8Array, +): Promise { + const signature = await signer.sign(message); + + const OFFSETS_START = 2; + const OFFSETS_SIZE = 14; + const DATA_START = OFFSETS_START + OFFSETS_SIZE; // 16 + + const signatureOffset = DATA_START; + const pubkeyOffset = signatureOffset + 64; + const msgOffset = pubkeyOffset + 33 + 1; // +1 padding + + const totalSize = msgOffset + message.length; + const data = new Uint8Array(totalSize); + + data[0] = 1; // number of signatures + data[1] = 0; // padding + + const view = new DataView( + data.buffer, + data.byteOffset + OFFSETS_START, + OFFSETS_SIZE, + ); + view.setUint16(0, signatureOffset, true); + view.setUint16(2, 0xffff, true); // instruction index (0xffff = current) + view.setUint16(4, pubkeyOffset, true); + view.setUint16(6, 0xffff, true); + view.setUint16(8, msgOffset, true); + view.setUint16(10, message.length, true); + view.setUint16(12, 0xffff, true); + + data.set(signature, signatureOffset); + data.set(signer.publicKeyBytes, pubkeyOffset); + data.set(message, msgOffset); + + return new TransactionInstruction({ + programId: SECP256R1_PROGRAM_ID, + keys: [], + data: Buffer.from(data), + }); +} diff --git a/sdk/solita-client/src/utils/wrapper.ts b/sdk/solita-client/src/utils/wrapper.ts new file mode 100644 index 0000000..1a6195f --- /dev/null +++ b/sdk/solita-client/src/utils/wrapper.ts @@ -0,0 +1,1683 @@ +import { + Connection, + Keypair, + PublicKey, + Transaction, + TransactionInstruction, + SystemProgram, + type AccountMeta, +} from '@solana/web3.js'; + +import { LazorInstructionBuilder } from './client'; +import { + findWalletPda, + findVaultPda, + findAuthorityPda, + findConfigPda, + findTreasuryShardPda, + findSessionPda, + PROGRAM_ID, +} from './pdas'; + +import { Role } from '../generated'; +import { AuthorityAccount } from '../generated/accounts'; +import { + type Secp256r1Signer, + buildSecp256r1Message, + buildSecp256r1PrecompileIx, + appendSecp256r1Sysvars, + buildAuthPayload, + buildAuthenticatorData, + readCurrentSlot, +} from './secp256r1'; +import { + computeAccountsHash, + packCompactInstructions, + type CompactInstruction, +} from './packing'; +import bs58 from 'bs58'; + +// ─── Enums ─────────────────────────────────────────────────────────────────── + +export enum AuthType { + Ed25519 = 0, + Secp256r1 = 1, +} + +export { Role }; + +// ─── Constants ─────────────────────────────────────────────────────────────── + +export const AUTHORITY_ACCOUNT_HEADER_SIZE = 48; +export const AUTHORITY_ACCOUNT_ED25519_SIZE = 48 + 32; // 80 bytes +export const AUTHORITY_ACCOUNT_SECP256R1_SIZE = 48 + 65; // 113 bytes +export const DISCRIMINATOR_AUTHORITY = 2; + +// ─── Types ─────────────────────────────────────────────────────────────────── + +/** + * Parameters for creating a new LazorKit wallet. + * + * - `userSeed` is optional — the SDK auto-generates a random 32-byte seed if omitted. + * - For Ed25519: only `owner` public key is required. + * - For Secp256r1: `pubkey` (33-byte compressed P-256 key) and `credentialHash` (32-byte SHA-256 of credential ID) are required. + */ +export type CreateWalletParams = + | { + payer: Keypair; + authType: AuthType.Ed25519; + owner: PublicKey; + userSeed?: Uint8Array; + } + | { + payer: Keypair; + authType?: AuthType.Secp256r1; + /** 33-byte compressed P-256 public key */ + pubkey: Uint8Array; + /** 32-byte SHA-256 hash of the WebAuthn credential ID */ + credentialHash: Uint8Array; + userSeed?: Uint8Array; + }; + +/** + * Identifies the admin/owner who is authorizing a privileged action. + * + * - Ed25519: provide `adminSigner` Keypair — SDK derives the Authority PDA automatically. + * - Secp256r1: provide `adminCredentialHash` to derive the Authority PDA. + * The actual signature verification is done via a preceding Secp256r1 precompile instruction. + */ +export type AdminSignerOptions = + | { + adminType: AuthType.Ed25519; + adminSigner: Keypair; + } + | { + adminType?: AuthType.Secp256r1; + /** + * 32-byte SHA-256 hash of the WebAuthn credential ID. + * Used to derive the admin Authority PDA. + * The actual Secp256r1 signature must be provided as a separate + * precompile instruction prepended to the transaction. + */ + adminCredentialHash: Uint8Array; + }; + +// ─── LazorClient ───────────────────────────────────────────────────────────── + +/** + * High-level client for the LazorKit Smart Wallet program. + * + * Two usage layers: + * + * **Layer 1 — Instruction builders** (`createWallet`, `addAuthority`, `execute`, …) + * Return a `TransactionInstruction` (plus any derived addresses). + * Callers compose multiple instructions and send the transaction themselves. + * + * **Layer 2 — Transaction builders** (`createWalletTxn`, `addAuthorityTxn`, …) + * Wrap Layer-1 results in a `Transaction` object ready to be signed and sent. + * + * Sending is always the caller's responsibility — use `connection.sendRawTransaction` or + * any helper you prefer so that this SDK stays free of transport assumptions. + */ +export class LazorClient { + public builder: LazorInstructionBuilder; + + constructor( + public connection: Connection, + public programId: PublicKey = PROGRAM_ID, + ) { + this.builder = new LazorInstructionBuilder(programId); + } + + // ─── PDA helpers (instance convenience) ────────────────────────────────── + + /** Derives the Wallet PDA from a 32-byte seed. */ + getWalletPda(userSeed: Uint8Array): PublicKey { + return findWalletPda(userSeed, this.programId)[0]; + } + + /** Derives the Vault PDA from a Wallet PDA. */ + getVaultPda(walletPda: PublicKey): PublicKey { + return findVaultPda(walletPda, this.programId)[0]; + } + + /** + * Derives an Authority PDA. + * @param idSeed For Ed25519: 32-byte public key. For Secp256r1: 32-byte credential hash. + */ + getAuthorityPda( + walletPda: PublicKey, + idSeed: Uint8Array | PublicKey, + ): PublicKey { + const seed = idSeed instanceof PublicKey ? idSeed.toBytes() : idSeed; + return findAuthorityPda(walletPda, seed, this.programId)[0]; + } + + /** Derives a Session PDA from a wallet PDA and session public key. */ + getSessionPda(walletPda: PublicKey, sessionKey: PublicKey): PublicKey { + return findSessionPda(walletPda, sessionKey, this.programId)[0]; + } + + /** Derives the global Config PDA. */ + getConfigPda(): PublicKey { + return findConfigPda(this.programId)[0]; + } + + /** Derives a Treasury Shard PDA for a given shard index. */ + getTreasuryShardPda(shardId: number): PublicKey { + return findTreasuryShardPda(shardId, this.programId)[0]; + } + + // ─── Internal helpers ───────────────────────────────────────────────────── + + private computeShardId(pubkey: PublicKey, numShards: number): number { + const n = Math.max(1, Math.min(255, Math.floor(numShards))); + return pubkey.toBytes().reduce((a, b) => a + b, 0) % n; + } + + private async getConfigNumShards(configPda: PublicKey): Promise { + const info = await this.connection.getAccountInfo(configPda, 'confirmed'); + if (!info?.data || info.data.length < 4) return 16; + + // ConfigAccount layout (program/src/state/config.rs): + // [0]=discriminator, [1]=bump, [2]=version, [3]=num_shards + const discriminator = info.data[0]; + const numShards = info.data[3]; + + // Discriminator 4 = Config. If this is not a Config PDA, fall back to default. + if (discriminator !== 4) return 16; + if (numShards === 0) return 16; + return numShards; + } + + private async getCommonPdas( + payerPubkey: PublicKey, + ): Promise<{ configPda: PublicKey; treasuryShard: PublicKey }> { + const configPda = findConfigPda(this.programId)[0]; + const numShards = await this.getConfigNumShards(configPda); + const shardId = this.computeShardId(payerPubkey, numShards); + const treasuryShard = findTreasuryShardPda(shardId, this.programId)[0]; + return { configPda, treasuryShard }; + } + + // ─── Layer 1: Instruction builders ─────────────────────────────────────── + + /** + * Builds a `CreateWallet` instruction. + * + * - `userSeed` is optional — a random 32-byte seed is generated when omitted. + * - Returns the derived `walletPda`, `authorityPda`, and the actual `userSeed` used, + * so callers can store the seed for later recovery. + */ + async createWallet(params: CreateWalletParams): Promise<{ + ix: TransactionInstruction; + walletPda: PublicKey; + authorityPda: PublicKey; + userSeed: Uint8Array; + }> { + const userSeed = + params.userSeed ?? crypto.getRandomValues(new Uint8Array(32)); + const [walletPda] = findWalletPda(userSeed, this.programId); + const [vaultPda] = findVaultPda(walletPda, this.programId); + + const authType = params.authType ?? AuthType.Secp256r1; + let authorityPda: PublicKey; + let authBump: number; + let authPubkey: Uint8Array; + let credentialHash: Uint8Array = new Uint8Array(32); + + if (params.authType === AuthType.Ed25519) { + authPubkey = params.owner.toBytes(); + [authorityPda, authBump] = findAuthorityPda( + walletPda, + authPubkey, + this.programId, + ); + } else { + const p = params as { pubkey: Uint8Array; credentialHash: Uint8Array }; + authPubkey = p.pubkey; + credentialHash = p.credentialHash; + [authorityPda, authBump] = findAuthorityPda( + walletPda, + credentialHash, + this.programId, + ); + } + + const { configPda, treasuryShard } = await this.getCommonPdas( + params.payer.publicKey, + ); + + const ix = this.builder.createWallet({ + config: configPda, + treasuryShard, + payer: params.payer.publicKey, + wallet: walletPda, + vault: vaultPda, + authority: authorityPda, + userSeed, + authType, + authBump, + authPubkey, + credentialHash, + }); + + return { ix, walletPda, authorityPda, userSeed }; + } + + /** + * Builds an `AddAuthority` instruction. + * + * - `role` defaults to `Role.Spender`. + * - `authType` defaults to `AuthType.Secp256r1`. + * - `adminAuthorityPda` can be provided to override auto-derivation. + */ + async addAuthority( + params: { + payer: Keypair; + walletPda: PublicKey; + newAuthType?: AuthType; + newAuthPubkey: Uint8Array; + newCredentialHash?: Uint8Array; + role?: Role; + /** Override the admin Authority PDA instead of auto-deriving it. */ + adminAuthorityPda?: PublicKey; + } & AdminSignerOptions, + ): Promise<{ ix: TransactionInstruction; newAuthority: PublicKey }> { + const { configPda, treasuryShard } = await this.getCommonPdas( + params.payer.publicKey, + ); + + const newAuthType = params.newAuthType ?? AuthType.Secp256r1; + const role = params.role ?? Role.Spender; + const adminType = params.adminType ?? AuthType.Secp256r1; + + const idSeed = + newAuthType === AuthType.Secp256r1 + ? params.newCredentialHash ?? new Uint8Array(32) + : params.newAuthPubkey.slice(0, 32); + const [newAuthority] = findAuthorityPda( + params.walletPda, + idSeed, + this.programId, + ); + + let adminAuthority: PublicKey; + if (params.adminAuthorityPda) { + adminAuthority = params.adminAuthorityPda; + } else if (adminType === AuthType.Ed25519) { + const p = params as { adminSigner: Keypair }; + [adminAuthority] = findAuthorityPda( + params.walletPda, + p.adminSigner.publicKey.toBytes(), + this.programId, + ); + } else { + const p = params as { adminCredentialHash: Uint8Array }; + [adminAuthority] = findAuthorityPda( + params.walletPda, + p.adminCredentialHash, + this.programId, + ); + } + + const ix = this.builder.addAuthority({ + payer: params.payer.publicKey, + wallet: params.walletPda, + adminAuthority, + newAuthority, + config: configPda, + treasuryShard, + newAuthType, + newRole: role, + newAuthPubkey: params.newAuthPubkey, + newCredentialHash: params.newCredentialHash, + authorizerSigner: + adminType === AuthType.Ed25519 + ? (params as any).adminSigner.publicKey + : undefined, + }); + + return { ix, newAuthority }; + } + + /** + * Builds a `RemoveAuthority` instruction. + * + * - `refundDestination` is optional — defaults to `payer.publicKey`. + */ + async removeAuthority( + params: { + payer: Keypair; + walletPda: PublicKey; + authorityToRemovePda: PublicKey; + /** Where to send the recovered rent SOL. Defaults to payer. */ + refundDestination?: PublicKey; + } & AdminSignerOptions, + ): Promise { + const { configPda, treasuryShard } = await this.getCommonPdas( + params.payer.publicKey, + ); + const refundDestination = + params.refundDestination ?? params.payer.publicKey; + + let adminAuthority: PublicKey; + if (params.adminType === AuthType.Ed25519) { + [adminAuthority] = findAuthorityPda( + params.walletPda, + params.adminSigner.publicKey.toBytes(), + this.programId, + ); + } else { + [adminAuthority] = findAuthorityPda( + params.walletPda, + params.adminCredentialHash, + this.programId, + ); + } + + return this.builder.removeAuthority({ + config: configPda, + treasuryShard, + payer: params.payer.publicKey, + wallet: params.walletPda, + adminAuthority, + targetAuthority: params.authorityToRemovePda, + refundDestination, + authorizerSigner: + params.adminType === AuthType.Ed25519 + ? params.adminSigner.publicKey + : undefined, + }); + } + + /** + * Builds a `CreateSession` instruction. + * + * - `sessionKey` is optional — an ephemeral Keypair is auto-generated when omitted. + * - `expiresAt` is optional — defaults to 1 hour from now (in Unix seconds). + * - Returns the generated `sessionKeypair` so the caller can store / use it. + */ + async createSession( + params: { + payer: Keypair; + walletPda: PublicKey; + /** Session public key. Omit to let the SDK auto-generate an ephemeral keypair. */ + sessionKey?: PublicKey; + /** + * Absolute slot height (or Unix timestamp) at which the session expires. + * Defaults to `Date.now() / 1000 + 3600` (1 hour from now). + */ + expiresAt?: bigint | number; + } & AdminSignerOptions, + ): Promise<{ + ix: TransactionInstruction; + sessionPda: PublicKey; + /** The session keypair — only set when auto-generated; null if caller supplied sessionKey. */ + sessionKeypair: Keypair | null; + }> { + const { configPda, treasuryShard } = await this.getCommonPdas( + params.payer.publicKey, + ); + const expiresAt = + params.expiresAt != null + ? BigInt(params.expiresAt) + : BigInt(Math.floor(Date.now() / 1000) + 3600); + const adminType = params.adminType ?? AuthType.Secp256r1; + + let sessionKeypair: Keypair | null = null; + let sessionKeyPubkey: PublicKey; + if (params.sessionKey) { + sessionKeyPubkey = params.sessionKey; + } else { + sessionKeypair = Keypair.generate(); + sessionKeyPubkey = sessionKeypair.publicKey; + } + + const [sessionPda] = findSessionPda( + params.walletPda, + sessionKeyPubkey, + this.programId, + ); + + let adminAuthority: PublicKey; + if (adminType === AuthType.Ed25519) { + const p = params as { adminSigner: Keypair }; + [adminAuthority] = findAuthorityPda( + params.walletPda, + p.adminSigner.publicKey.toBytes(), + this.programId, + ); + } else { + const p = params as { adminCredentialHash: Uint8Array }; + [adminAuthority] = findAuthorityPda( + params.walletPda, + p.adminCredentialHash, + this.programId, + ); + } + + const ix = this.builder.createSession({ + config: configPda, + treasuryShard, + payer: params.payer.publicKey, + wallet: params.walletPda, + adminAuthority, + session: sessionPda, + sessionKey: Array.from(sessionKeyPubkey.toBytes()), + expiresAt, + authorizerSigner: + adminType === AuthType.Ed25519 + ? (params as any).adminSigner.publicKey + : undefined, + }); + + return { ix, sessionPda, sessionKeypair }; + } + + /** + * Builds a `CloseSession` instruction. + */ + async closeSession(params: { + payer: Keypair; + walletPda: PublicKey; + sessionPda: PublicKey; + /** Override the Config PDA if needed. */ + configPda?: PublicKey; + authorizer?: { + authorizerPda: PublicKey; + signer: Keypair; + }; + }): Promise { + const configPda = params.configPda ?? findConfigPda(this.programId)[0]; + + return this.builder.closeSession({ + config: configPda, + payer: params.payer.publicKey, + wallet: params.walletPda, + session: params.sessionPda, + authorizer: params.authorizer?.authorizerPda, + authorizerSigner: params.authorizer?.signer.publicKey, + }); + } + + /** + * Builds an `Execute` instruction using the high-level builder that handles + * account deduplication and CompactInstruction packing automatically. + * + * - `authorityPda` is optional when `signer` (Ed25519 Keypair) is provided — + * the SDK derives the Authority PDA from `signer.publicKey`. + * - `vaultPda` is optional — derived from `walletPda` when omitted. + */ + async execute(params: { + payer: Keypair; + walletPda: PublicKey; + innerInstructions: TransactionInstruction[]; + /** Authority PDA. Omit when `signer` is an Ed25519 Keypair — SDK auto-derives it. */ + authorityPda?: PublicKey; + /** Ed25519 keypair that signs the transaction (authorizer signer). */ + signer?: Keypair; + /** Secp256r1 signature bytes appended to instruction data. */ + signature?: Uint8Array; + /** Override vault PDA if different from the canonical derivation. */ + vaultPda?: PublicKey; + }): Promise { + const { configPda, treasuryShard } = await this.getCommonPdas( + params.payer.publicKey, + ); + const vaultPda = + params.vaultPda ?? findVaultPda(params.walletPda, this.programId)[0]; + + // Auto-derive authorityPda from signer if not provided + let authorityPda = params.authorityPda; + if (!authorityPda && params.signer) { + [authorityPda] = findAuthorityPda( + params.walletPda, + params.signer.publicKey.toBytes(), + this.programId, + ); + } + if (!authorityPda) { + throw new Error( + 'execute(): either `authorityPda` or `signer` must be provided so the SDK can identify the Authority PDA.', + ); + } + + const ix = this.builder.buildExecute({ + config: configPda, + treasuryShard, + payer: params.payer.publicKey, + wallet: params.walletPda, + authority: authorityPda, + vault: vaultPda, + innerInstructions: params.innerInstructions, + authorizerSigner: params.signer?.publicKey, + }); + + if (params.signature) { + const newData = Buffer.alloc(ix.data.length + params.signature.length); + ix.data.copy(newData); + newData.set(params.signature, ix.data.length); + ix.data = newData; + } + + return ix; + } + + /** + * Builds a bundle of two instructions for Secp256r1 Execution: + * 1. Secp256r1 Precompile Instruction + * 2. LazorKit Execute Instruction with appended signature payload + * + * @returns { precompileIx, executeIx } object containing both instructions + */ + async executeWithSecp256r1(params: { + payer: Keypair; + walletPda: PublicKey; + innerInstructions: TransactionInstruction[]; + signer: Secp256r1Signer; + /** Optional: custom authenticatorData bytes. If omitted, counter is fetched from Authority account. */ + authenticatorData?: Uint8Array; + /** Optional: absolute slot height for liveness proof. fetched automatically if 0n/omitted */ + slot?: bigint; + rpId?: string; + /** Optional: Fully qualified origin URL like "https://my-app.com" */ + origin?: string; + }): Promise<{ + precompileIx: TransactionInstruction; + executeIx: TransactionInstruction; + }> { + const { configPda, treasuryShard } = await this.getCommonPdas( + params.payer.publicKey, + ); + const vaultPda = findVaultPda(params.walletPda, this.programId)[0]; + const [authorityPda] = findAuthorityPda( + params.walletPda, + params.signer.credentialIdHash, + this.programId, + ); + + // 1. Replicate deduplication to get exact account list for hashing + const baseAccounts: PublicKey[] = [ + params.payer.publicKey, + params.walletPda, + authorityPda, + vaultPda, + configPda, + treasuryShard, + SystemProgram.programId, + ]; + + const accountMap = new Map(); + const accountMetas: AccountMeta[] = []; + + baseAccounts.forEach((pk, idx) => { + accountMap.set(pk.toBase58(), idx); + accountMetas.push({ + pubkey: pk, + isWritable: idx === 0 || idx === 2 || idx === 3 || idx === 5, + isSigner: idx === 0, + }); + }); + + const vaultKey = vaultPda.toBase58(); + const walletKey = params.walletPda.toBase58(); + + const addAccount = ( + pubkey: PublicKey, + isSigner: boolean, + isWritable: boolean, + ): number => { + const key = pubkey.toBase58(); + if (key === vaultKey || key === walletKey) isSigner = false; + + if (!accountMap.has(key)) { + const idx = accountMetas.length; + accountMap.set(key, idx); + accountMetas.push({ pubkey, isWritable, isSigner }); + return idx; + } else { + const idx = accountMap.get(key)!; + const existing = accountMetas[idx]; + if (isWritable) existing.isWritable = true; + if (isSigner) existing.isSigner = true; + return idx; + } + }; + + const compactIxs: CompactInstruction[] = []; + for (const ix of params.innerInstructions) { + const programIdIndex = addAccount(ix.programId, false, false); + const accountIndexes: number[] = []; + for (const acc of ix.keys) { + accountIndexes.push( + addAccount(acc.pubkey, acc.isSigner, acc.isWritable), + ); + } + compactIxs.push({ programIdIndex, accountIndexes, data: ix.data }); + } + + const packedInstructions = packCompactInstructions(compactIxs); + + // 2. Load Slot + const slot = params.slot ?? (await readCurrentSlot(this.connection)); + + // 2b. Fetch current counter from Authority account if not using custom authenticatorData + let counter = 1; + if (!params.authenticatorData) { + try { + const authAccount = await AuthorityAccount.fromAccountAddress( + this.connection, + authorityPda, + ); + counter = Number(authAccount.counter) + 1; + } catch { + // If Authority doesn't exist yet, default to 1 + counter = 1; + } + } + + // 3. Build Auth Payload + const executeIxBase = new TransactionInstruction({ + programId: this.programId, + keys: [...accountMetas], // copy + data: Buffer.alloc(0), + }); + const { + ix: ixWithSysvars, + sysvarIxIndex, + sysvarSlotIndex, + } = appendSecp256r1Sysvars(executeIxBase); + + const authenticatorData = + params.authenticatorData ?? + (await buildAuthenticatorData(params.rpId, counter)); + + const authPayload = buildAuthPayload({ + sysvarIxIndex, + sysvarSlotIndex, + authenticatorData, + slot, + rpId: params.rpId, + }); + + // 4. Compute Accounts Hash + const accountsHash = await computeAccountsHash( + ixWithSysvars.keys, + compactIxs, + ); + + // 5. Build Combined Signed Payload = PackedInstructions + AccountsHash + const signedPayload = new Uint8Array(packedInstructions.length + 32); + signedPayload.set(packedInstructions, 0); + signedPayload.set(accountsHash, packedInstructions.length); + + // 6. Generate Message to Sign + const message = await buildSecp256r1Message({ + discriminator: 4, // Execute + authPayload, + signedPayload, + payer: params.payer.publicKey, + programId: this.programId, + slot, + origin: params.origin, + rpId: params.rpId, + counter, + }); + + // 7. Get Precompile Instruction + const precompileIx = await buildSecp256r1PrecompileIx( + params.signer, + message, + ); + + // 8. Build final Execute Instruction Data + // Layout: [disc(1)] [packedInstructions] [authPayload] + // Program parses: data_payload = &instruction_data[..compact_len] (the packed instructions) + // authority_payload = &instruction_data[compact_len..] (the auth payload) + const finalData = Buffer.alloc( + 1 + packedInstructions.length + authPayload.length, + ); + finalData[0] = 4; // disc + finalData.set(packedInstructions, 1); + finalData.set(authPayload, 1 + packedInstructions.length); + + const executeIx = new TransactionInstruction({ + programId: this.programId, + keys: ixWithSysvars.keys, + data: finalData, + }); + + return { precompileIx, executeIx }; + } + + /** + * AddAuthority authorized by a Secp256r1 admin. + * Builds: 1) Secp precompile, 2) final AddAuthority ix with appended auth payload. + */ + async addAuthorityWithSecp256r1(params: { + payer: Keypair; + walletPda: PublicKey; + signer: Secp256r1Signer; // admin signer (has credentialIdHash/publicKeyBytes) + newAuthType: AuthType; + newAuthPubkey: Uint8Array; + newCredentialHash?: Uint8Array; + role: Role; + slot?: bigint; + rpId?: string; + origin?: string; + }): Promise<{ + precompileIx: TransactionInstruction; + addIx: TransactionInstruction; + }> { + // 1) Build base ix (without auth payload) + const { configPda, treasuryShard } = await this.getCommonPdas( + params.payer.publicKey, + ); + const { newAuthType, role } = params; + const { walletPda } = params; + + const [adminAuthority] = findAuthorityPda( + walletPda, + params.signer.credentialIdHash, + this.programId, + ); + const [newAuthority] = findAuthorityPda( + walletPda, + newAuthType === AuthType.Secp256r1 + ? params.newCredentialHash ?? new Uint8Array(32) + : params.newAuthPubkey.slice(0, 32), + this.programId, + ); + + let addIx = this.builder.addAuthority({ + payer: params.payer.publicKey, + wallet: walletPda, + adminAuthority, + newAuthority, + config: configPda, + treasuryShard, + newAuthType, + newRole: role, + newAuthPubkey: params.newAuthPubkey, + newCredentialHash: params.newCredentialHash, + }); + + // 2) Append sysvars and build auth payload + const { + ix: ixWithSysvars, + sysvarIxIndex, + sysvarSlotIndex, + } = appendSecp256r1Sysvars(addIx); + const slot = params.slot ?? (await readCurrentSlot(this.connection)); + // Fetch current counter from admin authority and use counter+1 for the signature + let counter = 1; + try { + const authAccount = await AuthorityAccount.fromAccountAddress( + this.connection, + adminAuthority, + ); + counter = Number(authAccount.counter) + 1; + } catch { + counter = 1; + } + const authenticatorData = await buildAuthenticatorData( + params.rpId, + counter, + ); + const authPayload = buildAuthPayload({ + sysvarIxIndex, + sysvarSlotIndex, + authenticatorData, + slot, + rpId: params.rpId, + }); + + // 3) Build signed payload = data_payload (args+full_auth) + payer + wallet + const argsAndFull = ixWithSysvars.data.subarray(1); // after disc(1) + const extended = new Uint8Array(argsAndFull.length + 64); + extended.set(argsAndFull, 0); + extended.set(params.payer.publicKey.toBytes(), argsAndFull.length); + extended.set(walletPda.toBytes(), argsAndFull.length + 32); + + const message = await buildSecp256r1Message({ + discriminator: 1, + authPayload, + signedPayload: extended, + payer: params.payer.publicKey, + programId: this.programId, + slot, + origin: params.origin, + rpId: params.rpId, + counter, + }); + + const precompileIx = await buildSecp256r1PrecompileIx( + params.signer, + message, + ); + + // 4) Final instruction data = [disc][args+full_auth][authPayload] + const finalData = Buffer.alloc(1 + argsAndFull.length + authPayload.length); + finalData[0] = 1; + finalData.set(argsAndFull, 1); + finalData.set(authPayload, 1 + argsAndFull.length); + ixWithSysvars.data = finalData; + + return { precompileIx, addIx: ixWithSysvars }; + } + + /** CreateSession authorized by a Secp256r1 admin. */ + async createSessionWithSecp256r1(params: { + payer: Keypair; + walletPda: PublicKey; + signer: Secp256r1Signer; // admin signer + sessionKey?: PublicKey; + expiresAt?: bigint | number; + slot?: bigint; + rpId?: string; + origin?: string; + }): Promise<{ + precompileIx: TransactionInstruction; + sessionIx: TransactionInstruction; + sessionPda: PublicKey; + sessionKeypair: Keypair | null; + }> { + const expiresAt = + params.expiresAt != null + ? BigInt(params.expiresAt) + : BigInt(Math.floor(Date.now() / 1000) + 3600); + let sessionKeypair: Keypair | null = null; + let sessionKeyPubkey: PublicKey; + if (params.sessionKey) { + sessionKeyPubkey = params.sessionKey; + } else { + sessionKeypair = Keypair.generate(); + sessionKeyPubkey = sessionKeypair.publicKey; + } + + const { configPda, treasuryShard } = await this.getCommonPdas( + params.payer.publicKey, + ); + const [adminAuthority] = findAuthorityPda( + params.walletPda, + params.signer.credentialIdHash, + this.programId, + ); + const [sessionPda] = findSessionPda( + params.walletPda, + sessionKeyPubkey, + this.programId, + ); + + let sessionIx = this.builder.createSession({ + payer: params.payer.publicKey, + wallet: params.walletPda, + adminAuthority, + session: sessionPda, + config: configPda, + treasuryShard, + systemProgram: undefined as any, + sessionKey: Array.from(sessionKeyPubkey.toBytes()), + expiresAt, + } as any); + + const { + ix: ixWithSysvars, + sysvarIxIndex, + sysvarSlotIndex, + } = appendSecp256r1Sysvars(sessionIx); + // Ensure admin authority is writable for Secp256r1 paths (program expects mutable borrow) + for (const k of ixWithSysvars.keys) { + if (k.pubkey.equals(adminAuthority)) { + k.isWritable = true; + break; + } + } + const slot = params.slot ?? (await readCurrentSlot(this.connection)); + let counter = 1; + try { + const authAccount = await AuthorityAccount.fromAccountAddress( + this.connection, + adminAuthority, + ); + counter = Number(authAccount.counter) + 1; + } catch { + counter = 1; + } + const authenticatorData = await buildAuthenticatorData( + params.rpId, + counter, + ); + const authPayload = buildAuthPayload({ + sysvarIxIndex, + sysvarSlotIndex, + authenticatorData, + slot, + rpId: params.rpId, + }); + + // signed payload = args (after disc) + payer + wallet + const args = ixWithSysvars.data.subarray(1); + const extended = new Uint8Array(args.length + 64); + extended.set(args, 0); + extended.set(params.payer.publicKey.toBytes(), args.length); + extended.set(params.walletPda.toBytes(), args.length + 32); + + const message = await buildSecp256r1Message({ + discriminator: 5, + authPayload, + signedPayload: extended, + payer: params.payer.publicKey, + programId: this.programId, + slot, + origin: params.origin, + rpId: params.rpId, + counter, + }); + const precompileIx = await buildSecp256r1PrecompileIx( + params.signer, + message, + ); + + const finalData = Buffer.alloc(1 + args.length + authPayload.length); + finalData[0] = 5; + finalData.set(args, 1); + finalData.set(authPayload, 1 + args.length); + ixWithSysvars.data = finalData; + + return { + precompileIx, + sessionIx: ixWithSysvars, + sessionPda, + sessionKeypair, + }; + } + + /** RemoveAuthority authorized by Secp256r1 admin. */ + async removeAuthorityWithSecp256r1(params: { + payer: Keypair; + walletPda: PublicKey; + signer: Secp256r1Signer; // admin signer + authorityToRemovePda: PublicKey; + refundDestination?: PublicKey; + slot?: bigint; + rpId?: string; + origin?: string; + }): Promise<{ + precompileIx: TransactionInstruction; + removeIx: TransactionInstruction; + }> { + const refundDestination = + params.refundDestination ?? params.payer.publicKey; + // base ix (no payload) + let ix = await this.removeAuthority({ + payer: params.payer, + walletPda: params.walletPda, + authorityToRemovePda: params.authorityToRemovePda, + refundDestination, + adminType: AuthType.Secp256r1, + adminCredentialHash: params.signer.credentialIdHash, + } as any); + + const { + ix: ixWithSysvars, + sysvarIxIndex, + sysvarSlotIndex, + } = appendSecp256r1Sysvars(ix); + const slot = params.slot ?? (await readCurrentSlot(this.connection)); + let counter = 1; + try { + const authAccount = await AuthorityAccount.fromAccountAddress( + this.connection, + findAuthorityPda( + params.walletPda, + params.signer.credentialIdHash, + this.programId, + )[0], + ); + counter = Number(authAccount.counter) + 1; + } catch { + counter = 1; + } + const authenticatorData = await buildAuthenticatorData( + params.rpId, + counter, + ); + const authPayload = buildAuthPayload({ + sysvarIxIndex, + sysvarSlotIndex, + authenticatorData, + slot, + rpId: params.rpId, + }); + + // data_payload = target_auth_pda || refund_dest + const target = params.authorityToRemovePda.toBytes(); + const refund = refundDestination.toBytes(); + const dataPayload = new Uint8Array(64); + dataPayload.set(target, 0); + dataPayload.set(refund, 32); + + const message = await buildSecp256r1Message({ + discriminator: 2, + authPayload, + signedPayload: dataPayload, + payer: params.payer.publicKey, + programId: this.programId, + slot, + origin: params.origin, + rpId: params.rpId, + counter, + }); + const precompileIx = await buildSecp256r1PrecompileIx( + params.signer, + message, + ); + + // final data = [disc][authPayload] + const finalData = Buffer.alloc(1 + authPayload.length); + finalData[0] = 2; + finalData.set(authPayload, 1); + ixWithSysvars.data = finalData; + + return { precompileIx, removeIx: ixWithSysvars }; + } + + /** CloseSession authorized by Secp256r1 admin (Owner/Admin or contract admin if expired). */ + async closeSessionWithSecp256r1(params: { + payer: Keypair; + walletPda: PublicKey; + signer: Secp256r1Signer; // admin signer + sessionPda: PublicKey; + authorizerPda?: PublicKey; // optional admin/owner authority PDA + slot?: bigint; + rpId?: string; + origin?: string; + }): Promise<{ + precompileIx: TransactionInstruction; + closeIx: TransactionInstruction; + }> { + let ix = await this.closeSession({ + payer: params.payer, + walletPda: params.walletPda, + sessionPda: params.sessionPda, + authorizer: params.authorizerPda + ? ({ + authorizerPda: params.authorizerPda, + signer: Keypair.generate(), + } as any) + : undefined, + } as any); + + const { + ix: ixWithSysvars, + sysvarIxIndex, + sysvarSlotIndex, + } = appendSecp256r1Sysvars(ix); + const slot = params.slot ?? (await readCurrentSlot(this.connection)); + let counter = 1; + try { + const authAccount = await AuthorityAccount.fromAccountAddress( + this.connection, + findAuthorityPda( + params.walletPda, + params.signer.credentialIdHash, + this.programId, + )[0], + ); + counter = Number(authAccount.counter) + 1; + } catch { + counter = 1; + } + const authenticatorData = await buildAuthenticatorData( + params.rpId, + counter, + ); + const authPayload = buildAuthPayload({ + sysvarIxIndex, + sysvarSlotIndex, + authenticatorData, + slot, + rpId: params.rpId, + }); + + const dataPayload = params.sessionPda.toBytes(); + const message = await buildSecp256r1Message({ + discriminator: 8, + authPayload, + signedPayload: dataPayload, + payer: params.payer.publicKey, + programId: this.programId, + slot, + origin: params.origin, + rpId: params.rpId, + counter, + }); + const precompileIx = await buildSecp256r1PrecompileIx( + params.signer, + message, + ); + + const finalData = Buffer.alloc(1 + authPayload.length); + finalData[0] = 8; + finalData.set(authPayload, 1); + ixWithSysvars.data = finalData; + + return { precompileIx, closeIx: ixWithSysvars }; + } + + /** + * Builds a `CloseWallet` instruction. + * + * - `destination` is optional — defaults to `payer.publicKey`. + * - `vaultPda` is optional — derived from `walletPda` when omitted. + * - `adminAuthorityPda` is optional — auto-derived from admin signer credentials. + */ + async closeWallet( + params: { + payer: Keypair; + walletPda: PublicKey; + /** Where to sweep all remaining SOL. Defaults to payer. */ + destination?: PublicKey; + /** Override the Vault PDA if needed. */ + vaultPda?: PublicKey; + /** Override the owner Authority PDA instead of auto-deriving it. */ + adminAuthorityPda?: PublicKey; + } & AdminSignerOptions, + ): Promise { + const vaultPda = + params.vaultPda ?? findVaultPda(params.walletPda, this.programId)[0]; + const destination = params.destination ?? params.payer.publicKey; + + let ownerAuthority: PublicKey; + if (params.adminAuthorityPda) { + ownerAuthority = params.adminAuthorityPda; + } else if (params.adminType === AuthType.Ed25519) { + [ownerAuthority] = findAuthorityPda( + params.walletPda, + params.adminSigner.publicKey.toBytes(), + this.programId, + ); + } else { + [ownerAuthority] = findAuthorityPda( + params.walletPda, + params.adminCredentialHash ?? new Uint8Array(), + this.programId, + ); + } + + const ix = this.builder.closeWallet({ + payer: params.payer.publicKey, + wallet: params.walletPda, + vault: vaultPda, + ownerAuthority, + destination, + ownerSigner: + params.adminType === AuthType.Ed25519 + ? params.adminSigner.publicKey + : undefined, + }); + + // Required by on-chain close logic + ix.keys.push({ + pubkey: SystemProgram.programId, + isWritable: false, + isSigner: false, + }); + + return ix; + } + + /** + * Builds a `TransferOwnership` instruction. + */ + async transferOwnership(params: { + payer: Keypair; + walletPda: PublicKey; + currentOwnerAuthority: PublicKey; + newOwnerAuthority: PublicKey; + newAuthType: AuthType; + newAuthPubkey: Uint8Array; + newCredentialHash?: Uint8Array; + /** Ed25519 signer (optional — for Secp256r1, auth comes via precompile instruction). */ + signer?: Keypair; + }): Promise { + const { configPda, treasuryShard } = await this.getCommonPdas( + params.payer.publicKey, + ); + + return this.builder.transferOwnership({ + payer: params.payer.publicKey, + wallet: params.walletPda, + currentOwnerAuthority: params.currentOwnerAuthority, + newOwnerAuthority: params.newOwnerAuthority, + config: configPda, + treasuryShard, + newAuthType: params.newAuthType, + newAuthPubkey: params.newAuthPubkey, + newCredentialHash: params.newCredentialHash, + authorizerSigner: params.signer?.publicKey, + }); + } + + /** + * TransferOwnership authorized by a Secp256r1 admin. + * Builds: 1) Secp precompile, 2) final TransferOwnership ix with appended auth payload. + */ + async transferOwnershipWithSecp256r1(params: { + payer: Keypair; + walletPda: PublicKey; + signer: Secp256r1Signer; // admin signer + newAuthPubkey: Uint8Array; + newCredentialHash?: Uint8Array; + newAuthType: AuthType; + slot?: bigint; + rpId?: string; + origin?: string; + }): Promise<{ + precompileIx: TransactionInstruction; + transferIx: TransactionInstruction; + }> { + const { configPda, treasuryShard } = await this.getCommonPdas( + params.payer.publicKey, + ); + + // derive current owner authority PDA from signer credentials + const [currentOwnerAuthority] = findAuthorityPda( + params.walletPda, + params.signer.credentialIdHash, + this.programId, + ); + + // derive new owner authority PDA depending on auth type + const [newOwnerAuthority] = findAuthorityPda( + params.walletPda, + params.newAuthType === AuthType.Secp256r1 + ? params.newCredentialHash ?? new Uint8Array(32) + : params.newAuthPubkey.slice(0, 32), + this.programId, + ); + + // base ix (no auth payload) + let ix = await this.transferOwnership({ + payer: params.payer, + walletPda: params.walletPda, + currentOwnerAuthority, + newOwnerAuthority, + newAuthType: params.newAuthType, + newAuthPubkey: params.newAuthPubkey, + newCredentialHash: params.newCredentialHash, + config: configPda, + treasuryShard, + } as any); + + const { + ix: ixWithSysvars, + sysvarIxIndex, + sysvarSlotIndex, + } = appendSecp256r1Sysvars(ix); + const slot = params.slot ?? (await readCurrentSlot(this.connection)); + const authenticatorData = await buildAuthenticatorData(params.rpId); + const authPayload = buildAuthPayload({ + sysvarIxIndex, + sysvarSlotIndex, + authenticatorData, + slot, + rpId: params.rpId, + }); + + // signed payload = args (after disc) + payer + wallet + const args = ixWithSysvars.data.subarray(1); + const extended = new Uint8Array(args.length + 64); + extended.set(args, 0); + extended.set(params.payer.publicKey.toBytes(), args.length); + extended.set(params.walletPda.toBytes(), args.length + 32); + + const message = await buildSecp256r1Message({ + discriminator: 3, + authPayload, + signedPayload: extended, + payer: params.payer.publicKey, + programId: this.programId, + slot, + origin: params.origin, + rpId: params.rpId, + }); + const precompileIx = await buildSecp256r1PrecompileIx( + params.signer, + message, + ); + + const finalData = Buffer.alloc(1 + args.length + authPayload.length); + finalData[0] = 3; + finalData.set(args, 1); + finalData.set(authPayload, 1 + args.length); + ixWithSysvars.data = finalData; + + return { precompileIx, transferIx: ixWithSysvars }; + } + + /** + * CloseWallet authorized by a Secp256r1 admin. + * Builds: 1) Secp precompile, 2) final CloseWallet ix with appended auth payload. + */ + async closeWalletWithSecp256r1(params: { + payer: Keypair; + walletPda: PublicKey; + signer: Secp256r1Signer; + destination?: PublicKey; + vaultPda?: PublicKey; + adminAuthorityPda?: PublicKey; + slot?: bigint; + rpId?: string; + origin?: string; + }): Promise<{ + precompileIx: TransactionInstruction; + closeIx: TransactionInstruction; + }> { + // Build base closeWallet ix using Secp admin credential to derive PDA + let ix = await this.closeWallet({ + payer: params.payer, + walletPda: params.walletPda, + destination: params.destination, + vaultPda: params.vaultPda, + adminAuthorityPda: params.adminAuthorityPda, + adminType: AuthType.Secp256r1, + adminCredentialHash: params.signer.credentialIdHash, + } as any); + + const { + ix: ixWithSysvars, + sysvarIxIndex, + sysvarSlotIndex, + } = appendSecp256r1Sysvars(ix); + const slot = params.slot ?? (await readCurrentSlot(this.connection)); + const authenticatorData = await buildAuthenticatorData(params.rpId); + const authPayload = buildAuthPayload({ + sysvarIxIndex, + sysvarSlotIndex, + authenticatorData, + slot, + rpId: params.rpId, + }); + + // signed payload = args (after disc) + payer + wallet + const args = ixWithSysvars.data.subarray(1); + const extended = new Uint8Array(args.length + 64); + extended.set(args, 0); + extended.set(params.payer.publicKey.toBytes(), args.length); + extended.set(params.walletPda.toBytes(), args.length + 32); + + const message = await buildSecp256r1Message({ + discriminator: 9, + authPayload, + signedPayload: extended, + payer: params.payer.publicKey, + programId: this.programId, + slot, + origin: params.origin, + rpId: params.rpId, + }); + + const precompileIx = await buildSecp256r1PrecompileIx( + params.signer, + message, + ); + + const finalData = Buffer.alloc(1 + args.length + authPayload.length); + finalData[0] = 9; + finalData.set(args, 1); + finalData.set(authPayload, 1 + args.length); + ixWithSysvars.data = finalData; + + return { precompileIx, closeIx: ixWithSysvars }; + } + + // ─── Admin instructions ─────────────────────────────────────────────────── + + /** + * Builds an `InitializeConfig` instruction. + */ + async initializeConfig(params: { + admin: Keypair; + walletFee: bigint | number; + actionFee: bigint | number; + numShards: number; + }): Promise { + const configPda = findConfigPda(this.programId)[0]; + return this.builder.initializeConfig({ + admin: params.admin.publicKey, + config: configPda, + walletFee: BigInt(params.walletFee), + actionFee: BigInt(params.actionFee), + numShards: params.numShards, + }); + } + + /** + * Builds an `UpdateConfig` instruction. + */ + async updateConfig(params: { + admin: Keypair; + walletFee?: bigint | number; + actionFee?: bigint | number; + numShards?: number; + newAdmin?: PublicKey; + configPda?: PublicKey; + }): Promise { + const configPda = params.configPda ?? findConfigPda(this.programId)[0]; + return this.builder.updateConfig({ + admin: params.admin.publicKey, + config: configPda, + walletFee: params.walletFee, + actionFee: params.actionFee, + numShards: params.numShards, + newAdmin: params.newAdmin, + }); + } + + /** + * Builds an `InitTreasuryShard` instruction. + */ + async initTreasuryShard(params: { + payer: Keypair; + shardId: number; + }): Promise { + const configPda = findConfigPda(this.programId)[0]; + const [treasuryShard] = findTreasuryShardPda( + params.shardId, + this.programId, + ); + return this.builder.initTreasuryShard({ + payer: params.payer.publicKey, + config: configPda, + treasuryShard, + shardId: params.shardId, + }); + } + + /** + * Builds a `SweepTreasury` instruction. + */ + async sweepTreasury(params: { + admin: Keypair; + shardId: number; + destination: PublicKey; + }): Promise { + const configPda = findConfigPda(this.programId)[0]; + const [treasuryShard] = findTreasuryShardPda( + params.shardId, + this.programId, + ); + return this.builder.sweepTreasury({ + admin: params.admin.publicKey, + config: configPda, + treasuryShard, + destination: params.destination, + shardId: params.shardId, + }); + } + + // ─── Layer 2: Transaction builders ─────────────────────────────────────── + // Return a `Transaction` object with `feePayer` set. Signing and sending + // is always the caller's responsibility. + + async createWalletTxn( + params: Parameters[0], + ): Promise<{ + transaction: Transaction; + walletPda: PublicKey; + authorityPda: PublicKey; + userSeed: Uint8Array; + }> { + const { ix, walletPda, authorityPda, userSeed } = await this.createWallet( + params, + ); + const transaction = new Transaction().add(ix); + transaction.feePayer = params.payer.publicKey; + return { transaction, walletPda, authorityPda, userSeed }; + } + + async addAuthorityTxn( + params: Parameters[0], + ): Promise<{ + transaction: Transaction; + newAuthority: PublicKey; + }> { + const { ix, newAuthority } = await this.addAuthority(params); + const transaction = new Transaction().add(ix); + transaction.feePayer = params.payer.publicKey; + return { transaction, newAuthority }; + } + + async createSessionTxn( + params: Parameters[0], + ): Promise<{ + transaction: Transaction; + sessionPda: PublicKey; + sessionKeypair: Keypair | null; + }> { + const { ix, sessionPda, sessionKeypair } = await this.createSession(params); + const transaction = new Transaction().add(ix); + transaction.feePayer = params.payer.publicKey; + return { transaction, sessionPda, sessionKeypair }; + } + + async executeTxn( + params: Parameters[0], + ): Promise { + const ix = await this.execute(params); + const transaction = new Transaction().add(ix); + transaction.feePayer = params.payer.publicKey; + return transaction; + } + + // ─── Discovery helpers ──────────────────────────────────────────────────── + + /** + * Finds all Wallet PDAs associated with a given Ed25519 public key. + */ + static async findWalletsByEd25519Pubkey( + connection: Connection, + ed25519Pubkey: PublicKey, + programId: PublicKey = PROGRAM_ID, + ): Promise { + const accounts = await connection.getProgramAccounts(programId, { + filters: [{ dataSize: AUTHORITY_ACCOUNT_ED25519_SIZE }], // Ed25519 authority size + }); + + const results: PublicKey[] = []; + for (const a of accounts) { + const data = a.account.data; + if (data[0] === 2 && data[1] === 0) { + // disc=2 (Authority), type=0 (Ed25519) + const storedPubkey = data.subarray(48, 80); + if (Buffer.compare(storedPubkey, ed25519Pubkey.toBuffer()) === 0) { + results.push(new PublicKey(data.subarray(16, 48))); + } + } + } + return results; + } + + /** + * Finds all Wallet PDAs associated with a Secp256r1 credential hash. + */ + static async findWalletsByCredentialHash( + connection: Connection, + credentialHash: Uint8Array, + programId: PublicKey = PROGRAM_ID, + ): Promise { + const accounts = await connection.getProgramAccounts(programId, { + filters: [{ dataSize: AUTHORITY_ACCOUNT_SECP256R1_SIZE }], // Secp256r1 authority size + }); + + const results: PublicKey[] = []; + for (const a of accounts) { + const data = a.account.data; + if (data[0] === 2 && data[1] === 1) { + // disc=2 (Authority), type=1 (Secp256r1) + const storedHash = data.subarray(48, 80); + if (Buffer.compare(storedHash, Buffer.from(credentialHash)) === 0) { + results.push(new PublicKey(data.subarray(16, 48))); + } + } + } + return results; + } + + /** + * Finds all Authority PDA records associated with a given Secp256r1 credential hash. + * + * Unlike `findWalletByCredentialHash` which only returns Wallet PDAs, this method + * returns rich authority metadata — useful for credential-based wallet recovery flows. + * + * Account memory layout (Authority PDA): + * - byte 0: discriminator (2 = Authority) + * - byte 1: authority_type (0 = Ed25519, 1 = Secp256r1) + * - byte 2: role (0 = Owner, 1 = Admin, 2 = Spender) + * - bytes 16–48: wallet PDA + * - bytes 48–80: credential_id_hash (Secp256r1) or pubkey (Ed25519) + */ + static async findAllAuthoritiesByCredentialHash( + connection: Connection, + credentialHash: Uint8Array, + programId: PublicKey = PROGRAM_ID, + ): Promise< + Array<{ + authority: PublicKey; + wallet: PublicKey; + role: number; + authorityType: number; + }> + > { + const accounts = await connection.getProgramAccounts(programId, { + filters: [ + { dataSize: AUTHORITY_ACCOUNT_SECP256R1_SIZE }, // Secp256r1 authority size + { + memcmp: { + offset: 0, + bytes: bs58.encode(Buffer.from([DISCRIMINATOR_AUTHORITY])), + }, + }, // disc = Authority + { + memcmp: { + offset: 48, + bytes: bs58.encode(Buffer.from(credentialHash)), + }, + }, // credentialHash + ], + }); + + return accounts.map((a) => { + const data = a.account.data; + return { + authority: a.pubkey, + wallet: new PublicKey(data.subarray(16, 48)), + role: data[2], + authorityType: data[1], + }; + }); + } +} diff --git a/sdk/solita-client/tsconfig.json b/sdk/solita-client/tsconfig.json new file mode 100644 index 0000000..13349e4 --- /dev/null +++ b/sdk/solita-client/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "declaration": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist" + }, + "include": ["src/**/*"] +} diff --git a/sdk/transfer_limit.ts b/sdk/transfer_limit.ts deleted file mode 100644 index da7901c..0000000 --- a/sdk/transfer_limit.ts +++ /dev/null @@ -1,121 +0,0 @@ -import * as anchor from '@coral-xyz/anchor'; -import { TransferLimit } from '../target/types/transfer_limit'; -import * as types from './types'; -import * as constants from './constants'; - -export class TransferLimitProgram { - private connection: anchor.web3.Connection; - private Idl: anchor.Idl = require('../target/idl/transfer_limit.json'); - - constructor(connection: anchor.web3.Connection) { - this.connection = connection; - } - - get program(): anchor.Program { - return new anchor.Program(this.Idl, { - connection: this.connection, - }); - } - - get programId(): anchor.web3.PublicKey { - return this.program.programId; - } - - rule(smartWallet: anchor.web3.PublicKey): anchor.web3.PublicKey { - return anchor.web3.PublicKey.findProgramAddressSync( - [constants.RULE_SEED, smartWallet.toBuffer()], - this.programId - )[0]; - } - - get config(): anchor.web3.PublicKey { - return anchor.web3.PublicKey.findProgramAddressSync( - [constants.CONFIG_SEED], - this.programId - )[0]; - } - - member( - smartWallet: anchor.web3.PublicKey, - smartWalletAuthenticator: anchor.web3.PublicKey - ) { - return anchor.web3.PublicKey.findProgramAddressSync( - [ - constants.MEMBER_SEED, - smartWallet.toBuffer(), - smartWalletAuthenticator.toBuffer(), - ], - this.programId - )[0]; - } - - ruleData( - smartWallet: anchor.web3.PublicKey, - tokenMint: anchor.web3.PublicKey = anchor.web3.PublicKey.default - ) { - return anchor.web3.PublicKey.findProgramAddressSync( - [constants.RULE_DATA_SEED, smartWallet.toBuffer(), tokenMint.toBuffer()], - this.programId - )[0]; - } - - async initRuleIns( - payer: anchor.web3.PublicKey, - smartWallet: anchor.web3.PublicKey, - smartWalletAuthenticator: anchor.web3.PublicKey, - smartWalletConfig: anchor.web3.PublicKey, - args: types.InitRuleArgs - ) { - return await this.program.methods - .initRule(args) - .accountsPartial({ - payer, - smartWallet, - smartWalletAuthenticator, - member: this.member(smartWallet, smartWalletAuthenticator), - ruleData: this.ruleData(smartWallet, args.token), - smartWalletConfig, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .instruction(); - } - - async addMemeberIns( - payer: anchor.web3.PublicKey, - smartWallet: anchor.web3.PublicKey, - smartWalletAuthenticator: anchor.web3.PublicKey, - newSmartWalletAuthenticator: anchor.web3.PublicKey, - lazorkit: anchor.web3.PublicKey, - new_passkey_pubkey: number[], - bump: number - ) { - return await this.program.methods - .addMember(new_passkey_pubkey, bump) - .accountsPartial({ - payer, - smartWalletAuthenticator, - newSmartWalletAuthenticator, - admin: this.member(smartWallet, smartWalletAuthenticator), - member: this.member(smartWallet, newSmartWalletAuthenticator), - lazorkit, - systemProgram: anchor.web3.SystemProgram.programId, - }) - .instruction(); - } - - async checkRuleIns( - smartWallet: anchor.web3.PublicKey, - smartWalletAuthenticator: anchor.web3.PublicKey, - cpiIns: anchor.web3.TransactionInstruction, - tokenMint: anchor.web3.PublicKey = anchor.web3.PublicKey.default - ) { - return await this.program.methods - .checkRule(tokenMint, cpiIns.data, cpiIns.programId) - .accountsPartial({ - smartWalletAuthenticator, - ruleData: this.ruleData(smartWallet, tokenMint), - member: this.member(smartWallet, smartWalletAuthenticator), - }) - .instruction(); - } -} diff --git a/sdk/types.ts b/sdk/types.ts deleted file mode 100644 index 4410504..0000000 --- a/sdk/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as anchor from '@coral-xyz/anchor'; - -import { Lazorkit } from '../target/types/lazorkit'; -import { TransferLimit } from '../target/types/transfer_limit'; - -export type CpiData = anchor.IdlTypes['cpiData']; -export type SmartWalletSeq = anchor.IdlTypes['smartWalletSeq']; -export type SmartWalletConfig = anchor.IdlTypes['smartWalletConfig']; -export type SmartWalletAuthenticator = - anchor.IdlTypes['smartWalletAuthenticator']; - -export type SmartWallet = anchor.Idl; - -export const ExecuteAction = { - ['ExecuteCpi']: { executeCpi: {} }, - ['ChangeProgramRule']: { changeProgramRule: {} }, - ['CheckAuthenticator']: { checkAuthenticator: {} }, - ['CallRuleProgram']: { callRuleProgram: {} }, -}; - -// TransferLimitType -export type InitRuleArgs = anchor.IdlTypes['initRuleArgs']; diff --git a/sdk/utils.ts b/sdk/utils.ts deleted file mode 100644 index 2bcdac1..0000000 --- a/sdk/utils.ts +++ /dev/null @@ -1,200 +0,0 @@ -import * as anchor from '@coral-xyz/anchor'; -import { sha256 } from 'js-sha256'; - -export function hashSeeds( - passkey: number[], - smartWallet: anchor.web3.PublicKey -): Buffer { - const rawBuffer = Buffer.concat([ - Buffer.from(passkey), - smartWallet.toBuffer(), - ]); - const hash = sha256.arrayBuffer(rawBuffer); - return Buffer.from(hash).subarray(0, 32); -} - -// Constants from the Rust code -const SIGNATURE_OFFSETS_SERIALIZED_SIZE = 14; -const SIGNATURE_OFFSETS_START = 2; -const DATA_START = SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START; -const SIGNATURE_SERIALIZED_SIZE: number = 64; -const COMPRESSED_PUBKEY_SERIALIZED_SIZE = 33; -const FIELD_SIZE = 32; - -const SECP256R1_NATIVE_PROGRAM = new anchor.web3.PublicKey( - 'Secp256r1SigVerify1111111111111111111111111' -); - -// Order of secp256r1 curve (same as in Rust code) -const SECP256R1_ORDER = new Uint8Array([ - 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, 0xf3, 0xb9, - 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51, -]); - -// Half order of secp256r1 curve (same as in Rust code) -const SECP256R1_HALF_ORDER = new Uint8Array([ - 0x7f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xde, 0x73, 0x7d, 0x56, 0xd3, 0x8b, 0xcf, 0x42, 0x79, 0xdc, - 0xe5, 0x61, 0x7e, 0x31, 0x92, 0xa8, -]); - -interface Secp256r1SignatureOffsets { - signature_offset: number; - signature_instruction_index: number; - public_key_offset: number; - public_key_instruction_index: number; - message_data_offset: number; - message_data_size: number; - message_instruction_index: number; -} - -function bytesOf(data: any): Uint8Array { - if (data instanceof Uint8Array) { - return data; - } else if (Array.isArray(data)) { - return new Uint8Array(data); - } else { - // Convert object to buffer using DataView for consistent byte ordering - const buffer = new ArrayBuffer(Object.values(data).length * 2); - const view = new DataView(buffer); - Object.values(data).forEach((value, index) => { - view.setUint16(index * 2, value as number, true); - }); - return new Uint8Array(buffer); - } -} - -// Compare two big numbers represented as Uint8Arrays -function isGreaterThan(a: Uint8Array, b: Uint8Array): boolean { - if (a.length !== b.length) { - return a.length > b.length; - } - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { - return a[i] > b[i]; - } - } - return false; -} - -// Subtract one big number from another (a - b), both represented as Uint8Arrays -function subtractBigNumbers(a: Uint8Array, b: Uint8Array): Uint8Array { - const result = new Uint8Array(a.length); - let borrow = 0; - - for (let i = a.length - 1; i >= 0; i--) { - let diff = a[i] - b[i] - borrow; - if (diff < 0) { - diff += 256; - borrow = 1; - } else { - borrow = 0; - } - result[i] = diff; - } - - return result; -} - -export function createSecp256r1Instruction( - message: Uint8Array, - pubkey: Buffer, - signature: Buffer -): anchor.web3.TransactionInstruction { - try { - // Ensure signature is the correct length - if (signature.length !== SIGNATURE_SERIALIZED_SIZE) { - // Extract r and s from the signature - const r = signature.slice(0, FIELD_SIZE); - const s = signature.slice(FIELD_SIZE, FIELD_SIZE * 2); - - // Pad r and s to correct length if needed - const paddedR = Buffer.alloc(FIELD_SIZE, 0); - const paddedS = Buffer.alloc(FIELD_SIZE, 0); - r.copy(paddedR, FIELD_SIZE - r.length); - s.copy(paddedS, FIELD_SIZE - s.length); - - // Check if s > half_order, if so, compute s = order - s - if (isGreaterThan(paddedS, SECP256R1_HALF_ORDER)) { - const newS = subtractBigNumbers(SECP256R1_ORDER, paddedS); - signature = Buffer.concat([paddedR, Buffer.from(newS)]); - } else { - signature = Buffer.concat([paddedR, paddedS]); - } - } - - // Verify lengths - if ( - pubkey.length !== COMPRESSED_PUBKEY_SERIALIZED_SIZE || - signature.length !== SIGNATURE_SERIALIZED_SIZE - ) { - throw new Error('Invalid key or signature length'); - } - - // Calculate total size and create instruction data - const totalSize = - DATA_START + - SIGNATURE_SERIALIZED_SIZE + - COMPRESSED_PUBKEY_SERIALIZED_SIZE + - message.length; - - const instructionData = new Uint8Array(totalSize); - - // Calculate offsets - const numSignatures: number = 1; - const publicKeyOffset = DATA_START; - const signatureOffset = publicKeyOffset + COMPRESSED_PUBKEY_SERIALIZED_SIZE; - const messageDataOffset = signatureOffset + SIGNATURE_SERIALIZED_SIZE; - - // Write number of signatures - instructionData.set(bytesOf([numSignatures, 0]), 0); - - // Create and write offsets - const offsets: Secp256r1SignatureOffsets = { - signature_offset: signatureOffset, - signature_instruction_index: 0xffff, // u16::MAX - public_key_offset: publicKeyOffset, - public_key_instruction_index: 0xffff, - message_data_offset: messageDataOffset, - message_data_size: message.length, - message_instruction_index: 0xffff, - }; - - // Write all components - instructionData.set(bytesOf(offsets), SIGNATURE_OFFSETS_START); - instructionData.set(pubkey, publicKeyOffset); - instructionData.set(signature, signatureOffset); - instructionData.set(message, messageDataOffset); - - return new anchor.web3.TransactionInstruction({ - keys: [], - programId: SECP256R1_NATIVE_PROGRAM, - data: Buffer.from(instructionData), - }); - } catch (error) { - throw new Error(`Failed to create secp256r1 instruction: ${error}`); - } -} - -/** - * Convenience helper: convert a {@link anchor.web3.TransactionInstruction}'s `keys` - * array into the `AccountMeta` objects Anchor expects for - * `remainingAccounts(...)`. - * - * The mapping uses the original `isWritable` flag from the instruction and - * marks the account as a signer if either: - * • the instruction already flagged it as signer, or - * • the account equals the provided {@link payer} (the wallet paying for the - * transaction). - */ -export function instructionToAccountMetas( - ix: anchor.web3.TransactionInstruction, - payer: anchor.web3.PublicKey -): anchor.web3.AccountMeta[] { - return ix.keys.map((k) => ({ - pubkey: k.pubkey, - isWritable: k.isWritable, - isSigner: k.isSigner || k.pubkey.equals(payer), - })); -} diff --git a/swig-wallet b/swig-wallet new file mode 160000 index 0000000..975c81d --- /dev/null +++ b/swig-wallet @@ -0,0 +1 @@ +Subproject commit 975c81d1f7a31ace84fed21f487f2c73df266b90 diff --git a/tests-real-rpc/package-lock.json b/tests-real-rpc/package-lock.json new file mode 100644 index 0000000..46fd638 --- /dev/null +++ b/tests-real-rpc/package-lock.json @@ -0,0 +1,2671 @@ +{ + "name": "lazorkit-tests-real-rpc", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lazorkit-tests-real-rpc", + "version": "1.0.0", + "dependencies": { + "@lazorkit/codama-client": "file:../sdk/codama-client", + "@solana/kit": "^6.0.1", + "bs58": "^6.0.0", + "dotenv": "^16.4.5", + "ecdsa-secp256r1": "^1.3.3" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.9.3", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + } + }, + "../sdk/codama-client": { + "name": "@lazorkit/codama-client", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@solana/kit": "^6.0.1" + }, + "devDependencies": { + "@codama/nodes-from-anchor": "^1.3.8", + "@codama/renderers-js": "^1.7.0", + "@types/node": "^25.2.3", + "codama": "^1.5.0", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lazorkit/codama-client": { + "resolved": "../sdk/codama-client", + "link": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@solana/accounts": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/accounts/-/accounts-6.1.0.tgz", + "integrity": "sha512-0jhmhSSS71ClLtBQIDrLlhkiNER4M9RIXTl1eJ1yJoFlE608JaKHTjNWsdVKdke7uBD6exdjNZkIVmouQPHMcA==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/codecs-core": "6.1.0", + "@solana/codecs-strings": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/rpc-spec": "6.1.0", + "@solana/rpc-types": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/addresses": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/addresses/-/addresses-6.1.0.tgz", + "integrity": "sha512-QT04Vie4iICaalQQRJFMGj/P56IxXiwFtVuZHu1qjZUNmuGTOvX6G98b27RaGtLzpJ3NIku/6OtKxLUBqAKAyQ==", + "license": "MIT", + "dependencies": { + "@solana/assertions": "6.1.0", + "@solana/codecs-core": "6.1.0", + "@solana/codecs-strings": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/nominal-types": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/assertions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/assertions/-/assertions-6.1.0.tgz", + "integrity": "sha512-pLgxB2xxTk2QfTaWpnRpSMYgaPkKYDQgptRvbwmuDQnOW1Zopg+42MT2UrDGd3UFMML1uOFPxIwKM6m51H0uXw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-6.1.0.tgz", + "integrity": "sha512-VHBS3t8fyVjE0Nqo6b4TUnzdwdRaVo+B5ufHhPLbbjkEXzz8HB4E/OBjgasn+zWGlfScfQAiBFOsfZjbVWu4XA==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.1.0", + "@solana/codecs-data-structures": "6.1.0", + "@solana/codecs-numbers": "6.1.0", + "@solana/codecs-strings": "6.1.0", + "@solana/options": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-core": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-6.1.0.tgz", + "integrity": "sha512-5rNnDOOm2GRFMJbd9imYCPNvGOrQ+TZ53NCkFFWbbB7f+L9KkLeuuAsDMFN1lCziJFlymvN785YtDnMeWj2W+g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-data-structures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-6.1.0.tgz", + "integrity": "sha512-1cb9g5hrrucTuGkGxqVVq7dCwSMnn4YqwTe365iKkK8HBpLBmUl8XATf1MUs5UtDun1g9eNWOL72Psr8mIUqTQ==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.1.0", + "@solana/codecs-numbers": "6.1.0", + "@solana/errors": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-numbers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-6.1.0.tgz", + "integrity": "sha512-YPQwwl6LE3igH23ah+d8kgpyE5xFcPbuwhxCDsLWqY/ESrvO/0YQSbsgIXahbhZxN59ZC4uq1LnHhBNbpCSVQg==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.1.0", + "@solana/errors": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-strings": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-6.1.0.tgz", + "integrity": "sha512-pRH5uAn4VCFUs2rYiDITyWsRnpvs3Uh/nhSc6OSP/kusghcCcCJcUzHBIjT4x08MVacXmGUlSLe/9qPQO+QK3Q==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.1.0", + "@solana/codecs-numbers": "6.1.0", + "@solana/errors": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "fastestsmallesttextencoderdecoder": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/errors": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-6.1.0.tgz", + "integrity": "sha512-cqSwcw3Rmn85UR7PyF5nKPdlQsRYBkx7YGRvFaJ6Sal1PM+bfolhL5iT7STQoXxdhXGYwHMPg7kZYxmMdjwnJA==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.3" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/fast-stable-stringify": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/fast-stable-stringify/-/fast-stable-stringify-6.1.0.tgz", + "integrity": "sha512-QXUfDFaJCFeARsxJgScWmJ153Tit7Cimk9y0UWWreNBr2Aphi67Nlcj/tr7UABTO0Qaw/0gwrK76zz3m1t3nIw==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/functional": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/functional/-/functional-6.1.0.tgz", + "integrity": "sha512-+Sm8ldVxSTHIKaZDvcBu81FPjknXx6OMPlakkKmXjKxPgVLl86ruqMo2yEwoDUHV7DysLrLLcRNn13rfulomRw==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/instruction-plans": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/instruction-plans/-/instruction-plans-6.1.0.tgz", + "integrity": "sha512-zcsHg544t1zn7LLOVUxOWYlsKn9gvT7R+pL3cTiP2wFNoUN0h9En87H6nVqkZ8LWw23asgW0uM5uJGwfBx2h1Q==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0", + "@solana/instructions": "6.1.0", + "@solana/keys": "6.1.0", + "@solana/promises": "6.1.0", + "@solana/transaction-messages": "6.1.0", + "@solana/transactions": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/instructions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/instructions/-/instructions-6.1.0.tgz", + "integrity": "sha512-w1LdbJ3yanESckNTYC5KPckgN/25FyGCm07WWrs+dCnnpRNeLiVHIytXCPmArOVAXVkOYidXzhWmqCzqKUjYaA==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.1.0", + "@solana/errors": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/keys": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/keys/-/keys-6.1.0.tgz", + "integrity": "sha512-C/SGCl3VOgBQZ0mLrMxCcJYnMsGpgE8wbx29jqRY+R91m5YhS1f/GfXJPR1lN/h7QGrJ6YDm8eI0Y3AZ7goKHg==", + "license": "MIT", + "dependencies": { + "@solana/assertions": "6.1.0", + "@solana/codecs-core": "6.1.0", + "@solana/codecs-strings": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/nominal-types": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/kit": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-6.1.0.tgz", + "integrity": "sha512-24exn11BPonquufyCkGgypVtmN4JOsdGMsbF3EZ4kFyk7ZNryCn/N8eELr1FCVrHWRXoc0xy/HFaESBULTMf6g==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "6.1.0", + "@solana/addresses": "6.1.0", + "@solana/codecs": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/functional": "6.1.0", + "@solana/instruction-plans": "6.1.0", + "@solana/instructions": "6.1.0", + "@solana/keys": "6.1.0", + "@solana/offchain-messages": "6.1.0", + "@solana/plugin-core": "6.1.0", + "@solana/plugin-interfaces": "6.1.0", + "@solana/program-client-core": "6.1.0", + "@solana/programs": "6.1.0", + "@solana/rpc": "6.1.0", + "@solana/rpc-api": "6.1.0", + "@solana/rpc-parsed-types": "6.1.0", + "@solana/rpc-spec-types": "6.1.0", + "@solana/rpc-subscriptions": "6.1.0", + "@solana/rpc-types": "6.1.0", + "@solana/signers": "6.1.0", + "@solana/sysvars": "6.1.0", + "@solana/transaction-confirmation": "6.1.0", + "@solana/transaction-messages": "6.1.0", + "@solana/transactions": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/nominal-types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/nominal-types/-/nominal-types-6.1.0.tgz", + "integrity": "sha512-+skHjN0arNNB9TLsGqA94VCx7euyGURI+qG6wck6E4D7hH6i6DxGiVrtKRghx+smJkkLtTm9BvdVKGoeNQYr7Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/offchain-messages": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/offchain-messages/-/offchain-messages-6.1.0.tgz", + "integrity": "sha512-jrUb7HGUnRA+k44upcqKeevtEdqMxYRSlFdE0JTctZunGlP3GCcTl12tFOpbnFHvBLt8RwS62+nyeES8zzNwXA==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/codecs-core": "6.1.0", + "@solana/codecs-data-structures": "6.1.0", + "@solana/codecs-numbers": "6.1.0", + "@solana/codecs-strings": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/keys": "6.1.0", + "@solana/nominal-types": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/options": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/options/-/options-6.1.0.tgz", + "integrity": "sha512-/4FtVfR6nkHkMCumyh7/lJ6jMqyES6tKUbOJRa6gJxcIUWeRDu+XrHTHLf3gRNUqDAbFvW8FMIrQm7PdreZgRA==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "6.1.0", + "@solana/codecs-data-structures": "6.1.0", + "@solana/codecs-numbers": "6.1.0", + "@solana/codecs-strings": "6.1.0", + "@solana/errors": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/plugin-core": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/plugin-core/-/plugin-core-6.1.0.tgz", + "integrity": "sha512-2nmNCPa6B1QArqpAZHWUkK6K7UXLTrekfcfJm2V//ATEtLpKEBlv0c3mrhOYwNAKP2TpNuvEV33InXWKst9oXQ==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/plugin-interfaces": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/plugin-interfaces/-/plugin-interfaces-6.1.0.tgz", + "integrity": "sha512-eWSzfOuwtHUp8vljf5V24Tkz3WxqxiV0vD/BJZBNRZMdYRw3Cw3oeWcvEqHHxGUOie6AjIK8GrKggi8F73ZXbg==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/instruction-plans": "6.1.0", + "@solana/keys": "6.1.0", + "@solana/rpc-spec": "6.1.0", + "@solana/rpc-subscriptions-spec": "6.1.0", + "@solana/rpc-types": "6.1.0", + "@solana/signers": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/program-client-core": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/program-client-core/-/program-client-core-6.1.0.tgz", + "integrity": "sha512-5Apka+ulWNfLNLYNR63pLnr5XvkXTQWeaftWED93iTWTZrZv9SyFWcmIsaes6eqGXMQ3RhlebnrWODtKuAA62g==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "6.1.0", + "@solana/addresses": "6.1.0", + "@solana/codecs-core": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/instruction-plans": "6.1.0", + "@solana/instructions": "6.1.0", + "@solana/plugin-interfaces": "6.1.0", + "@solana/rpc-api": "6.1.0", + "@solana/signers": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/programs": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/programs/-/programs-6.1.0.tgz", + "integrity": "sha512-i4L4gSlIHDsdYRt3/YKVKMIN3UuYSKHRqK9B+AejcIc0y6Y/AXnHqzmpBRXEhvTXz18nt59MLXpVU4wu7ASjJA==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/errors": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/promises": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/promises/-/promises-6.1.0.tgz", + "integrity": "sha512-/mUW6peXQiEOaylLpGv4vtkvPzQvSbfhX9j5PNIK/ry4S3SHRQ3j3W/oGy4y3LR5alwo7NcVbubrkh4e4xwcww==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc/-/rpc-6.1.0.tgz", + "integrity": "sha512-R3y5PklW9mPy5Y34hsXj40R28zN2N7AGLnHqYJVkXkllwVub/QCNpSdDxAnbbS5EGOYGoUOW8s5LFoXwMSr1LQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0", + "@solana/fast-stable-stringify": "6.1.0", + "@solana/functional": "6.1.0", + "@solana/rpc-api": "6.1.0", + "@solana/rpc-spec": "6.1.0", + "@solana/rpc-spec-types": "6.1.0", + "@solana/rpc-transformers": "6.1.0", + "@solana/rpc-transport-http": "6.1.0", + "@solana/rpc-types": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-api/-/rpc-api-6.1.0.tgz", + "integrity": "sha512-+hO5+kZjJHuUNATUQxlJ1+ztXFkgn1j46zRwt3X7kF+VHkW3wsQ7up0JTS+Xsacmkrj1WKfymQweq8JTrsAG8A==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/codecs-core": "6.1.0", + "@solana/codecs-strings": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/keys": "6.1.0", + "@solana/rpc-parsed-types": "6.1.0", + "@solana/rpc-spec": "6.1.0", + "@solana/rpc-transformers": "6.1.0", + "@solana/rpc-types": "6.1.0", + "@solana/transaction-messages": "6.1.0", + "@solana/transactions": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-parsed-types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-parsed-types/-/rpc-parsed-types-6.1.0.tgz", + "integrity": "sha512-YKccynVgWt/gbs0tBYstNw6BSVuOeWdeAldTB2OgH95o2Q04DpO4v97X1MZDysA4SvSZM30Ek5Ni5ss3kskgdw==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-spec": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec/-/rpc-spec-6.1.0.tgz", + "integrity": "sha512-RxpkIGizCYhXGUcap7npV2S/rAXZ7P/liozY/ExjMmCxYTDwGIW33kp/uH/JRxuzrL8+f8FqY76VsqqIe+2VZw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0", + "@solana/rpc-spec-types": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-spec-types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec-types/-/rpc-spec-types-6.1.0.tgz", + "integrity": "sha512-tldMv1b6VGcvcRrY5MDWKlsyEKH6K96zE7gAIpKDX2G4T47ZOV+OMA3nh6xQpRgtyCUBsej0t80qmvTBDX/5IQ==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions/-/rpc-subscriptions-6.1.0.tgz", + "integrity": "sha512-sqwj+cQinWcZ7M/9+cudKxMPTkTQyGP73980vPCWM7vCpPkp2qzgrEie4DdgDGo+NMwIjeFgu2kdUuLHI3GD/g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0", + "@solana/fast-stable-stringify": "6.1.0", + "@solana/functional": "6.1.0", + "@solana/promises": "6.1.0", + "@solana/rpc-spec-types": "6.1.0", + "@solana/rpc-subscriptions-api": "6.1.0", + "@solana/rpc-subscriptions-channel-websocket": "6.1.0", + "@solana/rpc-subscriptions-spec": "6.1.0", + "@solana/rpc-transformers": "6.1.0", + "@solana/rpc-types": "6.1.0", + "@solana/subscribable": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-6.1.0.tgz", + "integrity": "sha512-I6J+3VU0dda6EySKbDyd+1urC7RGIRPRp0DcWRVcy68NOLbq0I5C40Dn9O2Zf8iCdK4PbQ7JKdCvZ/bDd45hdg==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/keys": "6.1.0", + "@solana/rpc-subscriptions-spec": "6.1.0", + "@solana/rpc-transformers": "6.1.0", + "@solana/rpc-types": "6.1.0", + "@solana/transaction-messages": "6.1.0", + "@solana/transactions": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions-channel-websocket": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-6.1.0.tgz", + "integrity": "sha512-vsx9b+uyCr9L3giao/BTiBFA8DxV5+gDNFq0t5uL21uQ17JXzBektwzHuHoth9IjkvXV/h+IhwXfuLE9Qm4GQg==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0", + "@solana/functional": "6.1.0", + "@solana/rpc-subscriptions-spec": "6.1.0", + "@solana/subscribable": "6.1.0", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions-spec": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-6.1.0.tgz", + "integrity": "sha512-P06jhqzHpZGaLeJmIQkpDeMDD1xUp53ARpmXMsduMC+U5ZKQt29CLo+JrR18boNtls6WfttjVMEbzF25/4UPVA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0", + "@solana/promises": "6.1.0", + "@solana/rpc-spec-types": "6.1.0", + "@solana/subscribable": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-transformers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transformers/-/rpc-transformers-6.1.0.tgz", + "integrity": "sha512-OsSuuRPmsmS02eR9Zz+4iTsr+21hvEMEex5vwbwN6LAGPFlQ4ohqGkxgZCwmYd+Q5HWpnn9Uuf1MDTLLrKQkig==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0", + "@solana/functional": "6.1.0", + "@solana/nominal-types": "6.1.0", + "@solana/rpc-spec-types": "6.1.0", + "@solana/rpc-types": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-transport-http": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transport-http/-/rpc-transport-http-6.1.0.tgz", + "integrity": "sha512-3ebaTYuglLJagaXtjwDPVI7SQeeeFN2fpetpGKsuMAiti4fzYqEkNN8FIo+nXBzqqG/cVc2421xKjXl6sO1k/g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0", + "@solana/rpc-spec": "6.1.0", + "@solana/rpc-spec-types": "6.1.0", + "undici-types": "^7.21.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-types/-/rpc-types-6.1.0.tgz", + "integrity": "sha512-lR+Cb3v5Rpl49HsXWASy++TSE1AD86eRKabY+iuWnbBMYVGI4MamAvYwgBiygsCNc30nyO2TFNj9STMeSD/gAg==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/codecs-core": "6.1.0", + "@solana/codecs-numbers": "6.1.0", + "@solana/codecs-strings": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/nominal-types": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/signers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/signers/-/signers-6.1.0.tgz", + "integrity": "sha512-WDPGZJr6jIe2dEChv/2KQBnaga8dqOjd6ceBj/HcDHxnCudo66t7GlyZ9+9jMO40AgOOb7EDE5FDqPMrHMg5Yw==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/codecs-core": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/instructions": "6.1.0", + "@solana/keys": "6.1.0", + "@solana/nominal-types": "6.1.0", + "@solana/offchain-messages": "6.1.0", + "@solana/transaction-messages": "6.1.0", + "@solana/transactions": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/subscribable": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/subscribable/-/subscribable-6.1.0.tgz", + "integrity": "sha512-HiUfkxN7638uxPmY4t0gI4+yqnFLZYJKFaT9EpWIuGrOB1d9n+uOHNs3NU7cVMwWXgfZUbztTCKyCVTbcwesNg==", + "license": "MIT", + "dependencies": { + "@solana/errors": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/sysvars": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-6.1.0.tgz", + "integrity": "sha512-KwJyBBrAOx0BgkiZqOKAaySDb/0JrUFSBQL9/O1kSKGy9TCRX55Ytr1HxNTcTPppWNpbM6JZVK+yW3Ruey0HRw==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "6.1.0", + "@solana/codecs": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/rpc-types": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/transaction-confirmation": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-confirmation/-/transaction-confirmation-6.1.0.tgz", + "integrity": "sha512-akSjcqAMOGPFvKctFDSzhjcRc/45WbEVdVQ9mjgH6OYo7B11WZZZaeGPlzAw5KyuG34Px941xmICkBmNqEH47Q==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/codecs-strings": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/keys": "6.1.0", + "@solana/promises": "6.1.0", + "@solana/rpc": "6.1.0", + "@solana/rpc-subscriptions": "6.1.0", + "@solana/rpc-types": "6.1.0", + "@solana/transaction-messages": "6.1.0", + "@solana/transactions": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/transaction-messages": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-messages/-/transaction-messages-6.1.0.tgz", + "integrity": "sha512-Dpv54LRVcfFbFEa/uB53LaY/TRfKuPGMKR7Z4F290zBgkj9xkpZkI+WLiJBiSloI7Qo2KZqXj3514BIeZvJLcg==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/codecs-core": "6.1.0", + "@solana/codecs-data-structures": "6.1.0", + "@solana/codecs-numbers": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/functional": "6.1.0", + "@solana/instructions": "6.1.0", + "@solana/nominal-types": "6.1.0", + "@solana/rpc-types": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@solana/transactions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@solana/transactions/-/transactions-6.1.0.tgz", + "integrity": "sha512-1dkiNJcTtlHm4Fvs5VohNVpv7RbvbUYYKV7lYXMPIskoLF1eZp0tVlEqD/cRl91RNz7HEysfHqBAwlcJcRmrRg==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "6.1.0", + "@solana/codecs-core": "6.1.0", + "@solana/codecs-data-structures": "6.1.0", + "@solana/codecs-numbers": "6.1.0", + "@solana/codecs-strings": "6.1.0", + "@solana/errors": "6.1.0", + "@solana/functional": "6.1.0", + "@solana/instructions": "6.1.0", + "@solana/keys": "6.1.0", + "@solana/nominal-types": "6.1.0", + "@solana/rpc-types": "6.1.0", + "@solana/transaction-messages": "6.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", + "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ecdsa-secp256r1": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/ecdsa-secp256r1/-/ecdsa-secp256r1-1.3.3.tgz", + "integrity": "sha512-7JkHQ43iv9vT1U9tyGuxcE4+SMF/da+YiIMRpcwmbHmJQmqfFUuT6c7LKMasJ9soEpwFL0b9JyNkRjQ+vjVgpQ==", + "license": "MIT", + "dependencies": { + "asn1.js": "^5.0.1", + "bn.js": "^4.11.8" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.22.0.tgz", + "integrity": "sha512-RKZvifiL60xdsIuC80UY0dq8Z7DbJUV8/l2hOVbyZAxBzEeQU4Z58+4ZzJ6WN2Lidi9KzT5EbiGX+PI/UGYuRw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/tests-real-rpc/package.json b/tests-real-rpc/package.json new file mode 100644 index 0000000..08a8dc5 --- /dev/null +++ b/tests-real-rpc/package.json @@ -0,0 +1,25 @@ +{ + "name": "lazorkit-tests-real-rpc", + "version": "1.0.0", + "description": "Real RPC End-to-End Tests for LazorKit", + "main": "index.js", + "scripts": { + "test": "vitest run", + "test:local": "./scripts/test-local.sh", + "test:devnet": "vitest run --fileParallelism=false --testTimeout 60000", + "test:devnet:file": "vitest run --fileParallelism=false --testTimeout 60000" + }, + "dependencies": { + "@solana/kit": "^6.0.1", + "@lazorkit/codama-client": "file:../sdk/codama-client", + "bs58": "^6.0.0", + "dotenv": "^16.4.5", + "ecdsa-secp256r1": "^1.3.3" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.9.3", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + } +} diff --git a/tests-real-rpc/scripts/test-local.sh b/tests-real-rpc/scripts/test-local.sh new file mode 100755 index 0000000..51146b3 --- /dev/null +++ b/tests-real-rpc/scripts/test-local.sh @@ -0,0 +1,52 @@ +#!/bin/bash +set -e + +# Configuration +TEST_DIR="$(pwd)/tests-real-rpc" +SOLANA_DIR="$TEST_DIR/.test-ledger" +PROGRAM_DIR="$(cd ../program && pwd)" +DEPLOY_DIR="$(cd ../target/deploy && pwd)" + +# Define cleanup function to safely shut down validator on exit +function cleanup { + echo "-> Cleaning up..." + if [ -n "$VALIDATOR_PID" ]; then + kill $VALIDATOR_PID || true + fi + rm -rf "$SOLANA_DIR" +} +trap cleanup EXIT + +echo "=========================================================" +echo "🔬 Starting LazorKit Local Validator and E2E Tests..." +echo "=========================================================" + +# 1. Start solana-test-validator in the background +echo "-> Starting solana-test-validator..." +mkdir -p "$SOLANA_DIR" + +solana-test-validator \ + --ledger "$SOLANA_DIR" \ + --bpf-program DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2 "$DEPLOY_DIR/lazorkit_program.so" \ + --reset \ + --quiet & + +VALIDATOR_PID=$! + +# Wait for validator to be ready +echo "-> Waiting for validator to start..." +while ! curl -s http://127.0.0.1:8899 > /dev/null; do + sleep 1 +done +echo "-> Validator is up!" + +# Set connection pointing to our local node +export RPC_URL="http://127.0.0.1:8899" +export WS_URL="ws://127.0.0.1:8900" + +# 2. Run Test Suite +echo "-> Running Vitest suite sequentially..." +cd "$TEST_DIR" +npm run test -- --fileParallelism=false --testTimeout=30000 --hookTimeout=30000 + +echo "✅ All tests completed!" diff --git a/tests-real-rpc/test-ecdsa.js b/tests-real-rpc/test-ecdsa.js new file mode 100644 index 0000000..2704a9f --- /dev/null +++ b/tests-real-rpc/test-ecdsa.js @@ -0,0 +1,15 @@ +const ECDSA = require('ecdsa-secp256r1'); + +async function main() { + const key = await ECDSA.generateKey(); + const pub = key.toCompressedPublicKey(); + console.log("pub base64 len:", pub.length, pub); + console.log("pub buf len:", Buffer.from(pub, 'base64').length); + + const msg = Buffer.alloc(32, 1); + const sig = await key.sign(msg); + console.log("sig base64:", sig); + console.log("sig buffer len:", Buffer.from(sig, 'base64').length); +} + +main().catch(console.error); diff --git a/tests-real-rpc/tests/audit_regression.test.ts b/tests-real-rpc/tests/audit_regression.test.ts new file mode 100644 index 0000000..04d03ef --- /dev/null +++ b/tests-real-rpc/tests/audit_regression.test.ts @@ -0,0 +1,256 @@ + +import { describe, it, expect, beforeAll } from "vitest"; +import { + type Address, + generateKeyPairSigner, + getAddressEncoder, + type TransactionSigner, + getProgramDerivedAddress, +} from "@solana/kit"; +import { + setupTest, + processInstruction, + tryProcessInstruction, + type TestContext, + getSystemTransferIx, + PROGRAM_ID_STR +} from "./common"; +import { + findWalletPda, + findVaultPda, + findAuthorityPda, + findSessionPda +} from "@lazorkit/codama-client/src"; + +function getRandomSeed() { + return new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); +} + +describe("Audit Regression Suite", () => { + let context: TestContext; + let client: any; + let walletPda: Address; + let vaultPda: Address; + let owner: TransactionSigner; + let ownerAuthPda: Address; + + beforeAll(async () => { + ({ context, client } = await setupTest()); + + const userSeed = getRandomSeed(); + [walletPda] = await findWalletPda(userSeed); + [vaultPda] = await findVaultPda(walletPda); + owner = await generateKeyPairSigner(); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + let authBump; + [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + // Fund the wallet with 100 SOL + await processInstruction(context, getSystemTransferIx(context.payer, vaultPda, 100_000_000n)); + + // Create the wallet + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })); + + // Fund vault + await processInstruction(context, getSystemTransferIx(context.payer, vaultPda, 100_000_000n)); + }); + + it("Regression 1: SweepTreasury preserves rent-exemption and remains operational", async () => { + // 1. Get current balance of shard + const initialBalance = await context.rpc.getBalance(context.treasuryShard).send(); + console.log(`Initial Shard Balance: ${initialBalance.value} lamports`); + + // 2. Perform Sweep (Shard ID is derived in setupTest, usually 0-15) + // We need to know which shard we are using + const pubkeyBytes = getAddressEncoder().encode(context.payer.address); + const sum = pubkeyBytes.reduce((a, b) => a + b, 0); + const shardId = sum % 16; + + const sweepIx = client.sweepTreasury({ + admin: context.payer, + config: context.configPda, + treasuryShard: context.treasuryShard, + destination: context.payer.address, + shardId, + }); + + console.log("SweepTreasury Accounts:", sweepIx.accounts.map((a: any) => a.address)); + const signature = await processInstruction(context, sweepIx, [context.payer]); + + const tx = await context.rpc + .getTransaction(signature, { + maxSupportedTransactionVersion: 0 + }) + .send(); + + console.log("SweepTreasury Transaction Log:", tx.meta.logMessages); + + // 3. Verify balance is exactly rent-exempt (890,880 for 0 bytes) + const postSweepBalance = await context.rpc.getBalance(context.treasuryShard).send(); + const RENT_EXEMPT_MIN = 890_880n; + expect(postSweepBalance.value).toBe(RENT_EXEMPT_MIN); + console.log(`Post-Sweep Shard Balance: ${postSweepBalance.value} lamports (Verified Rent-Exempt)`); + + // 4. Operationality Check: Perform an 'Execute' which charges action_fee + // If Sweep didn't leave rent, this would FAIL with RentExemption error + const recipient = (await generateKeyPairSigner()).address; + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: ownerAuthPda, + vault: vaultPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 890_880n) + ], + authorizerSigner: owner, + }); + + const signature2 = await processInstruction(context, executeIx, [owner]); + + const tx2 = await context.rpc + .getTransaction(signature2, { + maxSupportedTransactionVersion: 0 + }) + .send(); + + + console.log("Execute Transaction Log:", tx2.meta.logMessages); + + // Read actual action_fee from config (may have been modified by config.test.ts) + const configInfo = await context.rpc.getAccountInfo(context.configPda, { encoding: 'base64' }).send(); + const configData = Buffer.from(configInfo.value!.data[0] as string, 'base64'); + const actionFee = configData.readBigUInt64LE(48); // offset: 1+1+1+1+4+32+8 = 48 + + const finalBalance = await context.rpc.getBalance(context.treasuryShard).send(); + // Should be RENT_EXEMPT_MIN + action_fee (from the Execute above) + wallet_fee (from createWallet) + // However, sweep happened between createWallet and execute, so only the execute fee remains + expect(finalBalance.value).toBe(RENT_EXEMPT_MIN + actionFee); + console.log(`Operationality Check Passed: Shard accepted new fees (${actionFee} lamports) after sweep.`); + }); + + it("Regression 2: CloseWallet rejects self-transfer to prevent burn", async () => { + // Attempt to close wallet with vault as destination + const closeIx = client.closeWallet({ + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + ownerAuthority: ownerAuthPda, + destination: vaultPda, // ATTACK: Self-transfer + ownerSigner: owner, + }); + + const result = await tryProcessInstruction(context, closeIx, [owner]); + // Should fail with InvalidArgument (Solana Error or Custom 0xbbd/etc if defined, but here we expect rejection) + expect(result.result).toMatch(/simulation failed|InvalidArgument|3004/i); + console.log("Self-transfer rejection verified."); + }); + + it("Regression 3: CloseSession rejects Config PDA spoofing", async () => { + const sessionKey = await generateKeyPairSigner(); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + await processInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: Uint8Array.from(getAddressEncoder().encode(sessionKey.address)), + expiresAt: BigInt(2 ** 62), + authorizerSigner: owner, + }), [owner]); + + // Create a FAKE Config PDA + const [fakeConfigPda] = await getProgramDerivedAddress({ + programAddress: context.payer.address, + seeds: ["fake_config"], + }); + + // This test is tricky because we can't easily "initialize" a fake config with our own admin + // unless we deploy another instance or use a mock. + // However, the check `find_program_address(["config"], program_id)` on-chain will catch it. + + const closeSessionIx = client.closeSession({ + payer: context.payer, + wallet: walletPda, + session: sessionPda, + config: fakeConfigPda, // SPOOFED + authorizer: ownerAuthPda, + authorizerSigner: owner, + }); + + const result = await tryProcessInstruction(context, closeSessionIx, [owner]); + // Should fail with InvalidSeeds (0x7d0 or similar) + expect(result.result).toMatch(/InvalidSeeds|simulation failed/i); + console.log("Config PDA spoofing protection verified."); + }); + + it("Regression 4: Verify no protocol fees on cleanup instructions", async () => { + const initialPayerBalance = await context.rpc.getBalance(context.payer.address).send(); + + // 1. Close Session (should be free in terms of protocol fees, only network fees) + const sessionKey = await generateKeyPairSigner(); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + await processInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: Uint8Array.from(getAddressEncoder().encode(sessionKey.address)), + expiresAt: BigInt(2 ** 62), + authorizerSigner: owner, + }), [owner]); + + const preCloseBalance = await context.rpc.getBalance(context.payer.address).send(); + + await processInstruction(context, client.closeSession({ + payer: context.payer, + wallet: walletPda, + session: sessionPda, + config: context.configPda, + authorizer: ownerAuthPda, + authorizerSigner: owner, + }), [owner]); + + const postCloseBalance = await context.rpc.getBalance(context.payer.address).send(); + + // Rent for Session is roughly 0.002 SOL. + // If protocol fee (0.000001) was charged, it would be much less than the rent refund. + // But we want to ensure it's NOT charged to the treasury. + const shardBalanceBefore = await context.rpc.getBalance(context.treasuryShard).send(); + + // Repeat for Wallet + await processInstruction(context, client.closeWallet({ + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + ownerAuthority: ownerAuthPda, + destination: context.payer.address, + ownerSigner: owner, + }), [owner]); + + const shardBalanceAfter = await context.rpc.getBalance(context.treasuryShard).send(); + + // Shard balance should NOT have increased + expect(shardBalanceAfter.value).toBe(shardBalanceBefore.value); + console.log("No-fee verification passed: Shard balance remained constant during cleanup."); + }); +}); diff --git a/tests-real-rpc/tests/authority.test.ts b/tests-real-rpc/tests/authority.test.ts new file mode 100644 index 0000000..dd8e79a --- /dev/null +++ b/tests-real-rpc/tests/authority.test.ts @@ -0,0 +1,664 @@ + +import { describe, it, expect, beforeAll } from "vitest"; +import { + type Address, + generateKeyPairSigner, + getAddressEncoder, + type TransactionSigner +} from "@solana/kit"; +import { setupTest, processInstruction, tryProcessInstruction, type TestContext, getSystemTransferIx, PROGRAM_ID_STR } from "./common"; +import { findWalletPda, findVaultPda, findAuthorityPda } from "@lazorkit/codama-client/src"; + +function getRandomSeed() { + return new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); +} + +describe("Instruction: ManageAuthority (Add/Remove)", () => { + let context: TestContext; + let client: any; + let walletPda: Address; + let owner: TransactionSigner; + let ownerAuthPda: Address; + + beforeAll(async () => { + ({ context, client } = await setupTest()); + + // Setup a wallet + const userSeed = getRandomSeed(); + [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + owner = await generateKeyPairSigner(); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + let authBump; + [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })); + }, 30000); + + it("Success: Owner adds an Admin", async () => { + const newAdmin = await generateKeyPairSigner(); + const newAdminBytes = Uint8Array.from(getAddressEncoder().encode(newAdmin.address)); + const [newAdminPda] = await findAuthorityPda(walletPda, newAdminBytes); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: newAdminPda, + authType: 0, + newRole: 1, // Admin + authPubkey: newAdminBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + const acc = await client.getAuthority(newAdminPda); + expect(acc.role).toBe(1); + }); + + it("Success: Admin adds a Spender", async () => { + // ... (existing Spender test) + const spender = await generateKeyPairSigner(); + const spenderBytes = Uint8Array.from(getAddressEncoder().encode(spender.address)); + const [spenderPda] = await findAuthorityPda(walletPda, spenderBytes); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, // Using owner to add admin for next step + newAuthority: spenderPda, + authType: 0, + newRole: 2, + authPubkey: spenderBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + const acc = await client.getAuthority(spenderPda); + expect(acc.role).toBe(2); + }); + + it("Success: Owner adds a Secp256r1 Admin", async () => { + const credentialIdHash = getRandomSeed(); + const p256Pubkey = new Uint8Array(33).map(() => Math.floor(Math.random() * 256)); + p256Pubkey[0] = 0x02; + const [newAdminPda] = await findAuthorityPda(walletPda, credentialIdHash); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: newAdminPda, + authType: 1, // Secp256r1 + newRole: 1, // Admin + authPubkey: p256Pubkey, + credentialHash: credentialIdHash, + authorizerSigner: owner, + }), [owner]); + + const acc = await client.getAuthority(newAdminPda); + expect(acc.authorityType).toBe(1); + expect(acc.role).toBe(1); + }); + + it("Failure: Admin tries to add an Admin", async () => { + const admin = await generateKeyPairSigner(); + const adminBytes = Uint8Array.from(getAddressEncoder().encode(admin.address)); + const [adminPda] = await findAuthorityPda(walletPda, adminBytes); + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: adminPda, + authType: 0, + newRole: 1, + authPubkey: adminBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + const anotherAdmin = await generateKeyPairSigner(); + const anotherAdminBytes = Uint8Array.from(getAddressEncoder().encode(anotherAdmin.address)); + const [anotherAdminPda] = await findAuthorityPda(walletPda, anotherAdminBytes); + + const result = await tryProcessInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: adminPda, + newAuthority: anotherAdminPda, + authType: 0, + newRole: 1, // Admin (Forbidden for Admin) + authPubkey: anotherAdminBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: admin, + }), [admin]); + + expect(result.result).toMatch(/0xbba|3002|simulation failed/i); + }); + + it("Success: Admin removes a Spender", async () => { + const admin = await generateKeyPairSigner(); + const adminBytes = Uint8Array.from(getAddressEncoder().encode(admin.address)); + const [adminPda] = await findAuthorityPda(walletPda, adminBytes); + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: adminPda, + authType: 0, + newRole: 1, + authPubkey: adminBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + const spender = await generateKeyPairSigner(); + const spenderBytes = Uint8Array.from(getAddressEncoder().encode(spender.address)); + const [spenderPda] = await findAuthorityPda(walletPda, spenderBytes); + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: spenderPda, + authType: 0, + newRole: 2, + authPubkey: spenderBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + // Admin removes Spender + await processInstruction(context, client.removeAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: adminPda, + targetAuthority: spenderPda, + refundDestination: context.payer.address, + authorizerSigner: admin, + }), [admin]); + + // Verify removed + const { value: acc } = await context.rpc.getAccountInfo(spenderPda).send(); + expect(acc).toBeNull(); + }); + + it("Failure: Spender tries to remove another Spender", async () => { + const spender1 = await generateKeyPairSigner(); + const s1Bytes = Uint8Array.from(getAddressEncoder().encode(spender1.address)); + const [s1Pda] = await findAuthorityPda(walletPda, s1Bytes); + const spender2 = await generateKeyPairSigner(); + const s2Bytes = Uint8Array.from(getAddressEncoder().encode(spender2.address)); + const [s2Pda] = await findAuthorityPda(walletPda, s2Bytes); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: s1Pda, + authType: 0, + newRole: 2, + authPubkey: s1Bytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: s2Pda, + authType: 0, + newRole: 2, + authPubkey: s2Bytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + const result = await tryProcessInstruction(context, client.removeAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: s1Pda, + targetAuthority: s2Pda, + refundDestination: context.payer.address, + authorizerSigner: spender1, + }), [spender1]); + + expect(result.result).toMatch(/0xbba|3002|simulation failed/i); + }); + + it("Success: Secp256r1 Admin removes a Spender", async () => { + // Create Secp256r1 Admin + const { generateMockSecp256r1Signer, createSecp256r1Instruction } = await import("./secp256r1Utils"); + const secpAdmin = await generateMockSecp256r1Signer(); + const [secpAdminPda] = await findAuthorityPda(walletPda, secpAdmin.credentialIdHash); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: secpAdminPda, + authType: 1, // Secp256r1 + newRole: 1, // Admin + authPubkey: secpAdmin.publicKeyBytes, + credentialHash: secpAdmin.credentialIdHash, + authorizerSigner: owner, + }), [owner]); + + // Create a disposable Spender via the Owner + const victim = await generateKeyPairSigner(); + const victimBytes = Uint8Array.from(getAddressEncoder().encode(victim.address)); + const [victimPda] = await findAuthorityPda(walletPda, victimBytes); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: victimPda, + authType: 0, + newRole: 2, + authPubkey: victimBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + // Secp256r1 Admin removes the victim + const removeAuthIx = client.removeAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: secpAdminPda, + targetAuthority: victimPda, + refundDestination: context.payer.address, + }); + + // Append sysvars AFTER all existing accounts (config/treasury consumed by iterator) + removeAuthIx.accounts = [ + ...(removeAuthIx.accounts || []), + { address: "Sysvar1nstructions1111111111111111111111111" as any, role: 0 }, + { address: "SysvarS1otHashes111111111111111111111111111" as any, role: 0 }, + ]; + + // Fetch current slot and slotHash from SysvarS1otHashes + const slotHashesAddress = "SysvarS1otHashes111111111111111111111111111" as Address; + const accountInfo = await context.rpc.getAccountInfo(slotHashesAddress, { encoding: 'base64' }).send(); + const rawData = Buffer.from(accountInfo.value!.data[0] as string, 'base64'); + const currentSlot = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength).getBigUint64(8, true); + const currentSlotHash = new Uint8Array(rawData.buffer, rawData.byteOffset + 16, 32); + + // SYSVAR Indexes + const sysvarIxIndex = removeAuthIx.accounts.length - 2; // Sysvar1nstructions position + const sysvarSlotIndex = removeAuthIx.accounts.length - 1; // SysvarSlotHashes position + + const { buildSecp256r1AuthPayload, getSecp256r1MessageToSign, generateAuthenticatorData } = await import("./secp256r1Utils"); + const authenticatorDataRaw = generateAuthenticatorData("example.com"); + + const authPayload = buildSecp256r1AuthPayload(sysvarIxIndex, sysvarSlotIndex, authenticatorDataRaw, currentSlot); + + // The Secp256r1 signed payload for remove_authority is strictly `target_auth_pda` + `refund_dest` + const signedPayload = new Uint8Array(64); + signedPayload.set(getAddressEncoder().encode(victimPda), 0); + signedPayload.set(getAddressEncoder().encode(context.payer.address), 32); // refund dest + + const currentSlotBytes = new Uint8Array(8); + new DataView(currentSlotBytes.buffer).setBigUint64(0, currentSlot, true); + + const msgToSign = getSecp256r1MessageToSign( + new Uint8Array([2]), // Discriminator for RemoveAuthority is 2 + authPayload, + signedPayload, + new Uint8Array(getAddressEncoder().encode(context.payer.address)), + new Uint8Array(getAddressEncoder().encode(PROGRAM_ID_STR as import("@solana/kit").Address)), + authenticatorDataRaw, + currentSlotBytes + ); + + // Append authPayload to removeAuthIx data (since Secp256r1Authenticator parses it from instruction_data) + const newIxData = new Uint8Array(removeAuthIx.data.length + authPayload.length); + newIxData.set(removeAuthIx.data, 0); + newIxData.set(authPayload, removeAuthIx.data.length); + removeAuthIx.data = newIxData; + + const sysvarIx = await createSecp256r1Instruction(secpAdmin, msgToSign); + + const { tryProcessInstructions } = await import("./common"); + const result = await tryProcessInstructions(context, [sysvarIx, removeAuthIx]); + + expect(result.result).toBe("ok"); + + // Verify removed + const { value: acc } = await context.rpc.getAccountInfo(victimPda).send(); + expect(acc).toBeNull(); + }); + + // --- Category 2: SDK Encoding Correctness --- + + it("Encoding: AddAuthority Secp256r1 data matches expected binary layout", async () => { + const credentialIdHash = new Uint8Array(32).fill(0xCC); + const p256Pubkey = new Uint8Array(33).fill(0xDD); + p256Pubkey[0] = 0x03; + + const [newAuthPda] = await findAuthorityPda(walletPda, credentialIdHash); + + const ix = client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: newAuthPda, + authType: 1, // Secp256r1 + newRole: 2, // Spender + authPubkey: p256Pubkey, + credentialHash: credentialIdHash, + authorizerSigner: owner, + }); + + const data = new Uint8Array(ix.data); + // Layout: [disc(1)][authType(1)][newRole(1)][padding(6)][credIdHash(32)][pubkey(33)] + // Total: 1 + 1 + 1 + 6 + 32 + 33 = 74 + expect(data[0]).toBe(1); // discriminator = AddAuthority + expect(data[1]).toBe(1); // authType = Secp256r1 + expect(data[2]).toBe(2); // newRole = Spender + expect(Uint8Array.from(data.subarray(9, 41))).toEqual(Uint8Array.from(credentialIdHash)); // credential_id_hash + expect(Uint8Array.from(data.subarray(41, 74))).toEqual(Uint8Array.from(p256Pubkey)); // pubkey + }); + + // --- Category 4: RBAC Edge Cases --- + + it("Failure: Spender cannot add any authority", async () => { + const spender = await generateKeyPairSigner(); + const spenderBytes = Uint8Array.from(getAddressEncoder().encode(spender.address)); + const [spenderPda] = await findAuthorityPda(walletPda, spenderBytes); + + // Owner adds a Spender + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: spenderPda, + authType: 0, + newRole: 2, + authPubkey: spenderBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + // Spender tries to add another Spender → should fail + const victim = await generateKeyPairSigner(); + const victimBytes = Uint8Array.from(getAddressEncoder().encode(victim.address)); + const [victimPda] = await findAuthorityPda(walletPda, victimBytes); + + const result = await tryProcessInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: spenderPda, + newAuthority: victimPda, + authType: 0, + newRole: 2, + authPubkey: victimBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: spender, + }), [spender]); + + expect(result.result).toMatch(/0xbba|3002|simulation failed/i); // PermissionDenied + }); + + it("Failure: Admin cannot remove Owner", async () => { + const admin = await generateKeyPairSigner(); + const adminBytes = Uint8Array.from(getAddressEncoder().encode(admin.address)); + const [adminPda] = await findAuthorityPda(walletPda, adminBytes); + + // Owner adds an Admin + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: adminPda, + authType: 0, + newRole: 1, + authPubkey: adminBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + // Admin tries to remove Owner → should fail + const result = await tryProcessInstruction(context, client.removeAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: adminPda, + targetAuthority: ownerAuthPda, + refundDestination: context.payer.address, + authorizerSigner: admin, + }), [admin]); + + expect(result.result).toMatch(/simulation failed|3002|0xbba/i); // PermissionDenied + }); + + // --- P1: Cross-Wallet Attack Tests --- + + it("Failure: Authority from Wallet A cannot add authority to Wallet B", async () => { + // Create Wallet B with its own owner + const userSeedB = getRandomSeed(); + const [walletPdaB] = await findWalletPda(userSeedB); + const [vaultPdaB] = await findVaultPda(walletPdaB); + const ownerB = await generateKeyPairSigner(); + const ownerBBytes = Uint8Array.from(getAddressEncoder().encode(ownerB.address)); + const [ownerBAuthPda, ownerBBump] = await findAuthorityPda(walletPdaB, ownerBBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPdaB, + vault: vaultPdaB, + authority: ownerBAuthPda, + userSeed: userSeedB, + authType: 0, + authBump: ownerBBump, + authPubkey: ownerBBytes, + credentialHash: new Uint8Array(32), + })); + + // Wallet A's owner tries to add authority to Wallet B + const victim = await generateKeyPairSigner(); + const victimBytes = Uint8Array.from(getAddressEncoder().encode(victim.address)); + const [victimPda] = await findAuthorityPda(walletPdaB, victimBytes); + + const result = await tryProcessInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPdaB, // Target: Wallet B + adminAuthority: ownerAuthPda, // Using Wallet A's owner + newAuthority: victimPda, + authType: 0, + newRole: 2, + authPubkey: victimBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, // Wallet A's owner signer + }), [owner]); + + expect(result.result).toMatch(/simulation failed|InvalidAccountData/i); + }); + + it("Failure: Authority from Wallet A cannot remove authority in Wallet B", async () => { + // Create Wallet B + const userSeedB = getRandomSeed(); + const [walletPdaB] = await findWalletPda(userSeedB); + const [vaultPdaB] = await findVaultPda(walletPdaB); + const ownerB = await generateKeyPairSigner(); + const ownerBBytes = Uint8Array.from(getAddressEncoder().encode(ownerB.address)); + const [ownerBAuthPda, ownerBBump] = await findAuthorityPda(walletPdaB, ownerBBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPdaB, + vault: vaultPdaB, + authority: ownerBAuthPda, + userSeed: userSeedB, + authType: 0, + authBump: ownerBBump, + authPubkey: ownerBBytes, + credentialHash: new Uint8Array(32), + })); + + // Add a spender to Wallet B + const spenderB = await generateKeyPairSigner(); + const spenderBBytes = Uint8Array.from(getAddressEncoder().encode(spenderB.address)); + const [spenderBPda] = await findAuthorityPda(walletPdaB, spenderBBytes); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPdaB, + adminAuthority: ownerBAuthPda, + newAuthority: spenderBPda, + authType: 0, + newRole: 2, + authPubkey: spenderBBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: ownerB, + }), [ownerB]); + + // Wallet A's owner tries to remove Wallet B's spender + const result = await tryProcessInstruction(context, client.removeAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPdaB, // Target: Wallet B + adminAuthority: ownerAuthPda, // Using Wallet A's owner + targetAuthority: spenderBPda, + refundDestination: context.payer.address, + authorizerSigner: owner, // Wallet A's owner signer + }), [owner]); + + expect(result.result).toMatch(/simulation failed|InvalidAccountData/i); + }); + + // --- P1: Duplicate Authority Creation --- + + it("Failure: Cannot add same authority twice", async () => { + const newUser = await generateKeyPairSigner(); + const newUserBytes = Uint8Array.from(getAddressEncoder().encode(newUser.address)); + const [newUserPda] = await findAuthorityPda(walletPda, newUserBytes); + + // First add — should succeed + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: newUserPda, + authType: 0, + newRole: 2, + authPubkey: newUserBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + // Second add with same pubkey — should fail (AccountAlreadyInitialized) + const result = await tryProcessInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: newUserPda, + authType: 0, + newRole: 2, + authPubkey: newUserBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + expect(result.result).toMatch(/simulation failed|already in use|AccountAlreadyInitialized/i); + }); + + // --- P2: Owner Self-Removal Edge Case --- + + it("Edge: Owner can remove itself (leaves wallet ownerless)", async () => { + // Create a fresh wallet for this test + const userSeed = getRandomSeed(); + const [wPda] = await findWalletPda(userSeed); + const [vPda] = await findVaultPda(wPda); + const o = await generateKeyPairSigner(); + const oBytes = Uint8Array.from(getAddressEncoder().encode(o.address)); + const [oPda, oBump] = await findAuthorityPda(wPda, oBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, vault: vPda, authority: oPda, + userSeed, authType: 0, authBump: oBump, + authPubkey: oBytes, credentialHash: new Uint8Array(32), + })); + + // Owner removes itself + await processInstruction(context, client.removeAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, + adminAuthority: oPda, + targetAuthority: oPda, + refundDestination: context.payer.address, + authorizerSigner: o, + }), [o]); + + // Authority PDA should be closed + const { value: acc } = await context.rpc.getAccountInfo(oPda).send(); + expect(acc).toBeNull(); + }); +}); diff --git a/tests-real-rpc/tests/cleanup.test.ts b/tests-real-rpc/tests/cleanup.test.ts new file mode 100644 index 0000000..f65369d --- /dev/null +++ b/tests-real-rpc/tests/cleanup.test.ts @@ -0,0 +1,444 @@ +import { expect, describe, it, beforeAll } from "vitest"; +import { + generateKeyPairSigner, + lamports, + getAddressEncoder, + type Address, +} from "@solana/kit"; +import { setupTest, processInstructions, tryProcessInstructions, PROGRAM_ID_STR } from "./common"; +import { + getCloseSessionInstruction, + getCloseWalletInstruction, + findWalletPda, + findVaultPda, + findAuthorityPda, + findSessionPda +} from "@lazorkit/codama-client/src"; + +describe("Cleanup Instructions", () => { + let context: any; + let client: any; + + beforeAll(async () => { + const setup = await setupTest(); + context = setup.context; + client = setup.client; + }); + + it("should allow wallet owner to close an active session", async () => { + const { payer, configPda } = context; + + // 1. Create a Wallet + const owner = await generateKeyPairSigner(); + const userSeed = new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstructions(context, [client.createWallet({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })], [payer]); + + // 2. Create a Session + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + // Expiration in the future (active) + const validUntil = BigInt(Math.floor(Date.now() / 1000) + 3600); + + await processInstructions(context, [client.createSession({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: validUntil, + authorizerSigner: owner, + })], [payer, owner]); + + const closeSessionIx = getCloseSessionInstruction({ + payer: payer, + wallet: walletPda, + session: sessionPda, + config: configPda, + authorizer: ownerAuthPda, + authorizerSigner: owner, + }); + + const result = await tryProcessInstructions(context, [closeSessionIx], [payer, owner]); + expect(result.result).toBe("ok"); + }); + + it("should allow contract admin to close an expired session", async () => { + const { payer, configPda } = context; + + const owner = await generateKeyPairSigner(); + const userSeed = new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstructions(context, [client.createWallet({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })], [payer]); + + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + // Expiration in the past (expired). We use 0 since the smart contract validates against the slot number. + const validUntil = 0n; + + await processInstructions(context, [client.createSession({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: validUntil, + authorizerSigner: owner, + })], [payer, owner]); + + // Close the Session (Contract Admin acting as payer) without an authorizer + const closeSessionIx = getCloseSessionInstruction({ + payer: payer, // Payer is the global config admin in this context + wallet: walletPda, + session: sessionPda, + config: configPda, + }); + + const result = await tryProcessInstructions(context, [closeSessionIx], [payer]); + expect(result.result).toBe("ok"); + }); + + it("should reject contract admin closing an active session", async () => { + const { payer, configPda } = context; + + const owner = await generateKeyPairSigner(); + const userSeed = new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstructions(context, [client.createWallet({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })], [payer]); + + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + const validUntil = BigInt(Math.floor(Date.now() / 1000) + 3600); // active + + await processInstructions(context, [client.createSession({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: validUntil, + authorizerSigner: owner, + })], [payer, owner]); + + const closeSessionIx = getCloseSessionInstruction({ + payer: payer, + wallet: walletPda, + session: sessionPda, + config: configPda, + }); + + const result = await tryProcessInstructions(context, [closeSessionIx], [payer]); + // 0x1776 = InvalidAuthority (since admin isn't authorized for active session) + expect(result.result).not.toBe("ok"); + }); + + it("should reject a random user closing an expired session", async () => { + const { payer, configPda } = context; + + const owner = await generateKeyPairSigner(); + const randomUser = await generateKeyPairSigner(); + const userSeed = new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstructions(context, [client.createWallet({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })], [payer]); + + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + const validUntil = 0n; // expired + + await processInstructions(context, [client.createSession({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: validUntil, + authorizerSigner: owner, + })], [payer, owner]); + + const closeSessionIx = getCloseSessionInstruction({ + payer: randomUser, + wallet: walletPda, + session: sessionPda, + config: configPda, + }); + + const result = await tryProcessInstructions(context, [closeSessionIx], [randomUser]); + // Random user is not config admin and has no authorizer token + expect(result.result).not.toBe("ok"); + }); + + it("should allow wallet owner to close a wallet and sweep rent", async () => { + const { rpc, payer, configPda } = context; + + const owner = await generateKeyPairSigner(); + const userSeed = new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstructions(context, [client.createWallet({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })], [payer]); + + // Put some extra sol in the vault + const systemTransferIx = { + programAddress: "11111111111111111111111111111111" as Address, + data: Uint8Array.from([2, 0, 0, 0, ...new Uint8Array(new BigUint64Array([25000000n]).buffer)]), // 0.025 SOL + accounts: [ + { address: payer.address, role: 3, signer: payer }, + { address: vaultPda, role: 1 } + ] + }; + await processInstructions(context, [systemTransferIx], [payer]); + + const destWallet = await generateKeyPairSigner(); + + const closeWalletIxRaw = getCloseWalletInstruction({ + payer: payer, // Transaction fee payer + wallet: walletPda, + vault: vaultPda, + ownerAuthority: ownerAuthPda, + ownerSigner: owner, + destination: destWallet.address, + }); + const closeWalletIx = { + ...closeWalletIxRaw, + accounts: [ + ...closeWalletIxRaw.accounts, + { address: "11111111111111111111111111111111" as Address, role: 1 } + ] + }; + + const result = await tryProcessInstructions(context, [closeWalletIx], [payer, owner]); + expect(result.result).toBe("ok"); + + const destBalance = await rpc.getBalance(destWallet.address).send(); + // Should have received vault funds + rent of wallet/vault + expect(Number(destBalance.value)).toBeGreaterThan(25000000); + }); + + it("should reject non-owner from closing a wallet", async () => { + const { payer, configPda } = context; + + const owner = await generateKeyPairSigner(); + const attacker = await generateKeyPairSigner(); + const userSeed = new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstructions(context, [client.createWallet({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })], [payer]); + + const closeWalletIxRaw = getCloseWalletInstruction({ + payer: attacker, + wallet: walletPda, + vault: vaultPda, + ownerAuthority: ownerAuthPda, + ownerSigner: attacker, + destination: attacker.address, + }); + const closeWalletIx = { + ...closeWalletIxRaw, + accounts: [ + ...closeWalletIxRaw.accounts, + { address: "11111111111111111111111111111111" as Address, role: 1 } + ] + }; + + const result = await tryProcessInstructions(context, [closeWalletIx], [attacker]); + // 0x1776 = InvalidAuthority because the attacker's signer won't match the owner authority + expect(result.result).not.toBe("ok"); + }); + + it("should reject closing wallet if destination is the vault PDA", async () => { + const { payer, configPda } = context; + + const owner = await generateKeyPairSigner(); + const userSeed = new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstructions(context, [client.createWallet({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })], [payer]); + + const closeWalletIxRaw = getCloseWalletInstruction({ + payer: payer, + wallet: walletPda, + vault: vaultPda, + ownerAuthority: ownerAuthPda, + ownerSigner: owner, + destination: vaultPda, // self-destruct bug reproduction + }); + const closeWalletIx = { + ...closeWalletIxRaw, + accounts: [ + ...closeWalletIxRaw.accounts, + { address: "11111111111111111111111111111111" as Address, role: 1 } + ] + }; + + const result = await tryProcessInstructions(context, [closeWalletIx], [payer, owner]); + // ProgramError::InvalidArgument = 160 = 0xa0 + expect(result.result).toMatch(/0xa0|160|InvalidArgument|invalid program argument/i); + }); + + it("should reject closing wallet if destination is the wallet PDA", async () => { + const { payer, configPda } = context; + + const owner = await generateKeyPairSigner(); + const userSeed = new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstructions(context, [client.createWallet({ + config: configPda, + treasuryShard: context.treasuryShard, + payer: payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })], [payer]); + + const closeWalletIxRaw = getCloseWalletInstruction({ + payer: payer, + wallet: walletPda, + vault: vaultPda, + ownerAuthority: ownerAuthPda, + ownerSigner: owner, + destination: walletPda, // self-destruct bug reproduction + }); + const closeWalletIx = { + ...closeWalletIxRaw, + accounts: [ + ...closeWalletIxRaw.accounts, + { address: "11111111111111111111111111111111" as Address, role: 1 } + ] + }; + + const result = await tryProcessInstructions(context, [closeWalletIx], [payer, owner]); + // ProgramError::InvalidArgument = 160 = 0xa0 + expect(result.result).toMatch(/0xa0|160|InvalidArgument|invalid program argument/i); + }); +}); diff --git a/tests-real-rpc/tests/common.ts b/tests-real-rpc/tests/common.ts new file mode 100644 index 0000000..a74ade7 --- /dev/null +++ b/tests-real-rpc/tests/common.ts @@ -0,0 +1,336 @@ +import { + createSolanaRpc, + address, + type Address, + type TransactionSigner, + type Instruction, + generateKeyPairSigner, + pipe, + createTransactionMessage, + setTransactionMessageFeePayerSigner, + setTransactionMessageLifetimeUsingBlockhash, + appendTransactionMessageInstruction, + addSignersToTransactionMessage, + compileTransaction, + signTransactionMessageWithSigners, + getBase64EncodedWireTransaction, + sendAndConfirmTransactionFactory, + getSignatureFromTransaction, + createSolanaRpcSubscriptions, + lamports, +} from "@solana/kit"; +import { LazorClient } from "@lazorkit/codama-client/src"; +import * as dotenv from "dotenv"; +import bs58 from "bs58"; +import { createKeyPairSignerFromBytes } from "@solana/kit"; + +dotenv.config(); + +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +export const PROGRAM_ID_STR = "DfmiYzJSaeW4yBinoAF6RNa14gGmhXHiX1DNUofkztY2"; + +export interface TestContext { + rpc: any; + rpcSubscriptions: any; + payer: TransactionSigner; + configPda: Address; + treasuryShard: Address; +} + +export async function setupTest(): Promise<{ context: TestContext, client: LazorClient }> { + const rpcUrl = process.env.RPC_URL || "http://127.0.0.1:8899"; + const wsUrl = process.env.WS_URL || "ws://127.0.0.1:8900"; + const rpc = createSolanaRpc(rpcUrl); + const rpcSubscriptions = createSolanaRpcSubscriptions(wsUrl); + + let payer: TransactionSigner; + let skipAirdrop = false; + + if (process.env.PRIVATE_KEY) { + let keyBytes: Uint8Array; + if (process.env.PRIVATE_KEY.startsWith('[')) { + keyBytes = new Uint8Array(JSON.parse(process.env.PRIVATE_KEY)); + } else { + keyBytes = bs58.decode(process.env.PRIVATE_KEY); + } + payer = await createKeyPairSignerFromBytes(keyBytes); + skipAirdrop = true; // Use fixed account, usually already has funds + console.log(`Using fixed payer: ${payer.address}`); + } else { + payer = await generateKeyPairSigner(); + } + + // Check balance and log it + try { + const balance = await rpc.getBalance(payer.address).send(); + console.log(`Payer balance: ${Number(balance.value) / 1e9} SOL`); + + // If balance is low (< 0.5 SOL), try airdrop anyway (if not on mainnet) + if (balance.value < 500_000_000n && !rpcUrl.includes("mainnet")) { + console.log("Balance low. Attempting airdrop..."); + await rpc.requestAirdrop(payer.address, lamports(1_000_000_000n)).send(); + await sleep(2000); + const newBalance = await rpc.getBalance(payer.address).send(); + console.log(`New balance: ${Number(newBalance.value) / 1e9} SOL`); + } + } catch (e) { + console.warn("Could not check balance or airdrop."); + } + + const client = new LazorClient(rpc); + + // Compute Config and Treasury Shard PDAs + const { findConfigPda, findTreasuryShardPda } = await import("@lazorkit/codama-client/src/utils/pdas"); + const { getAddressEncoder } = await import("@solana/kit"); + + const [configPda] = await findConfigPda(); + const pubkeyBytes = getAddressEncoder().encode(payer.address); + const sum = pubkeyBytes.reduce((a, b) => a + b, 0); + const shardId = sum % 16; + const [treasuryShard] = await findTreasuryShardPda(shardId); + + // Initialize Config if not exists + try { + const accInfo = await rpc.getAccountInfo(configPda, { commitment: "processed" }).send(); + if (!accInfo || !accInfo.value) throw new Error("Not initialized"); + } catch { + console.log("Initializing Global Config and Treasury Shard..."); + const { getInitializeConfigInstruction, getInitTreasuryShardInstruction } = await import("@lazorkit/codama-client/src"); + + const initConfigIx = getInitializeConfigInstruction({ + admin: payer, + config: configPda, + systemProgram: "11111111111111111111111111111111" as Address, + rent: "SysvarRent111111111111111111111111111111111" as Address, + walletFee: 10000n, // 0.00001 SOL + actionFee: 1000n, // 0.000001 SOL + numShards: 16 + }); + + const initShardIx = getInitTreasuryShardInstruction({ + payer: payer, + config: configPda, + treasuryShard: treasuryShard, + systemProgram: "11111111111111111111111111111111" as Address, + rent: "SysvarRent111111111111111111111111111111111" as Address, + shardId, + }); + + try { + await processInstructions({ rpc, rpcSubscriptions, payer, configPda, treasuryShard }, [initConfigIx, initShardIx], [payer]); + } catch (e: any) { + // Ignore if already initialized by another parallel/sequential test + console.warn("Config initialization skipped or failed:", e.message); + } + } + + // Initialize Treasury Shard if not exists (in case config existed but not this shard) + try { + const accInfo = await rpc.getAccountInfo(treasuryShard, { commitment: "processed" }).send(); + if (!accInfo || !accInfo.value) throw new Error("Not initialized"); + } catch { + console.log(`Initializing Treasury Shard ${shardId}...`); + const { getInitTreasuryShardInstruction } = await import("@lazorkit/codama-client/src"); + const initShardIx = getInitTreasuryShardInstruction({ + payer: payer, + config: configPda, + treasuryShard: treasuryShard, + systemProgram: "11111111111111111111111111111111" as Address, + rent: "SysvarRent111111111111111111111111111111111" as Address, + shardId, + }); + + try { + await processInstructions({ rpc, rpcSubscriptions, payer, configPda, treasuryShard }, [initShardIx], [payer]); + } catch (e: any) { + console.warn(`Shard ${shardId} initialization skipped or failed:`, e.message); + } + } + + return { + context: { rpc, rpcSubscriptions, payer, configPda, treasuryShard }, + client + }; +} + +export async function processInstruction(context: TestContext, ix: any, signers: TransactionSigner[] = [], extraAccounts: any[] = []) { + const { rpc, rpcSubscriptions, payer } = context; + + let retries = 0; + const maxRetries = 3; + + while (retries < maxRetries) { + try { + // Add a small delay for Devnet to avoid 429 + if (process.env.RPC_URL?.includes("devnet")) { + await sleep(1000 + (retries * 2000)); // Exponential backoff + } + + const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); + + const accounts = [...(ix.accounts || [])]; + console.log("Execute accounts order:", accounts.map(a => a.address)); + for (const acc of extraAccounts) { + accounts.push(acc); + } + + const transactionMessage = pipe( + createTransactionMessage({ version: 0 }), + m => setTransactionMessageFeePayerSigner(payer, m), + m => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), + m => appendTransactionMessageInstruction({ + ...ix, + accounts + } as Instruction, m), + m => addSignersToTransactionMessage(signers, m) + ); + + const signedTransaction = await signTransactionMessageWithSigners(transactionMessage); + const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions }); + + await sendAndConfirm(signedTransaction as any, { + commitment: 'confirmed', + }); + + return getSignatureFromTransaction(signedTransaction); + + } catch (e: any) { + const isRateLimit = e.message?.includes("429") || + e.context?.headers?.status === 429 || + e.context?.status === 429; + if (isRateLimit && retries < maxRetries - 1) { + retries++; + console.log(`Rate limited (429). Retrying ${retries}/${maxRetries}...`); + continue; + } + + if (e.context?.logs) { + console.error("Simulation Logs:\n", e.context.logs.join("\n")); + } + throw e; + } + } + throw new Error("Max retries exceeded for transaction"); +} + +export async function tryProcessInstructions(context: TestContext, ixs: any[], signers: TransactionSigner[] = []) { + try { + const signature = await processInstructions(context, ixs, signers); + return { result: "ok", signature }; + } catch (e: any) { + console.error("DEBUG: Instructions failed:", e); + + let result = e.message || "Unknown Error"; + + // Extract error code if available (Solana v2 style) + const code = e.context?.code || e.cause?.context?.code || e.data?.code; + if (code !== undefined) { + result += ` (Code: ${code})`; + } + + // Include logs which often contain the actual program error message + const logs = e.context?.logs || e.cause?.context?.logs || e.data?.logs || []; + if (logs.length > 0) { + result += " | LOGS: " + logs.join("\n"); + } + + return { result }; + } +} + +export async function processInstructions(context: TestContext, ixs: any[], signers: TransactionSigner[] = []) { + const { rpc, rpcSubscriptions, payer } = context; + + let retries = 0; + const maxRetries = 3; + + while (retries < maxRetries) { + try { + if (process.env.RPC_URL?.includes("devnet")) { + await sleep(1000 + (retries * 2000)); + } + + const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); + + let transactionMessage = pipe( + createTransactionMessage({ version: 0 }), + m => setTransactionMessageFeePayerSigner(payer, m), + m => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m) + ); + + for (const ix of ixs) { + transactionMessage = appendTransactionMessageInstruction(ix as any, transactionMessage as any) as any; + } + + transactionMessage = addSignersToTransactionMessage(signers, transactionMessage); + + const signedTransaction = await signTransactionMessageWithSigners(transactionMessage as any); + const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions }); + + await sendAndConfirm(signedTransaction as any, { + commitment: 'confirmed', + }); + + return getSignatureFromTransaction(signedTransaction); + + } catch (e: any) { + const isRateLimit = e.message?.includes("429") || + e.context?.headers?.status === 429 || + e.context?.status === 429; + if (isRateLimit && retries < maxRetries - 1) { + retries++; + console.log(`Rate limited (429). Retrying ${retries}/${maxRetries}...`); + continue; + } + + if (e.context?.logs) { + console.error("Simulation Logs:\n", e.context.logs.join("\n")); + } + throw e; + } + } + throw new Error("Max retries reached"); +} + +export async function tryProcessInstruction(context: TestContext, ix: any, signers: TransactionSigner[] = []) { + try { + const signature = await processInstruction(context, ix, signers); + return { result: "ok", signature }; + } catch (e: any) { + console.error("DEBUG: Instruction failed:", e); + + let result = e.message || "Unknown Error"; + + // Extract error code if available (Solana v2 style) + const code = e.context?.code || e.cause?.context?.code || e.data?.code; + if (code !== undefined) { + result += ` (Code: ${code})`; + } + + // Include logs which often contain the actual program error message + const logs = e.context?.logs || e.cause?.context?.logs || e.data?.logs || []; + if (logs.length > 0) { + result += " | LOGS: " + logs.join("\n"); + } + + return { result }; + } +} + +export function getSystemTransferIx(from: TransactionSigner | Address, to: Address, amount: bigint) { + const fromAddress = typeof from === 'string' ? from : from.address; + const fromSigner = typeof from === 'string' ? undefined : from; + const data = new Uint8Array(12); + data[0] = 2; // Transfer + const view = new DataView(data.buffer); + view.setBigUint64(4, amount, true); + return { + programAddress: "11111111111111111111111111111111" as Address, + accounts: [ + { address: fromAddress, role: 3, ...(fromSigner ? { signer: fromSigner } : {}) }, + { address: to, role: 1 }, + ], + data + }; +} diff --git a/tests-real-rpc/tests/config.test.ts b/tests-real-rpc/tests/config.test.ts new file mode 100644 index 0000000..facbfd4 --- /dev/null +++ b/tests-real-rpc/tests/config.test.ts @@ -0,0 +1,267 @@ +import { expect, describe, it, beforeAll } from "vitest"; +import { + generateKeyPairSigner, + lamports, + getAddressEncoder, + type Address, +} from "@solana/kit"; +import { setupTest, processInstructions, tryProcessInstructions, PROGRAM_ID_STR } from "./common"; +import { + getInitializeConfigInstruction, + getUpdateConfigInstruction, + getInitTreasuryShardInstruction, + getSweepTreasuryInstruction, + findConfigPda, + findTreasuryShardPda +} from "@lazorkit/codama-client/src"; + +describe("Config and Treasury Instructions", () => { + let context: any; + + beforeAll(async () => { + // Run setupTest to initialize common context, including config and shard 0 + const setup = await setupTest(); + context = setup.context; + }); + + it("should fail to initialize an already initialized Config PDA", async () => { + const { rpc, payer, configPda } = context; + + const initConfigIx = getInitializeConfigInstruction({ + admin: payer, + config: configPda, + systemProgram: "11111111111111111111111111111111" as Address, + rent: "SysvarRent111111111111111111111111111111111" as Address, + walletFee: 10000n, + actionFee: 1000n, + numShards: 16 + }); + + // This should fail because setupTest already initialized it + const result = await tryProcessInstructions(context, [initConfigIx], [payer]); + console.log("INIT ERROR RESULT:", result.result); expect(result.result).to.not.equal("ok"); // "Account already initialized" error code from check_zero_data is typically 0x0 + }); + + it("should update config parameters by admin", async () => { + const { rpc, payer, configPda } = context; + + const data = new Uint8Array(56); + data[0] = 7; // UpdateConfig discriminator (is that right? Instruction 7) + // Wait, LazorKitInstruction enum has UpdateConfig at index 7, but the instruction discriminator for shank is usually 1 byte, let's look at instruction.rs implementation. + // Actually from instruction.rs: `7 => Ok(Self::UpdateConfig),` + // UpdateConfigArgs::from_bytes is called on `instruction_data` which does NOT include the discriminator (it's stripped in `entrypoint.rs`). + // Wait! `UpdateConfigArgs::from_bytes` expects 56 bytes. + // So the total data length needs to be 1 byte (discriminator = 7) + 56 bytes (args) = 57 bytes. + + const ixData = new Uint8Array(57); + ixData[0] = 7; // discriminator + ixData[1] = 1; // updateWalletFee + ixData[2] = 1; // updateActionFee + ixData[3] = 1; // updateNumShards + ixData[4] = 0; // updateAdmin + ixData[5] = 32; // numShards + + const view = new DataView(ixData.buffer); + view.setBigUint64(9, 20000n, true); // walletFee (offset 8 + 1) + view.setBigUint64(17, 2000n, true); // actionFee (offset 16 + 1) + // admin bytes at 25..57 + const adminBytes = getAddressEncoder().encode(payer.address); + ixData.set(adminBytes, 25); + + const updateConfigIx = { + programAddress: PROGRAM_ID_STR as Address, + accounts: [ + { address: payer.address, role: 3, signer: payer }, + { address: configPda, role: 1 }, + ], + data: ixData + }; + + const result = await tryProcessInstructions(context, [updateConfigIx], [payer]); + expect(result.result).to.equal("ok"); + + // Verify state change + const configInfo = await rpc.getAccountInfo(configPda, { commitment: "confirmed" }).send(); + expect(configInfo?.value?.data).to.not.be.null; + + // `@solana/kit` RPC returns data as [base64_string, encoding = "base64"] when using raw getAccountInfo without parsed typing + console.log("RAW CONFIG INFO DATA:", configInfo.value!.data); + /* + const dataBuffer = Buffer.from(configInfo.value!.data[0] as string, "base64"); + const storedNumShards = dataBuffer.readUInt8(3); + console.log("dataBuffer length:", dataBuffer.length); + expect(storedNumShards).to.equal(32); + + const storedWalletFee = dataBuffer.readBigUInt64LE(40); + expect(storedWalletFee).to.equal(20000n); + */ + }); + + it("should reject update config from non-admin", async () => { + const { payer, configPda } = context; + + const nonAdmin = await generateKeyPairSigner(); + + const ixData = new Uint8Array(57); + ixData[0] = 7; // discriminator + ixData[1] = 1; // updateWalletFee + ixData[2] = 0; // updateActionFee + ixData[3] = 0; // updateNumShards + ixData[4] = 0; // updateAdmin + ixData[5] = 32; // numShards + + const adminBytes = getAddressEncoder().encode(nonAdmin.address); + ixData.set(adminBytes, 25); + + const view = new DataView(ixData.buffer); + view.setBigUint64(9, 50000n, true); // walletFee + + const updateConfigIx = { + programAddress: PROGRAM_ID_STR as Address, + accounts: [ + { address: nonAdmin.address, role: 3, signer: nonAdmin }, + { address: configPda, role: 1 }, + ], + data: ixData + }; + + const result = await tryProcessInstructions(context, [updateConfigIx], [nonAdmin]); + console.log("ERROR RESULT:", result.result); expect(result.result).to.not.equal("ok"); // Authority error (6006) + }); + + it("should reject update config if a wrong account type is passed (discriminator check)", async () => { + const { payer, configPda } = context; + // In this test, we'll try to use the Wallet PDA's address in place of the Config PDA. + // Assuming we have a wallet pda from setup or we can just use a dummy address that has a different discriminator. + // But the best is to use an actual PDA from our program but with a different discriminator (e.g., Wallet = 1). + + const ixData = new Uint8Array(57); + ixData[0] = 7; // UpdateConfig + ixData[1] = 1; // updateWalletFee + // ... rest doesn't matter much as long as it reaches discriminator check + + // We'll use the payer as a dummy "config" account. It won't have the correct owner (System Program instead of our program), + // but the code checks Seeds/Owner first. + // To really test the discriminator check, we need an account OWNED by the program but with DIFFERENT discriminator. + // Let's create a wallet first to get a Wallet PDA. + + const walletPda = context.walletPda; // setupTest usually provides this + + const updateConfigIx = { + programAddress: PROGRAM_ID_STR as Address, + accounts: [ + { address: payer.address, role: 3, signer: payer }, + { address: walletPda, role: 1 }, // Pass Wallet PDA instead of Config PDA + ], + data: ixData + }; + + const result = await tryProcessInstructions(context, [updateConfigIx], [payer]); + // Should fail with InvalidAccountData (6003 or similar) due to discriminator mismatch + console.log("DISCRIMINATOR CHECK RESULT:", result.result); + expect(result.result).to.not.equal("ok"); + }); + + it("should initialize a new treasury shard", async () => { + const { payer, configPda } = context; + let treasuryShardPda = "11111111111111111111111111111111" as Address; + let shardId = 0; + + // Using shard 1 since shard 0 or hasher's derived shard was initialized in setup + for (let i = 0; i < 16; i++) { + shardId = i; + treasuryShardPda = (await findTreasuryShardPda(shardId))[0]; + + // check if shard is already initialized + const shardInfo = await context.rpc.getAccountInfo(treasuryShardPda, { commitment: "confirmed" }).send(); + + if (shardInfo?.value === null) { + break; + } + } + + const initShardIx = getInitTreasuryShardInstruction({ + payer: payer, + config: configPda, + treasuryShard: treasuryShardPda, + systemProgram: "11111111111111111111111111111111" as Address, + rent: "SysvarRent111111111111111111111111111111111" as Address, + shardId, + }); + + const result = await tryProcessInstructions(context, [initShardIx], [payer]); + expect(result.result).toEqual("ok"); + }); + + it("should sweep treasury shard funds as admin", async () => { + const { rpc, payer, configPda } = context; + + // Use the shard from setupTest (which we know is initialized) + const { getAddressEncoder: getAddrEnc } = await import("@solana/kit"); + const pubkeyBytes = getAddrEnc().encode(payer.address); + const sum = pubkeyBytes.reduce((a: number, b: number) => a + b, 0); + const shardId = sum % 16; + const [treasuryShardPda] = await findTreasuryShardPda(shardId); + + // Check shard exists and has funds above rent-exempt + const preBalance = await rpc.getBalance(treasuryShardPda).send(); + console.log(`Pre-sweep shard ${shardId} balance: ${preBalance.value}`); + + // Transfer some lamports to shard directly to simulate fees + const systemTransferIx = { + programAddress: "11111111111111111111111111111111" as Address, + data: Uint8Array.from([2, 0, 0, 0, ...new Uint8Array(new BigUint64Array([10000n]).buffer)]), // Transfer + accounts: [ + { address: payer.address, role: 3, signer: payer }, + { address: treasuryShardPda, role: 1 } + ] + }; + await processInstructions(context, [systemTransferIx], [payer]); + + const sweepIx = getSweepTreasuryInstruction({ + admin: payer, + config: configPda, + treasuryShard: treasuryShardPda, + destination: payer.address, + shardId, + }); + + const initialPayerBalance = await rpc.getBalance(payer.address).send(); + const sweepResult = await tryProcessInstructions(context, [sweepIx], [payer]); + expect(sweepResult.result).to.equal("ok"); + + const finalPayerBalance = await rpc.getBalance(payer.address).send(); + expect(Number(finalPayerBalance.value)).to.be.greaterThan(Number(initialPayerBalance.value)); + + const shardBalance = await rpc.getBalance(treasuryShardPda).send(); + // The shard must maintain rent exemption (890880 lamports for 0-byte account) safely. + expect(Number(shardBalance.value)).to.equal(890880); + }); + + it("should reject sweep treasury from non-admin", async () => { + const { configPda } = context; + + const nonAdmin = await generateKeyPairSigner(); + const shardId = 0; // The one from setup + const [treasuryShardPda] = await findTreasuryShardPda(shardId); + + const sweepIxRaw = getSweepTreasuryInstruction({ + admin: nonAdmin, + config: configPda, + treasuryShard: treasuryShardPda, + destination: nonAdmin.address, + shardId, + }); + + const sweepIx = { + ...sweepIxRaw, + accounts: [ + ...sweepIxRaw.accounts, + { address: "11111111111111111111111111111111" as Address, role: 1 } + ] + }; + + const result = await tryProcessInstructions(context, [sweepIx], [nonAdmin]); + console.log("ERROR RESULT:", result.result); expect(result.result).to.not.equal("ok"); // Authority error (6006) + }); +}); diff --git a/tests-real-rpc/tests/discovery.test.ts b/tests-real-rpc/tests/discovery.test.ts new file mode 100644 index 0000000..812a968 --- /dev/null +++ b/tests-real-rpc/tests/discovery.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import { setupTest, processInstruction, type TestContext } from "./common"; +import { findWalletPda, findVaultPda, findAuthorityPda } from "@lazorkit/codama-client/src"; +import crypto from "crypto"; + +describe("Recovery by Credential Hash", () => { + let context: TestContext; + let client: any; + + beforeAll(async () => { + ({ context, client } = await setupTest()); + }, 30000); + + it("Should discover a wallet by its credential hash", async () => { + // 1. Setup random data + const userSeed = new Uint8Array(32); + crypto.getRandomValues(userSeed); + + const credentialIdHash = new Uint8Array(32); + crypto.getRandomValues(credentialIdHash); + + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const [authPda, authBump] = await findAuthorityPda(walletPda, credentialIdHash); + + // Dummy Secp256r1 pubkey (33 bytes) + const authPubkey = new Uint8Array(33).fill(7); + + // 2. Create the wallet + console.log("Creating wallet for discovery test..."); + const createIx = client.createWallet({ + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: authPda, + config: context.configPda, + treasuryShard: context.treasuryShard, + userSeed, + authType: 1, // Secp256r1 + authBump, + authPubkey, + credentialHash: credentialIdHash, + }); + + await processInstruction(context, createIx, [context.payer]); + console.log("Wallet created."); + + // 3. Discover globally + console.log("Searching for wallets with credential hash..."); + const discovered = await client.findAllAuthoritiesByCredentialId(credentialIdHash); + + console.log("Discovered authorities:", discovered); + + // 4. Assertions + expect(discovered.length).toBeGreaterThanOrEqual(1); + const found = discovered.find((d: any) => d.authority === authPda); + expect(found).toBeDefined(); + expect(found?.wallet).toBe(walletPda); + expect(found?.role).toBe(0); // Owner + expect(found?.authorityType).toBe(1); // Secp256r1 + }, 60000); +}); diff --git a/tests-real-rpc/tests/execute.test.ts b/tests-real-rpc/tests/execute.test.ts new file mode 100644 index 0000000..95be4d6 --- /dev/null +++ b/tests-real-rpc/tests/execute.test.ts @@ -0,0 +1,479 @@ + +import { describe, it, expect, beforeAll } from "vitest"; +import { + type Address, + generateKeyPairSigner, + getAddressEncoder, + type TransactionSigner +} from "@solana/kit"; +import { setupTest, processInstruction, tryProcessInstruction, type TestContext, getSystemTransferIx, PROGRAM_ID_STR } from "./common"; +import { findWalletPda, findVaultPda, findAuthorityPda, findSessionPda } from "@lazorkit/codama-client/src"; + +function getRandomSeed() { + return new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); +} + + +describe("Instruction: Execute", () => { + let context: TestContext; + let client: any; + let walletPda: Address; + let vaultPda: Address; + let owner: TransactionSigner; + let ownerAuthPda: Address; + + beforeAll(async () => { + ({ context, client } = await setupTest()); + + const userSeed = getRandomSeed(); + [walletPda] = await findWalletPda(userSeed); + [vaultPda] = await findVaultPda(walletPda); + owner = await generateKeyPairSigner(); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + let authBump; + [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })); + + // Fund vault + await processInstruction(context, getSystemTransferIx(context.payer, vaultPda, 200_000_000n)); + }); + + it("Success: Owner executes a transfer", async () => { + const recipient = (await generateKeyPairSigner()).address; + + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: ownerAuthPda, + vault: vaultPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 1_000_000n) + ], + authorizerSigner: owner, + }); + + await processInstruction(context, executeIx, [owner]); + + const balance = await context.rpc.getBalance(recipient).send(); + expect(balance.value).toBe(1_000_000n); + }); + + it("Success: Spender executes a transfer", async () => { + const spender = await generateKeyPairSigner(); + const spenderBytes = Uint8Array.from(getAddressEncoder().encode(spender.address)); + const [spenderPda] = await findAuthorityPda(walletPda, spenderBytes); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: spenderPda, + authType: 0, + newRole: 2, // Spender + authPubkey: spenderBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + const recipient = (await generateKeyPairSigner()).address; + + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: spenderPda, + vault: vaultPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 1_000_000n) + ], + authorizerSigner: spender, + }); + + await processInstruction(context, executeIx, [spender]); + + const balance = await context.rpc.getBalance(recipient).send(); + expect(balance.value).toBe(1_000_000n); + }); + + it("Success: Session key executes a transfer", async () => { + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + // Create a valid session (expires in the far future) + await processInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: BigInt(2 ** 62), // Far future + authorizerSigner: owner, + }), [owner]); + + const recipient = (await generateKeyPairSigner()).address; + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: sessionPda, + vault: vaultPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 1_000_000n) + ], + authorizerSigner: sessionKey, + }); + + await processInstruction(context, executeIx, [sessionKey]); + + const balance = await context.rpc.getBalance(recipient).send(); + expect(balance.value).toBe(1_000_000n); + }); + + it("Success: Secp256r1 Admin executes a transfer", async () => { + // Create Secp256r1 Admin + const { generateMockSecp256r1Signer, createSecp256r1Instruction, buildSecp256r1AuthPayload, getSecp256r1MessageToSign } = await import("./secp256r1Utils"); + const secpAdmin = await generateMockSecp256r1Signer(); + const [secpAdminPda] = await findAuthorityPda(walletPda, secpAdmin.credentialIdHash); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: secpAdminPda, + authType: 1, // Secp256r1 + newRole: 1, // Admin + authPubkey: secpAdmin.publicKeyBytes, + credentialHash: secpAdmin.credentialIdHash, + authorizerSigner: owner, + }), [owner]); + + // Secp256r1 Admin executes a transfer + const recipient = (await generateKeyPairSigner()).address; + const innerInstructions = [ + getSystemTransferIx(vaultPda, recipient, 2_000_000n) + ]; + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: secpAdminPda, + vault: vaultPda, + innerInstructions, + // Since we're using Secp256r1, we don't pass an authorizerSigner. + // We'll calculate the payload manually. + }); + + // SDK accounts array is typically frozen, we MUST reassign a new array! + executeIx.accounts = [ + ...(executeIx.accounts || []), + { address: "Sysvar1nstructions1111111111111111111111111" as any, role: 0 }, + { address: "SysvarS1otHashes111111111111111111111111111" as any, role: 0 } + ]; + + const argsDataExecute = executeIx.data.subarray(1); // after discriminator + + // Fetch current slot and slotHash from SysvarS1otHashes + const slotHashesAddress = "SysvarS1otHashes111111111111111111111111111" as Address; + const accountInfo = await context.rpc.getAccountInfo(slotHashesAddress, { encoding: 'base64' }).send(); + const rawData = Buffer.from(accountInfo.value!.data[0] as string, 'base64'); + + // SlotHashes layout: + // u64 len + // SlotHash[0]: u64 slot, 32 bytes hash + const currentSlot = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength).getBigUint64(8, true); + const currentSlotHash = new Uint8Array(rawData.buffer, rawData.byteOffset + 16, 32); + + // SYSVAR Indexes + // Execute already has: Payer(0), Wallet(1), Auth(2), Vault(3), InnerAccs... + const sysvarIxIndex = executeIx.accounts.length - 2; + const sysvarSlotIndex = executeIx.accounts.length - 1; + + const { generateAuthenticatorData } = await import("./secp256r1Utils"); + const authenticatorDataRaw = generateAuthenticatorData("example.com"); + + // Build mock WebAuthn metadata payload + const authPayload = buildSecp256r1AuthPayload(sysvarIxIndex, sysvarSlotIndex, authenticatorDataRaw, currentSlot); + + // Compute Accounts Hash (unique to Execute instruction binding) + const systemProgramId = "11111111111111111111111111111111" as Address; // Transfer invokes System + const accountsHashData = new Uint8Array(32 * 3); + accountsHashData.set(getAddressEncoder().encode(systemProgramId), 0); + accountsHashData.set(getAddressEncoder().encode(vaultPda), 32); + accountsHashData.set(getAddressEncoder().encode(recipient), 64); + + const crypto = await import("crypto"); + const accountsHashHasher = crypto.createHash('sha256'); + accountsHashHasher.update(accountsHashData); + const accountsHash = new Uint8Array(accountsHashHasher.digest()); + + // The signed payload for Execute is `compact_instructions` (argsDataExecute) + `accounts_hash` + const signedPayload = new Uint8Array(argsDataExecute.length + 32); + signedPayload.set(argsDataExecute, 0); + signedPayload.set(accountsHash, argsDataExecute.length); + + const currentSlotBytes = new Uint8Array(8); + new DataView(currentSlotBytes.buffer).setBigUint64(0, currentSlot, true); + + const discriminator = new Uint8Array([4]); // Execute is 4 + const msgToSign = getSecp256r1MessageToSign( + discriminator, + authPayload, + signedPayload, + new Uint8Array(getAddressEncoder().encode(context.payer.address)), + new Uint8Array(getAddressEncoder().encode(PROGRAM_ID_STR as import("@solana/kit").Address)), + authenticatorDataRaw, + currentSlotBytes + ); + + const sysvarIx = await createSecp256r1Instruction(secpAdmin, msgToSign); + + // Pack the payload into executeIx.data + const finalExecuteData = new Uint8Array(1 + argsDataExecute.length + authPayload.length); + finalExecuteData.set(discriminator, 0); + finalExecuteData.set(argsDataExecute, 1); + finalExecuteData.set(authPayload, 1 + argsDataExecute.length); + executeIx.data = finalExecuteData; + + const { tryProcessInstructions } = await import("./common"); + const result = await tryProcessInstructions(context, [sysvarIx, executeIx]); + + expect(result.result).toBe("ok"); + + const balance = await context.rpc.getBalance(recipient).send(); + expect(balance.value).toBe(2_000_000n); + }); + + it("Failure: Session expired", async () => { + const sessionKey = await generateKeyPairSigner(); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + // Create a session that is already expired (expires at slot 0) + await processInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: Uint8Array.from(getAddressEncoder().encode(sessionKey.address)), + expiresAt: 0n, + authorizerSigner: owner, + }), [owner]); + + const recipient = (await generateKeyPairSigner()).address; + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: sessionPda, + vault: vaultPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 100n) + ], + authorizerSigner: sessionKey, + }); + + const result = await tryProcessInstruction(context, executeIx, [sessionKey]); + // SessionExpired error code (0xbc1 = 3009) + expect(result.result).toMatch(/3009|0xbc1|simulation failed/i); + }); + + it("Failure: Unauthorized signatory", async () => { + const thief = await generateKeyPairSigner(); + const recipient = (await generateKeyPairSigner()).address; + + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: ownerAuthPda, + vault: vaultPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 100n) + ], + authorizerSigner: thief, + }); + + const result = await tryProcessInstruction(context, executeIx, [thief]); + // Signature mismatch or unauthorized + expect(result.result).toMatch(/signature|unauthorized|simulation failed/i); + }); + + // --- P1: Cross-Wallet Execute Attack --- + + it("Failure: Authority from Wallet A cannot execute on Wallet B's vault", async () => { + // Create Wallet B + const userSeedB = getRandomSeed(); + const [walletPdaB] = await findWalletPda(userSeedB); + const [vaultPdaB] = await findVaultPda(walletPdaB); + const ownerB = await generateKeyPairSigner(); + const ownerBBytes = Uint8Array.from(getAddressEncoder().encode(ownerB.address)); + const [ownerBAuthPda, ownerBBump] = await findAuthorityPda(walletPdaB, ownerBBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPdaB, + vault: vaultPdaB, + authority: ownerBAuthPda, + userSeed: userSeedB, + authType: 0, + authBump: ownerBBump, + authPubkey: ownerBBytes, + credentialHash: new Uint8Array(32), + })); + + // Fund Wallet B's vault + await processInstruction(context, getSystemTransferIx(context.payer, vaultPdaB, 100_000_000n)); + + const recipient = (await generateKeyPairSigner()).address; + + // Wallet A's owner tries to execute on Wallet B + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPdaB, // Target: Wallet B + authority: ownerAuthPda, // Using Wallet A's owner auth + vault: vaultPdaB, + innerInstructions: [ + getSystemTransferIx(vaultPdaB, recipient, 1_000_000n) + ], + authorizerSigner: owner, // Wallet A's owner signer + }); + + const result = await tryProcessInstruction(context, executeIx, [owner]); + expect(result.result).toMatch(/simulation failed|InvalidAccountData/i); + }); + + // --- P1: Self-Reentrancy Protection (Issue #10) --- + + it("Failure: Execute rejects self-reentrancy (calling back into LazorKit)", async () => { + const PROGRAM_ID = "2m47smrvCRpuqAyX2dLqPxpAC1658n1BAQga1wRCsQiT" as import("@solana/kit").Address; + + // Build an inner instruction that calls back into the LazorKit program + const reentrancyIx = { + programAddress: PROGRAM_ID, + accounts: [ + { address: context.payer.address, role: 3 }, + { address: walletPda, role: 0 }, + ], + data: new Uint8Array([0]) // CreateWallet discriminator (doesn't matter, should be rejected before parsing) + }; + + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: ownerAuthPda, + vault: vaultPda, + innerInstructions: [reentrancyIx], + authorizerSigner: owner, + }); + + const result = await tryProcessInstruction(context, executeIx, [owner]); + // SelfReentrancyNotAllowed = 3013 = 0xbc5 + expect(result.result).toMatch(/3013|0xbc5|simulation failed/i); + }); + + // --- P3: Execute Instruction Gaps --- + + it("Success: Execute batch — multiple transfers in one execution", async () => { + const recipient1 = (await generateKeyPairSigner()).address; + const recipient2 = (await generateKeyPairSigner()).address; + const recipient3 = (await generateKeyPairSigner()).address; + + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: ownerAuthPda, + vault: vaultPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient1, 1_000_000n), + getSystemTransferIx(vaultPda, recipient2, 2_000_000n), + getSystemTransferIx(vaultPda, recipient3, 3_000_000n), + ], + authorizerSigner: owner, + }); + + await processInstruction(context, executeIx, [owner]); + + const bal1 = await context.rpc.getBalance(recipient1).send(); + const bal2 = await context.rpc.getBalance(recipient2).send(); + const bal3 = await context.rpc.getBalance(recipient3).send(); + + expect(bal1.value).toBe(1_000_000n); + expect(bal2.value).toBe(2_000_000n); + expect(bal3.value).toBe(3_000_000n); + }); + + it("Success: Execute with empty inner instructions", async () => { + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: ownerAuthPda, + vault: vaultPda, + innerInstructions: [], // Empty batch + authorizerSigner: owner, + }); + + // The transaction should succeed but do nothing + await processInstruction(context, executeIx, [owner]); + }); + + it("Failure: Execute with wrong vault PDA", async () => { + // Generate a random keypair to use as a fake vault + const fakeVault = await generateKeyPairSigner(); + const recipient = (await generateKeyPairSigner()).address; + + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: ownerAuthPda, + vault: fakeVault.address, // Use fake vault + innerInstructions: [ + getSystemTransferIx(fakeVault.address, recipient, 1_000_000n) + ], + authorizerSigner: owner, + }); + + const result = await tryProcessInstruction(context, executeIx, [owner, fakeVault]); + // Vault PDA validation in execute.rs should throw InvalidSeeds + expect(result.result).toMatch(/simulation failed|InvalidSeeds/i); + }); +}); diff --git a/tests-real-rpc/tests/full_flow.test.ts b/tests-real-rpc/tests/full_flow.test.ts new file mode 100644 index 0000000..c8bfbeb --- /dev/null +++ b/tests-real-rpc/tests/full_flow.test.ts @@ -0,0 +1,100 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import { + Address, + generateKeyPairSigner, + createTransactionMessage, + setTransactionMessageFeePayerSigner, + setTransactionMessageLifetimeUsingBlockhash, + appendTransactionMessageInstructions, + signTransactionMessageWithSigners, + getSignatureFromTransaction, + getBase64EncodedWireTransaction, + address, + sendAndConfirmTransactionFactory, +} from "@solana/kit"; +import { setupTest, processInstruction, type TestContext } from "./common"; +import { findWalletPda, findVaultPda, findAuthorityPda } from "@lazorkit/codama-client/src"; +import crypto from "crypto"; + +describe("Real RPC Integration Suite", () => { + let context: TestContext; + let client: any; + + // Test data + let userSeed: Uint8Array; + let walletPda: Address; + let vaultPda: Address; + let authPda: Address; + let p256Keypair: crypto.webcrypto.CryptoKeyPair; + let credentialIdHash: Uint8Array; + + beforeAll(async () => { + ({ context, client } = await setupTest()); + + // Initialize Wallet Config Variables + userSeed = new Uint8Array(32); + crypto.getRandomValues(userSeed); + + walletPda = (await findWalletPda(userSeed))[0]; + vaultPda = (await findVaultPda(walletPda))[0]; + + // 1. Generate a valid P256 Keypair + p256Keypair = await crypto.subtle.generateKey( + { name: "ECDSA", namedCurve: "P-256" }, + true, + ["sign", "verify"] + ); + + const rpId = "lazorkit.valid"; + const rpIdHashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(rpId)); + credentialIdHash = new Uint8Array(rpIdHashBuffer as ArrayBuffer); + + authPda = (await findAuthorityPda(walletPda, credentialIdHash))[0]; + + }, 30000); + + // 1. Process Transaction Helper + const processTransaction = async (instruction: any, signers: any[]) => { + return await processInstruction(context, instruction, signers); + }; + + it("1. Create Wallet with Real RPC", async () => { + // Prepare pubkey + const spki = await crypto.subtle.exportKey("spki", p256Keypair.publicKey); + let rawPubkeyInfo = new Uint8Array(spki as ArrayBuffer); + let rawP256Pubkey = rawPubkeyInfo.slice(-64); + let p256PubkeyCompressed = new Uint8Array(33); + p256PubkeyCompressed[0] = (rawP256Pubkey[63] % 2 === 0) ? 0x02 : 0x03; + p256PubkeyCompressed.set(rawP256Pubkey.slice(0, 32), 1); + + const authBump = (await findAuthorityPda(walletPda, credentialIdHash))[1]; + + const ix = client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: authPda, + userSeed, + authType: 1, // Secp256r1 + authBump, + authPubkey: p256PubkeyCompressed, + credentialHash: credentialIdHash, + }); + + // Send logic + const txResult = await processTransaction(ix, [context.payer]); + console.log(`✓ Wallet Created successfully. Signature: ${txResult}`); + + expect(txResult).toBeDefined(); + }, 30000); + + it("2. Wallet Account Data Inspection", async () => { + const res = await (context.rpc as any).getAccountInfo(walletPda).send(); + expect(res.value).toBeDefined(); + + const dataArr = new Uint8Array((res.value as any).data[0]); // Base64 or bytes + // Basic check on size. Should be > 0. + }, 10000); +}); diff --git a/tests-real-rpc/tests/integrity.test.ts b/tests-real-rpc/tests/integrity.test.ts new file mode 100644 index 0000000..0062a96 --- /dev/null +++ b/tests-real-rpc/tests/integrity.test.ts @@ -0,0 +1,203 @@ + +import { describe, it, expect, beforeAll } from "vitest"; +import { + type Address, + generateKeyPairSigner, + getAddressEncoder, +} from "@solana/kit"; +import { setupTest, processInstruction, type TestContext } from "./common"; +import { findWalletPda, findVaultPda, findAuthorityPda } from "@lazorkit/codama-client/src"; +import * as crypto from "crypto"; + +function getRandomSeed() { + return new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); +} + +/** + * AuthorityAccountHeader layout (48 bytes): + * discriminator(1) + authority_type(1) + role(1) + bump(1) + + * version(1) + _padding(3) + counter(8) + wallet(32) = 48 + * + * After header: + * Ed25519: [pubkey(32)] + * Secp256r1: [credential_id_hash(32)] [pubkey(33)] + * + * Both authority types start variable data at offset 48. + */ +const HEADER_SIZE = 48; +const DATA_OFFSET = HEADER_SIZE; // offset 48 for both types +const SECP256R1_PUBKEY_OFFSET = DATA_OFFSET + 32; // offset 80 + +describe("Contract Data Integrity", () => { + let context: TestContext; + let client: any; + + beforeAll(async () => { + ({ context, client } = await setupTest()); + }); + + async function getRawAccountData(address: Address): Promise { + const { value: acc } = await context.rpc.getAccountInfo(address, { encoding: 'base64' }).send(); + return Buffer.from(acc!.data[0], 'base64'); + } + + it("Ed25519: pubkey stored at correct offset", async () => { + const userSeed = getRandomSeed(); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const owner = await generateKeyPairSigner(); + const ownerPubkeyBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [authPda, authBump] = await findAuthorityPda(walletPda, ownerPubkeyBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: authPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerPubkeyBytes, + credentialHash: new Uint8Array(32), + })); + + const data = await getRawAccountData(authPda); + + // Header checks + expect(data[0]).toBe(2); // discriminator = Authority + expect(data[1]).toBe(0); // authority_type = Ed25519 + expect(data[2]).toBe(0); // role = Owner + + // Wallet pubkey in header (at offset 16 = 1+1+1+1+1+3+8) + const storedWallet = data.subarray(16, 48); + expect(Uint8Array.from(storedWallet)).toEqual(Uint8Array.from(getAddressEncoder().encode(walletPda))); + + // Ed25519 pubkey at DATA_OFFSET + const storedPubkey = data.subarray(DATA_OFFSET, DATA_OFFSET + 32); + expect(Uint8Array.from(storedPubkey)).toEqual(ownerPubkeyBytes); + }); + + it("Secp256r1: credential_id_hash + pubkey stored at correct offsets", async () => { + const userSeed = getRandomSeed(); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + + const credentialIdHash = new Uint8Array(crypto.randomBytes(32)); + const p256Pubkey = new Uint8Array(33); // compressed P-256 key + p256Pubkey[0] = 0x02; // valid prefix + crypto.randomBytes(32).copy(p256Pubkey, 1); + + const [authPda, authBump] = await findAuthorityPda(walletPda, credentialIdHash); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: authPda, + userSeed, + authType: 1, // Secp256r1 + authBump, + authPubkey: p256Pubkey, + credentialHash: credentialIdHash, + })); + + const data = await getRawAccountData(authPda); + + // Header checks + expect(data[0]).toBe(2); // discriminator = Authority + expect(data[1]).toBe(1); // authority_type = Secp256r1 + expect(data[2]).toBe(0); // role = Owner + + // credential_id_hash at DATA_OFFSET + const storedCredHash = data.subarray(DATA_OFFSET, DATA_OFFSET + 32); + expect(Uint8Array.from(storedCredHash)).toEqual(Uint8Array.from(credentialIdHash)); + + // pubkey at SECP256R1_PUBKEY_OFFSET (33 bytes compressed) + const storedPubkey = data.subarray(SECP256R1_PUBKEY_OFFSET, SECP256R1_PUBKEY_OFFSET + 33); + expect(Uint8Array.from(storedPubkey)).toEqual(Uint8Array.from(p256Pubkey)); + }); + + it("Multiple Secp256r1 authorities with different credential_id_hash", async () => { + const userSeed = getRandomSeed(); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + + // Create wallet with Ed25519 owner first + const owner = await generateKeyPairSigner(); + const ownerPubkeyBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [ownerPda, ownerBump] = await findAuthorityPda(walletPda, ownerPubkeyBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerPda, + userSeed, + authType: 0, + authBump: ownerBump, + authPubkey: ownerPubkeyBytes, + credentialHash: new Uint8Array(32), + })); + + // Add Passkey 1 + const credHash1 = new Uint8Array(crypto.randomBytes(32)); + const pubkey1 = new Uint8Array(33); pubkey1[0] = 0x02; crypto.randomBytes(32).copy(pubkey1, 1); + const [authPda1] = await findAuthorityPda(walletPda, credHash1); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerPda, + newAuthority: authPda1, + authType: 1, + newRole: 1, // Admin + authPubkey: pubkey1, + credentialHash: credHash1, + authorizerSigner: owner, + }), [owner]); + + // Add Passkey 2 (same domain, different credential) + const credHash2 = new Uint8Array(crypto.randomBytes(32)); + const pubkey2 = new Uint8Array(33); pubkey2[0] = 0x03; crypto.randomBytes(32).copy(pubkey2, 1); + const [authPda2] = await findAuthorityPda(walletPda, credHash2); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerPda, + newAuthority: authPda2, + authType: 1, + newRole: 2, // Spender + authPubkey: pubkey2, + credentialHash: credHash2, + authorizerSigner: owner, + }), [owner]); + + // PDAs must be unique + expect(authPda1).not.toEqual(authPda2); + + // Verify Passkey 1 data + const data1 = await getRawAccountData(authPda1); + expect(data1[1]).toBe(1); // Secp256r1 + expect(data1[2]).toBe(1); // Admin + expect(Uint8Array.from(data1.subarray(DATA_OFFSET, DATA_OFFSET + 32))).toEqual(Uint8Array.from(credHash1)); + expect(Uint8Array.from(data1.subarray(SECP256R1_PUBKEY_OFFSET, SECP256R1_PUBKEY_OFFSET + 33))).toEqual(Uint8Array.from(pubkey1)); + + // Verify Passkey 2 data + const data2 = await getRawAccountData(authPda2); + expect(data2[1]).toBe(1); // Secp256r1 + expect(data2[2]).toBe(2); // Spender + expect(Uint8Array.from(data2.subarray(DATA_OFFSET, DATA_OFFSET + 32))).toEqual(Uint8Array.from(credHash2)); + expect(Uint8Array.from(data2.subarray(SECP256R1_PUBKEY_OFFSET, SECP256R1_PUBKEY_OFFSET + 33))).toEqual(Uint8Array.from(pubkey2)); + }); +}); diff --git a/tests-real-rpc/tests/secp256r1Utils.ts b/tests-real-rpc/tests/secp256r1Utils.ts new file mode 100644 index 0000000..2024cc3 --- /dev/null +++ b/tests-real-rpc/tests/secp256r1Utils.ts @@ -0,0 +1,236 @@ +import { type Address } from '@solana/kit'; +import * as crypto from 'crypto'; +// @ts-ignore +import ECDSA from 'ecdsa-secp256r1'; + +export const SECP256R1_PROGRAM_ID = "Secp256r1SigVerify1111111111111111111111111" as Address; + +export interface MockSecp256r1Signer { + privateKey: any; // ecdsa-secp256r1 key object + publicKeyBytes: Uint8Array; // 33 byte compressed + credentialIdHash: Uint8Array; // 32 byte hash +} + +export async function generateMockSecp256r1Signer(credentialIdHash?: Uint8Array): Promise { + const privateKey = await ECDSA.generateKey(); + const pubKeyBase64 = privateKey.toCompressedPublicKey(); + const compressedPubKey = new Uint8Array(Buffer.from(pubKeyBase64, 'base64')); + + const credHash = credentialIdHash || new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); + + return { + privateKey, + publicKeyBytes: compressedPubKey, + credentialIdHash: credHash, + }; +} + +export async function signWithSecp256r1(signer: MockSecp256r1Signer, message: Uint8Array): Promise { + const signatureBase64 = await signer.privateKey.sign(Buffer.from(message)); + const rawSig = new Uint8Array(Buffer.from(signatureBase64, 'base64')); + + // Solana secp256r1 precompile STRICTLY requires low-S signatures. + // SECP256R1 curve order n + const SECP256R1_N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n; + const HALF_N = SECP256R1_N / 2n; + + let rBuffer: Uint8Array; + let sBufferLocal: Uint8Array; + + // Check if signature is DER encoded (starts with 0x30) + if (rawSig[0] === 0x30) { + // DER decode: 30 02 02 + let offset = 2; // skip 30 + if (rawSig[offset] !== 0x02) throw new Error("Invalid DER: expected 0x02 for r"); + offset++; + const rLen = rawSig[offset]; offset++; + const rRaw = rawSig.slice(offset, offset + rLen); offset += rLen; + if (rawSig[offset] !== 0x02) throw new Error("Invalid DER: expected 0x02 for s"); + offset++; + const sLen = rawSig[offset]; offset++; + const sRaw = rawSig.slice(offset, offset + sLen); + + // Pad/trim r and s to exactly 32 bytes + rBuffer = new Uint8Array(32); + if (rRaw.length > 32) { + rBuffer.set(rRaw.slice(rRaw.length - 32)); + } else { + rBuffer.set(rRaw, 32 - rRaw.length); + } + sBufferLocal = new Uint8Array(32); + if (sRaw.length > 32) { + sBufferLocal.set(sRaw.slice(sRaw.length - 32)); + } else { + sBufferLocal.set(sRaw, 32 - sRaw.length); + } + } else if (rawSig.length >= 64) { + // Raw r||s format (64 bytes) + rBuffer = rawSig.slice(0, 32); + sBufferLocal = rawSig.slice(32, 64); + } else { + throw new Error(`Unexpected signature format: length=${rawSig.length}, first byte=0x${rawSig[0]?.toString(16)}`); + } + + // convert s to bigint + let sBigInt = 0n; + for (let i = 0; i < 32; i++) { + sBigInt = (sBigInt << 8n) + BigInt(sBufferLocal[i]); + } + + if (sBigInt > HALF_N) { + // Enforce low S: s = n - s + sBigInt = SECP256R1_N - sBigInt; + + // Write low S back to sBufferLocal + for (let i = 31; i >= 0; i--) { + sBufferLocal[i] = Number(sBigInt & 0xffn); + sBigInt >>= 8n; + } + } + + // Return 64-byte raw r||s + const result = new Uint8Array(64); + result.set(rBuffer, 0); + result.set(sBufferLocal, 32); + return result; +} + +function bytesOf(data: any): Uint8Array { + if (data instanceof Uint8Array) { + return data; + } else if (Array.isArray(data)) { + return new Uint8Array(data); + } else { + // Convert object to buffer using DataView for consistent byte ordering + const buffer = new ArrayBuffer(Object.values(data).length * 2); + const view = new DataView(buffer); + Object.values(data).forEach((value, index) => { + view.setUint16(index * 2, value as number, true); + }); + return new Uint8Array(buffer); + } +} + +export async function createSecp256r1Instruction(signer: MockSecp256r1Signer, message: Uint8Array) { + const signature = await signWithSecp256r1(signer, message); + + const SIGNATURE_OFFSETS_SERIALIZED_SIZE = 14; + const SIGNATURE_OFFSETS_START = 2; // [num_sigs(1), padding(1)] + const DATA_START = SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START; // 16 + const SIGNATURE_SERIALIZED_SIZE = 64; + const COMPRESSED_PUBKEY_SERIALIZED_SIZE = 33; + + const signatureOffset = DATA_START; + const publicKeyOffset = signatureOffset + SIGNATURE_SERIALIZED_SIZE; // 80 + const messageDataOffset = publicKeyOffset + COMPRESSED_PUBKEY_SERIALIZED_SIZE + 1; // 114 (padding included) + + const totalSize = messageDataOffset + message.length; + const instructionData = new Uint8Array(totalSize); + + // Number of signatures + padding + instructionData[0] = 1; + instructionData[1] = 0; + + const offsetsView = new DataView(instructionData.buffer, instructionData.byteOffset + SIGNATURE_OFFSETS_START, 14); + offsetsView.setUint16(0, signatureOffset, true); + offsetsView.setUint16(2, 0xffff, true); + offsetsView.setUint16(4, publicKeyOffset, true); + offsetsView.setUint16(6, 0xffff, true); + offsetsView.setUint16(8, messageDataOffset, true); + offsetsView.setUint16(10, message.length, true); + offsetsView.setUint16(12, 0xffff, true); + + instructionData.set(signature, signatureOffset); + instructionData.set(signer.publicKeyBytes, publicKeyOffset); + instructionData.set(message, messageDataOffset); + + return { + programAddress: SECP256R1_PROGRAM_ID, + accounts: [], + data: instructionData, + }; +} + +export function generateAuthenticatorData(rpId: string = "example.com"): Uint8Array { + const rpIdHash = crypto.createHash('sha256').update(rpId).digest(); + const authenticatorData = new Uint8Array(37); + authenticatorData.set(rpIdHash, 0); // 32 bytes rpIdHash + authenticatorData[32] = 0x01; // User Present flag + // Counter is the last 4 bytes (0) + return authenticatorData; +} + +function bytesToBase64UrlNoPad(bytes: Uint8Array): string { + const base64 = Buffer.from(bytes).toString("base64"); + return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); +} + +export function buildSecp256r1AuthPayload( + sysvarInstructionsIndex: number, + sysvarSlothashesIndex: number, + authenticatorDataRaw: Uint8Array, + slot: bigint = 0n +): Uint8Array { + const rpIdStr = "example.com"; + const rpIdBytes = new TextEncoder().encode(rpIdStr); + + // 8 (slot) + 1 (sysvar_ix) + 1 (sysvar_slot) + 1 (flags) + 1 (rp_id_len) + N (rp_id) + 37 (authenticator_data) + const payloadLen = 12 + rpIdBytes.length + authenticatorDataRaw.length; + const payloadFull = new Uint8Array(payloadLen); + const view = new DataView(payloadFull.buffer, payloadFull.byteOffset, payloadFull.byteLength); + + view.setBigUint64(0, slot, true); + + payloadFull[8] = sysvarInstructionsIndex; + payloadFull[9] = sysvarSlothashesIndex; + + // 0x10 = webauthn.get (0x10) | https:// (0x00) + payloadFull[10] = 0x10; + + payloadFull[11] = rpIdBytes.length; + payloadFull.set(rpIdBytes, 12); + + const authDataOffset = 12 + rpIdBytes.length; + payloadFull.set(authenticatorDataRaw, authDataOffset); + + return payloadFull; +} + +export function getSecp256r1MessageToSign( + discriminator: Uint8Array, + authPayload: Uint8Array, + signedPayload: Uint8Array, + payer: Uint8Array, + programId: Uint8Array, + authenticatorDataRaw: Uint8Array, + slotBytes: Uint8Array // 8 bytes Little Endian slot +): Uint8Array { + const hasherHash = crypto.createHash("sha256"); + hasherHash.update(discriminator); + hasherHash.update(authPayload); + hasherHash.update(signedPayload); + hasherHash.update(slotBytes); + hasherHash.update(payer); + hasherHash.update(programId); + const challengeHash = hasherHash.digest(); + + const clientDataJsonRaw = Buffer.from( + new Uint8Array( + new TextEncoder().encode( + JSON.stringify({ + type: "webauthn.get", + challenge: bytesToBase64UrlNoPad(new Uint8Array(challengeHash)), + origin: "https://example.com", + crossOrigin: false + }) + ).buffer + ) + ); + + const message = Buffer.concat([ + authenticatorDataRaw, + Buffer.from(crypto.createHash("sha256").update(clientDataJsonRaw).digest()), + ]); + + return new Uint8Array(message); +} diff --git a/tests-real-rpc/tests/security_checklist.test.ts b/tests-real-rpc/tests/security_checklist.test.ts new file mode 100644 index 0000000..c8b7c0e --- /dev/null +++ b/tests-real-rpc/tests/security_checklist.test.ts @@ -0,0 +1,119 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import { + type Address, + generateKeyPairSigner, + getAddressEncoder, + type TransactionSigner, +} from "@solana/kit"; +import { + setupTest, + processInstruction, + tryProcessInstruction, + type TestContext, +} from "./common"; +import { findWalletPda, findVaultPda, findAuthorityPda, findSessionPda } from "@lazorkit/codama-client/src"; + +function getRandomSeed() { + return new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); +} + +describe("Security Checklist Gaps", () => { + let context: TestContext; + let client: any; + let walletPda: Address; + let vaultPda: Address; + let owner: TransactionSigner; + let ownerAuthPda: Address; + + beforeAll(async () => { + ({ context, client } = await setupTest()); + + const userSeed = getRandomSeed(); + [walletPda] = await findWalletPda(userSeed); + [vaultPda] = await findVaultPda(walletPda); + owner = await generateKeyPairSigner(); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + let authBump; + [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstruction( + context, + client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + }), + ); + }, 180_000); + + it("CreateSession rejects System Program spoofing", async () => { + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + const ix = client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: 999999999n, + authorizerSigner: owner, + }); + + // SystemProgram is at index 4 in LazorClient.createSession() + const spoofedSystemProgram = (await generateKeyPairSigner()).address; + ix.accounts = (ix.accounts || []).map((a: any, i: number) => + i === 4 ? { ...a, address: spoofedSystemProgram } : a, + ); + + const result = await tryProcessInstruction(context, ix, [owner]); + expect(result.result).toMatch(/IncorrectProgramId|simulation failed/i); + }); + + it("CloseSession: protocol admin cannot close an active session without wallet auth", async () => { + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + // Create a session far in the future => active + await processInstruction( + context, + client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: BigInt(2 ** 62), + authorizerSigner: owner, + }), + [owner], + ); + + // Call CloseSession with payer == config.admin (setupTest uses payer as admin), + // but do NOT provide wallet authorizer accounts. Should be rejected unless expired. + const closeIx = client.closeSession({ + payer: context.payer, + wallet: walletPda, + session: sessionPda, + config: context.configPda, + }); + + const result = await tryProcessInstruction(context, closeIx, [context.payer]); + expect(result.result).toMatch(/PermissionDenied|0xbba|3002|simulation failed/i); + }); +}); + diff --git a/tests-real-rpc/tests/session.test.ts b/tests-real-rpc/tests/session.test.ts new file mode 100644 index 0000000..03af787 --- /dev/null +++ b/tests-real-rpc/tests/session.test.ts @@ -0,0 +1,380 @@ + +import { describe, it, expect, beforeAll } from "vitest"; +import { + type Address, + generateKeyPairSigner, + getAddressEncoder, + type TransactionSigner +} from "@solana/kit"; +import { setupTest, processInstruction, tryProcessInstruction, type TestContext, getSystemTransferIx, PROGRAM_ID_STR } from "./common"; +import { findWalletPda, findVaultPda, findAuthorityPda, findSessionPda } from "@lazorkit/codama-client/src"; + +function getRandomSeed() { + return new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); +} + +describe("Instruction: CreateSession", () => { + let context: TestContext; + let client: any; + let walletPda: Address; + let owner: TransactionSigner; + let ownerAuthPda: Address; + + beforeAll(async () => { + ({ context, client } = await setupTest()); + + const userSeed = getRandomSeed(); + [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + owner = await generateKeyPairSigner(); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + let authBump; + [ownerAuthPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })); + + // Fund vault + await processInstruction(context, getSystemTransferIx(context.payer, vaultPda, 500_000_000n)); + }); + + it("Success: Owner creates a session key", async () => { + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + await processInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: 999999999n, + authorizerSigner: owner, + }), [owner]); + + const sessionAcc = await client.getSession(sessionPda); + expect(sessionAcc.discriminator).toBe(3); // Session + expect(sessionAcc.sessionKey).toEqual(sessionKey.address); + }); + + it("Success: Execution using session key", async () => { + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + const [vaultPda] = await findVaultPda(walletPda); + + await processInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: BigInt(2 ** 62), + authorizerSigner: owner, + }), [owner]); + + const recipient = (await generateKeyPairSigner()).address; + const executeIx = client.buildExecute({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + authority: sessionPda, + vault: vaultPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 1_000_000n) + ], + authorizerSigner: sessionKey, + }); + + await processInstruction(context, executeIx, [sessionKey]); + const balance = await context.rpc.getBalance(recipient).send(); + expect(balance.value).toBe(1_000_000n); + }); + + // --- P2: Session Permission Boundaries --- + + it("Failure: Spender cannot create session", async () => { + const spender = await generateKeyPairSigner(); + const spenderBytes = Uint8Array.from(getAddressEncoder().encode(spender.address)); + const [spenderPda] = await findAuthorityPda(walletPda, spenderBytes); + + // Owner adds a Spender + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: spenderPda, + authType: 0, + newRole: 2, + authPubkey: spenderBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + // Spender tries to create session → should fail + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + const result = await tryProcessInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: spenderPda, // Spender, not Admin/Owner + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: BigInt(2 ** 62), + authorizerSigner: spender, + }), [spender]); + + expect(result.result).toMatch(/0xbba|3002|simulation failed/i); // PermissionDenied + }); + + it("Failure: Session PDA cannot create another session", async () => { + // Create a valid session first + const sessionKey1 = await generateKeyPairSigner(); + const sessionKey1Bytes = Uint8Array.from(getAddressEncoder().encode(sessionKey1.address)); + const [sessionPda1] = await findSessionPda(walletPda, sessionKey1.address); + + await processInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda1, + sessionKey: sessionKey1Bytes, + expiresAt: BigInt(2 ** 62), + authorizerSigner: owner, + }), [owner]); + + // Now try to use the session PDA as adminAuthority to create another session + const sessionKey2 = await generateKeyPairSigner(); + const sessionKey2Bytes = Uint8Array.from(getAddressEncoder().encode(sessionKey2.address)); + const [sessionPda2] = await findSessionPda(walletPda, sessionKey2.address); + + const result = await tryProcessInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: sessionPda1, // Session PDA, not Authority + session: sessionPda2, + sessionKey: sessionKey2Bytes, + expiresAt: BigInt(2 ** 62), + authorizerSigner: sessionKey1, + }), [sessionKey1]); + + expect(result.result).toMatch(/simulation failed|InvalidAccountData/i); + }); + + // --- P4: Session Key Cannot Do Admin Actions --- + + it("Failure: Session key cannot add authority", async () => { + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + await processInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: BigInt(2 ** 62), + authorizerSigner: owner, + }), [owner]); + + // Try to use session PDA as adminAuthority to add authority + const newUser = await generateKeyPairSigner(); + const newUserBytes = Uint8Array.from(getAddressEncoder().encode(newUser.address)); + const [newUserPda] = await findAuthorityPda(walletPda, newUserBytes); + + const result = await tryProcessInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: sessionPda, // Session PDA, not Authority + newAuthority: newUserPda, + authType: 0, + newRole: 2, + authPubkey: newUserBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: sessionKey, + }), [sessionKey]); + + expect(result.result).toMatch(/simulation failed|InvalidAccountData/i); + }); + + it("Failure: Session key cannot remove authority", async () => { + // Create a spender first + const spender = await generateKeyPairSigner(); + const spenderBytes = Uint8Array.from(getAddressEncoder().encode(spender.address)); + const [spenderPda] = await findAuthorityPda(walletPda, spenderBytes); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: spenderPda, + authType: 0, + newRole: 2, + authPubkey: spenderBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + // Create a session + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + await processInstruction(context, client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt: BigInt(2 ** 62), + authorizerSigner: owner, + }), [owner]); + + // Try to use session PDA as adminAuthority to remove spender + const result = await tryProcessInstruction(context, client.removeAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: sessionPda, + targetAuthority: spenderPda, + refundDestination: context.payer.address, + authorizerSigner: sessionKey, + }), [sessionKey]); + + expect(result.result).toMatch(/simulation failed|InvalidAccountData/i); + }); + + it("Success: Secp256r1 Admin creates a session", async () => { + // Create Secp256r1 Admin + const { generateMockSecp256r1Signer, createSecp256r1Instruction, buildSecp256r1AuthPayload, getSecp256r1MessageToSign, generateAuthenticatorData } = await import("./secp256r1Utils"); + const crypto = await import("crypto"); + const secpAdmin = await generateMockSecp256r1Signer(); + const [secpAdminPda] = await findAuthorityPda(walletPda, secpAdmin.credentialIdHash); + + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: secpAdminPda, + authType: 1, // Secp256r1 + newRole: 1, // Admin + authPubkey: secpAdmin.publicKeyBytes, + credentialHash: secpAdmin.credentialIdHash, + authorizerSigner: owner, + }), [owner]); + + const sessionKey = await generateKeyPairSigner(); + const sessionKeyBytes = Uint8Array.from(getAddressEncoder().encode(sessionKey.address)); + const [sessionPda] = await findSessionPda(walletPda, sessionKey.address); + + const expiresAt = 999999999n; + + const createSessionIx = client.createSession({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: secpAdminPda, + session: sessionPda, + sessionKey: sessionKeyBytes, + expiresAt, + // Since we're using Secp256r1, we don't pass an authorizerSigner. + }); + + // Append sysvars AFTER all existing accounts (config/treasury are consumed by iterator) + createSessionIx.accounts = [ + ...(createSessionIx.accounts || []), + { address: "Sysvar1nstructions1111111111111111111111111" as any, role: 0 }, + { address: "SysvarS1otHashes111111111111111111111111111" as any, role: 0 }, + ]; + + // Fetch current slot and slotHash from SysvarS1otHashes + const slotHashesAddress = "SysvarS1otHashes111111111111111111111111111" as Address; + const accountInfo = await context.rpc.getAccountInfo(slotHashesAddress, { encoding: 'base64' }).send(); + const rawData = Buffer.from(accountInfo.value!.data[0] as string, 'base64'); + const currentSlot = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength).getBigUint64(8, true); + + const sysvarIxIndex = createSessionIx.accounts.length - 2; // Sysvar1nstructions position + const sysvarSlotIndex = createSessionIx.accounts.length - 1; // SysvarSlotHashes position + + const authenticatorDataRaw = generateAuthenticatorData("example.com"); + const authPayload = buildSecp256r1AuthPayload(sysvarIxIndex, sysvarSlotIndex, authenticatorDataRaw, currentSlot); + + // The signed payload for CreateSession is `session_key` + `expires_at` + `payer` + const signedPayload = new Uint8Array(32 + 8 + 32); + signedPayload.set(sessionKeyBytes, 0); + new DataView(signedPayload.buffer, signedPayload.byteOffset + 32).setBigUint64(0, expiresAt, true); + signedPayload.set(new Uint8Array(getAddressEncoder().encode(context.payer.address)), 40); + + const currentSlotBytes = new Uint8Array(8); + new DataView(currentSlotBytes.buffer).setBigUint64(0, currentSlot, true); + + const discriminator = new Uint8Array([5]); // CreateSession is 5 + const msgToSign = getSecp256r1MessageToSign( + discriminator, + authPayload, + signedPayload, + new Uint8Array(getAddressEncoder().encode(context.payer.address)), + new Uint8Array(getAddressEncoder().encode(PROGRAM_ID_STR as import("@solana/kit").Address)), + authenticatorDataRaw, + currentSlotBytes + ); + + const sysvarIx = await createSecp256r1Instruction(secpAdmin, msgToSign); + + // Pack the payload into createSessionIx.data + const originalData = createSessionIx.data; + const finalCreateSessionData = new Uint8Array(originalData.length + authPayload.length); + finalCreateSessionData.set(originalData, 0); + finalCreateSessionData.set(authPayload, originalData.length); + createSessionIx.data = finalCreateSessionData; + + const { tryProcessInstructions } = await import("./common"); + const result = await tryProcessInstructions(context, [sysvarIx, createSessionIx]); + + expect(result.result).toBe("ok"); + + const sessionAcc = await client.getSession(sessionPda); + expect(sessionAcc.discriminator).toBe(3); // Session + expect(sessionAcc.sessionKey).toEqual(sessionKey.address); + }); +}); diff --git a/tests-real-rpc/tests/utils/rpcSetup.ts b/tests-real-rpc/tests/utils/rpcSetup.ts new file mode 100644 index 0000000..f8cc632 --- /dev/null +++ b/tests-real-rpc/tests/utils/rpcSetup.ts @@ -0,0 +1,15 @@ +import { createSolanaRpc, createSolanaRpcSubscriptions } from "@solana/kit"; +import { LazorClient } from "../@lazorkit/codama-client/src"; +import dotenv from "dotenv"; + +dotenv.config(); + +const RPC_URL = process.env.RPC_URL || "http://127.0.0.1:8899"; +const WS_URL = process.env.WS_URL || "ws://127.0.0.1:8900"; + +export const rpc = createSolanaRpc(RPC_URL); +export const rpcSubscriptions = createSolanaRpcSubscriptions(WS_URL); + +export const client = new LazorClient(rpc as any); + +export { RPC_URL }; diff --git a/tests-real-rpc/tests/wallet.test.ts b/tests-real-rpc/tests/wallet.test.ts new file mode 100644 index 0000000..444818a --- /dev/null +++ b/tests-real-rpc/tests/wallet.test.ts @@ -0,0 +1,523 @@ + +import { describe, it, expect, beforeAll } from "vitest"; +import { + getAddressEncoder, + generateKeyPairSigner, + type Address, +} from "@solana/kit"; +import { setupTest, processInstruction, tryProcessInstruction, type TestContext, getSystemTransferIx, PROGRAM_ID_STR } from "./common"; +import { findWalletPda, findVaultPda, findAuthorityPda } from "@lazorkit/codama-client/src"; +import { LazorClient } from "@lazorkit/codama-client/src"; + +function getRandomSeed() { + return new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); +} + +describe("Wallet Lifecycle (Create, Discovery, Ownership)", () => { + let context: TestContext; + let client: LazorClient; + + beforeAll(async () => { + ({ context, client } = await setupTest()); + }); + + // --- Create Wallet --- + + it("Success: Create wallet with Ed25519 owner", async () => { + const userSeed = getRandomSeed(); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + + const owner = await generateKeyPairSigner(); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [authPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: authPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })); + + const authAcc = await client.getAuthority(authPda); + expect(authAcc.authorityType).toBe(0); // Ed25519 + expect(authAcc.role).toBe(0); // Owner + }); + + it("Success: Create wallet with Secp256r1 (WebAuthn) owner", async () => { + const userSeed = getRandomSeed(); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + + const credentialIdHash = getRandomSeed(); + const p256Pubkey = new Uint8Array(33).map(() => Math.floor(Math.random() * 256)); + p256Pubkey[0] = 0x02; + + const [authPda, authBump] = await findAuthorityPda(walletPda, credentialIdHash); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: authPda, + userSeed, + authType: 1, // Secp256r1 + authBump, + authPubkey: p256Pubkey, + credentialHash: credentialIdHash, + })); + + const authAcc = await client.getAuthority(authPda); + expect(authAcc.authorityType).toBe(1); // Secp256r1 + expect(authAcc.role).toBe(0); // Owner + }); + + // --- Discovery --- + + it("Discovery: Ed25519 — pubkey → PDA → wallet", async () => { + const userSeed = getRandomSeed(); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const owner = await generateKeyPairSigner(); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [authPda, authBump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: authPda, + userSeed, + authType: 0, + authBump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })); + + // Discover + const discoveredAuth = await client.getAuthorityByPublicKey(walletPda, owner.address); + expect(discoveredAuth).not.toBeNull(); + expect(discoveredAuth!.wallet).toBe(walletPda); + }); + + it("Discovery: Secp256r1 — credential_id → PDA → wallet", async () => { + const userSeed = getRandomSeed(); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const credIdHash = getRandomSeed(); + const [authPda, authBump] = await findAuthorityPda(walletPda, credIdHash); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: authPda, + userSeed, + authType: 1, + authBump, + authPubkey: new Uint8Array(33).fill(1), + credentialHash: credIdHash, + })); + + const discoveredAuth = await client.getAuthorityByCredentialId(walletPda, credIdHash); + expect(discoveredAuth).not.toBeNull(); + expect(discoveredAuth!.wallet).toBe(walletPda); + }); + + // --- Transfer Ownership --- + + it("Success: Transfer ownership (Ed25519 -> Ed25519)", async () => { + const userSeed = getRandomSeed(); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const currentOwner = await generateKeyPairSigner(); + const currentOwnerBytes = Uint8Array.from(getAddressEncoder().encode(currentOwner.address)); + const [currentAuthPda, currentBump] = await findAuthorityPda(walletPda, currentOwnerBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: currentAuthPda, + userSeed, + authType: 0, + authBump: currentBump, + authPubkey: currentOwnerBytes, + credentialHash: new Uint8Array(32), + })); + + const newOwner = await generateKeyPairSigner(); + const newOwnerBytes = Uint8Array.from(getAddressEncoder().encode(newOwner.address)); + const [newAuthPda] = await findAuthorityPda(walletPda, newOwnerBytes); + + await processInstruction(context, client.transferOwnership({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + currentOwnerAuthority: currentAuthPda, + newOwnerAuthority: newAuthPda, + authType: 0, + authPubkey: newOwnerBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: currentOwner, + }), [currentOwner]); + + const acc = await client.getAuthority(newAuthPda); + expect(acc.role).toBe(0); // Owner + }); + + it("Failure: Admin cannot transfer ownership", async () => { + const userSeed = getRandomSeed(); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + const owner = await generateKeyPairSigner(); + const ownerBytes = Uint8Array.from(getAddressEncoder().encode(owner.address)); + const [ownerAuthPda, bump] = await findAuthorityPda(walletPda, ownerBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: ownerAuthPda, + userSeed, + authType: 0, + authBump: bump, + authPubkey: ownerBytes, + credentialHash: new Uint8Array(32), + })); + + // Add Admin + const admin = await generateKeyPairSigner(); + const adminBytes = Uint8Array.from(getAddressEncoder().encode(admin.address)); + const [adminPda] = await findAuthorityPda(walletPda, adminBytes); + await processInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + adminAuthority: ownerAuthPda, + newAuthority: adminPda, + authType: 0, + newRole: 1, // Admin + authPubkey: adminBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: owner, + }), [owner]); + + // Admin tries to transfer + const result = await tryProcessInstruction(context, client.transferOwnership({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + currentOwnerAuthority: adminPda, + newOwnerAuthority: adminPda, // irrelevant + authType: 0, + authPubkey: adminBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: admin, + }), [admin]); + + expect(result.result).toMatch(/0xbba|3002|simulation failed/i); + }); + + // --- P1: Duplicate Wallet Creation --- + + it("Failure: Cannot create wallet with same seed twice", async () => { + const userSeed = getRandomSeed(); + const [wPda] = await findWalletPda(userSeed); + const [vPda] = await findVaultPda(wPda); + const o = await generateKeyPairSigner(); + const oBytes = Uint8Array.from(getAddressEncoder().encode(o.address)); + const [aPda, aBump] = await findAuthorityPda(wPda, oBytes); + + // First creation — should succeed + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, vault: vPda, authority: aPda, + userSeed, authType: 0, authBump: aBump, + authPubkey: oBytes, credentialHash: new Uint8Array(32), + })); + + // Second creation with same seed — should fail + const o2 = await generateKeyPairSigner(); + const o2Bytes = Uint8Array.from(getAddressEncoder().encode(o2.address)); + const [a2Pda, a2Bump] = await findAuthorityPda(wPda, o2Bytes); + + const result = await tryProcessInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, vault: vPda, authority: a2Pda, + userSeed, authType: 0, authBump: a2Bump, + authPubkey: o2Bytes, credentialHash: new Uint8Array(32), + })); + + expect(result.result).toMatch(/simulation failed|already in use|AccountAlreadyInitialized/i); + }); + + // --- P1: Zero-Address Transfer Ownership (Issue #15) --- + + it("Failure: Cannot transfer ownership to zero address", async () => { + const userSeed = getRandomSeed(); + const [wPda] = await findWalletPda(userSeed); + const [vPda] = await findVaultPda(wPda); + const o = await generateKeyPairSigner(); + const oBytes = Uint8Array.from(getAddressEncoder().encode(o.address)); + const [aPda, aBump] = await findAuthorityPda(wPda, oBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, vault: vPda, authority: aPda, + userSeed, authType: 0, authBump: aBump, + authPubkey: oBytes, credentialHash: new Uint8Array(32), + })); + + // Attempt transfer with zero pubkey + const zeroPubkey = new Uint8Array(32).fill(0); + const [zeroPda] = await findAuthorityPda(wPda, zeroPubkey); + + const result = await tryProcessInstruction(context, client.transferOwnership({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, + currentOwnerAuthority: aPda, + newOwnerAuthority: zeroPda, + authType: 0, + authPubkey: zeroPubkey, + credentialHash: new Uint8Array(32), + authorizerSigner: o, + }), [o]); + + expect(result.result).toMatch(/simulation failed|InvalidAccountData/i); + }); + + // --- P4: Ownership Transfer Verification --- + + it("Success: After transfer ownership, old owner account is closed", async () => { + const userSeed = getRandomSeed(); + const [wPda] = await findWalletPda(userSeed); + const [vPda] = await findVaultPda(wPda); + const oldOwner = await generateKeyPairSigner(); + const oldBytes = Uint8Array.from(getAddressEncoder().encode(oldOwner.address)); + const [oldPda, oldBump] = await findAuthorityPda(wPda, oldBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, vault: vPda, authority: oldPda, + userSeed, authType: 0, authBump: oldBump, + authPubkey: oldBytes, credentialHash: new Uint8Array(32), + })); + + const newOwner = await generateKeyPairSigner(); + const newBytes = Uint8Array.from(getAddressEncoder().encode(newOwner.address)); + const [newPda] = await findAuthorityPda(wPda, newBytes); + + await processInstruction(context, client.transferOwnership({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, + currentOwnerAuthority: oldPda, + newOwnerAuthority: newPda, + authType: 0, + authPubkey: newBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: oldOwner, + }), [oldOwner]); + + // Old owner PDA should be closed (zeroed / null) + const { value: oldAcc } = await context.rpc.getAccountInfo(oldPda).send(); + expect(oldAcc).toBeNull(); + + // New owner should exist with role 0 + const newAcc = await client.getAuthority(newPda); + expect(newAcc.role).toBe(0); + }); + + it("Failure: Old owner cannot act after ownership transfer", async () => { + const userSeed = getRandomSeed(); + const [wPda] = await findWalletPda(userSeed); + const [vPda] = await findVaultPda(wPda); + const oldOwner = await generateKeyPairSigner(); + const oldBytes = Uint8Array.from(getAddressEncoder().encode(oldOwner.address)); + const [oldPda, oldBump] = await findAuthorityPda(wPda, oldBytes); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, vault: vPda, authority: oldPda, + userSeed, authType: 0, authBump: oldBump, + authPubkey: oldBytes, credentialHash: new Uint8Array(32), + })); + + const newOwner = await generateKeyPairSigner(); + const newBytes = Uint8Array.from(getAddressEncoder().encode(newOwner.address)); + const [newPda] = await findAuthorityPda(wPda, newBytes); + + await processInstruction(context, client.transferOwnership({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, + currentOwnerAuthority: oldPda, + newOwnerAuthority: newPda, + authType: 0, + authPubkey: newBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: oldOwner, + }), [oldOwner]); + + // Old owner tries to add authority — should fail (authority PDA closed/zeroed) + const victim = await generateKeyPairSigner(); + const victimBytes = Uint8Array.from(getAddressEncoder().encode(victim.address)); + const [victimPda] = await findAuthorityPda(wPda, victimBytes); + + const result = await tryProcessInstruction(context, client.addAuthority({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: wPda, + adminAuthority: oldPda, + newAuthority: victimPda, + authType: 0, + newRole: 2, + authPubkey: victimBytes, + credentialHash: new Uint8Array(32), + authorizerSigner: oldOwner, + }), [oldOwner]); + + expect(result.result).toMatch(/simulation failed|IllegalOwner|InvalidAccountData/i); + }); + + it("Success: Secp256r1 Owner transfers ownership to Ed25519", async () => { + const { generateMockSecp256r1Signer, createSecp256r1Instruction, buildSecp256r1AuthPayload, getSecp256r1MessageToSign, generateAuthenticatorData } = await import("./secp256r1Utils"); + const crypto = await import("crypto"); + + const userSeed = getRandomSeed(); + const [walletPda] = await findWalletPda(userSeed); + const [vaultPda] = await findVaultPda(walletPda); + + // 1. Create Wallet with Secp256r1 Owner + const secpOwner = await generateMockSecp256r1Signer(); + const [secpOwnerPda, ownerBump] = await findAuthorityPda(walletPda, secpOwner.credentialIdHash); + + await processInstruction(context, client.createWallet({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + vault: vaultPda, + authority: secpOwnerPda, + userSeed, + authType: 1, // Secp256r1 + authBump: ownerBump, + authPubkey: secpOwner.publicKeyBytes, + credentialHash: secpOwner.credentialIdHash, + })); + + // 2. Prepare new Ed25519 Owner + const newOwner = await generateKeyPairSigner(); + const newOwnerBytes = Uint8Array.from(getAddressEncoder().encode(newOwner.address)); + const [newAuthPda] = await findAuthorityPda(walletPda, newOwnerBytes); + + // 3. Perform Transfer + const transferIx = client.transferOwnership({ + config: context.configPda, + treasuryShard: context.treasuryShard, + payer: context.payer, + wallet: walletPda, + currentOwnerAuthority: secpOwnerPda, + newOwnerAuthority: newAuthPda, + authType: 0, // Transfer to Ed25519 + authPubkey: newOwnerBytes, + credentialHash: new Uint8Array(32), + // No authorizerSigner for Secp256r1 + }); + + // Append sysvars AFTER all existing accounts (config/treasury consumed by iterator) + transferIx.accounts = [ + ...(transferIx.accounts || []), + { address: "Sysvar1nstructions1111111111111111111111111" as any, role: 0 }, + { address: "SysvarS1otHashes111111111111111111111111111" as any, role: 0 }, + { address: "SysvarRent111111111111111111111111111111111" as any, role: 0 }, + ]; + + // Fetch current slot and slotHash from SysvarS1otHashes + const slotHashesAddress = "SysvarS1otHashes111111111111111111111111111" as Address; + const accountInfo = await context.rpc.getAccountInfo(slotHashesAddress, { encoding: 'base64' }).send(); + const rawData = Buffer.from(accountInfo.value!.data[0] as string, 'base64'); + const currentSlot = new DataView(rawData.buffer, rawData.byteOffset, rawData.byteLength).getBigUint64(8, true); + + const sysvarIxIndex = transferIx.accounts.length - 3; // Sysvar1nstructions position + const sysvarSlotIndex = transferIx.accounts.length - 2; // SysvarSlotHashes position + + const authenticatorDataRaw = generateAuthenticatorData("example.com"); + const authPayload = buildSecp256r1AuthPayload(sysvarIxIndex, sysvarSlotIndex, authenticatorDataRaw, currentSlot); + + // The signed payload for TransferOwnership is `auth_type(1)` + `full_auth_data(32 for Ed25519)` + `payer(32)` + const signedPayload = new Uint8Array(1 + 32 + 32); + signedPayload[0] = 0; // New type Ed25519 + signedPayload.set(newOwnerBytes, 1); + signedPayload.set(new Uint8Array(getAddressEncoder().encode(context.payer.address)), 33); + + const currentSlotBytes = new Uint8Array(8); + new DataView(currentSlotBytes.buffer).setBigUint64(0, currentSlot, true); + + const discriminator = new Uint8Array([3]); // TransferOwnership is 3 + const msgToSign = getSecp256r1MessageToSign( + discriminator, + authPayload, + signedPayload, + new Uint8Array(getAddressEncoder().encode(context.payer.address)), + new Uint8Array(getAddressEncoder().encode(PROGRAM_ID_STR as import("@solana/kit").Address)), + authenticatorDataRaw, + currentSlotBytes + ); + + const sysvarIx = await createSecp256r1Instruction(secpOwner, msgToSign); + + // Pack the payload into transferIx.data + const originalData = transferIx.data; + const finalTransferData = new Uint8Array(originalData.length + authPayload.length); + finalTransferData.set(originalData, 0); + finalTransferData.set(authPayload, originalData.length); + transferIx.data = finalTransferData; + + const { tryProcessInstructions } = await import("./common"); + const result = await tryProcessInstructions(context, [sysvarIx, transferIx]); + + expect(result.result).toBe("ok"); + + const acc = await client.getAuthority(newAuthPda); + expect(acc.role).toBe(0); // Owner + expect(acc.authorityType).toBe(0); // Ed25519 + }); +}); + diff --git a/tests-real-rpc/tsconfig.json b/tests-real-rpc/tsconfig.json new file mode 100644 index 0000000..216b365 --- /dev/null +++ b/tests-real-rpc/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "types": ["node"] + }, + "include": ["tests/**/*", "scripts/**/*"] +} diff --git a/tests-v1-rpc/package-lock.json b/tests-v1-rpc/package-lock.json new file mode 100644 index 0000000..83284b9 --- /dev/null +++ b/tests-v1-rpc/package-lock.json @@ -0,0 +1,2541 @@ +{ + "name": "lazorkit-tests-v1-rpc", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lazorkit-tests-v1-rpc", + "version": "1.0.0", + "dependencies": { + "@lazorkit/solita-client": "file:../sdk/solita-client", + "@solana/web3.js": "^1.95.4", + "bs58": "^6.0.0", + "dotenv": "^16.4.5", + "ecdsa-secp256r1": "^1.3.3" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.9.3", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + } + }, + "../sdk/solita-client": { + "name": "@lazorkit/solita-client", + "version": "1.0.0", + "dependencies": { + "@metaplex-foundation/beet": "^0.7.2", + "@solana/web3.js": "^1.95.4" + }, + "devDependencies": { + "@metaplex-foundation/solita": "^0.20.1", + "typescript": "^5.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", + "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lazorkit/solita-client": { + "resolved": "../sdk/solita-client", + "link": true + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz", + "integrity": "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz", + "integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz", + "integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz", + "integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz", + "integrity": "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@solana/buffer-layout": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", + "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", + "license": "MIT", + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/codecs-core": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", + "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-numbers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", + "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/errors": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", + "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^14.0.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/web3.js": { + "version": "1.98.4", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", + "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "@noble/curves": "^1.4.2", + "@noble/hashes": "^1.4.0", + "@solana/buffer-layout": "^4.0.1", + "@solana/codecs-numbers": "^2.1.0", + "agentkeepalive": "^4.5.0", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.1", + "node-fetch": "^2.7.0", + "rpc-websockets": "^9.0.2", + "superstruct": "^2.0.2" + } + }, + "node_modules/@solana/web3.js/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@solana/web3.js/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz", + "integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz", + "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "chai": "^6.2.2", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz", + "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz", + "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz", + "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.0", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz", + "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "@vitest/utils": "4.1.0", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz", + "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz", + "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "license": "MIT" + }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "node_modules/borsh/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/borsh/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ecdsa-secp256r1": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/ecdsa-secp256r1/-/ecdsa-secp256r1-1.3.3.tgz", + "integrity": "sha512-7JkHQ43iv9vT1U9tyGuxcE4+SMF/da+YiIMRpcwmbHmJQmqfFUuT6c7LKMasJ9soEpwFL0b9JyNkRjQ+vjVgpQ==", + "license": "MIT", + "dependencies": { + "asn1.js": "^5.0.1", + "bn.js": "^4.11.8" + } + }, + "node_modules/ecdsa-secp256r1/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jayson": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.3.0.tgz", + "integrity": "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==", + "license": "MIT", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "stream-json": "^1.9.1", + "uuid": "^8.3.2", + "ws": "^7.5.10" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jayson/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT" + }, + "node_modules/jayson/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.9.tgz", + "integrity": "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.115.0", + "@rolldown/pluginutils": "1.0.0-rc.9" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-x64": "1.0.0-rc.9", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" + } + }, + "node_modules/rpc-websockets": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.5.tgz", + "integrity": "sha512-4mAmr+AEhPYJ9TmDtxF3r3ZcbWy7W8kvZ4PoZYw/Xgp2J7WixjwTgiQZsoTDvch5nimmg3Ay6/0Kuh9oIvVs9A==", + "license": "LGPL-3.0-only", + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/uuid": "^10.0.0", + "@types/ws": "^8.2.2", + "buffer": "^6.0.3", + "eventemitter3": "^5.0.1", + "uuid": "^11.0.0", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^6.0.0" + } + }, + "node_modules/rpc-websockets/node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/rpc-websockets/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/rpc-websockets/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0.tgz", + "integrity": "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/runtime": "0.115.0", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.9", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.0.0-alpha.31", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz", + "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.0", + "@vitest/mocker": "4.1.0", + "@vitest/pretty-format": "4.1.0", + "@vitest/runner": "4.1.0", + "@vitest/snapshot": "4.1.0", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.0", + "@vitest/browser-preview": "4.1.0", + "@vitest/browser-webdriverio": "4.1.0", + "@vitest/ui": "4.1.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/tests-v1-rpc/package.json b/tests-v1-rpc/package.json new file mode 100644 index 0000000..cde7c1c --- /dev/null +++ b/tests-v1-rpc/package.json @@ -0,0 +1,25 @@ +{ + "name": "lazorkit-tests-v1-rpc", + "version": "1.0.0", + "description": "Real RPC End-to-End Tests for LazorKit using Solita", + "main": "index.js", + "scripts": { + "test": "vitest run", + "test:local": "./scripts/test-local.sh", + "test:devnet": "vitest run --fileParallelism=false --testTimeout 60000", + "test:devnet:file": "vitest run --fileParallelism=false --testTimeout 60000" + }, + "dependencies": { + "@solana/web3.js": "^1.95.4", + "@lazorkit/solita-client": "file:../sdk/solita-client", + "bs58": "^6.0.0", + "dotenv": "^16.4.5", + "ecdsa-secp256r1": "^1.3.3" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.9.3", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + } +} diff --git a/tests-v1-rpc/scripts/test-local.sh b/tests-v1-rpc/scripts/test-local.sh new file mode 100755 index 0000000..1a61837 --- /dev/null +++ b/tests-v1-rpc/scripts/test-local.sh @@ -0,0 +1,73 @@ +#!/bin/bash +set -e + +# Configuration +TEST_DIR="$(pwd)" +SOLANA_DIR="$TEST_DIR/.test-ledger" +PROGRAM_DIR="$(cd ../program && pwd)" +DEPLOY_DIR="$(cd ../target/deploy && pwd)" + +# Resolve the current deployed program id from the keypair +PROGRAM_ID=$(solana address -k "$DEPLOY_DIR/lazorkit_program-keypair.json") +echo "Resolved program id: $PROGRAM_ID" + +# Define cleanup function to safely shut down validator on exit +function cleanup { + echo "-> Cleaning up..." + if [ -n "$VALIDATOR_PID" ]; then + kill $VALIDATOR_PID || true + fi + rm -rf "$SOLANA_DIR" +} +trap cleanup EXIT + +echo "=========================================================" +echo "🔬 Starting LazorKit Local Validator and E2E Tests..." +echo "=========================================================" + +# 0. Cleanup any existing validator to avoid port conflicts +echo "-> Cleaning up any existing solana-test-validator..." +if [ -f "$SOLANA_DIR/validator.pid" ]; then + OLD_PID=$(cat "$SOLANA_DIR/validator.pid") + kill "$OLD_PID" 2>/dev/null || true +fi +pkill -f solana-test-validator 2>/dev/null || true + +# 1. Start solana-test-validator in the background +echo "-> Starting solana-test-validator..." +mkdir -p "$SOLANA_DIR" + +solana-test-validator \ + --ledger "$SOLANA_DIR" \ + --bpf-program "$PROGRAM_ID" "$DEPLOY_DIR/lazorkit_program.so" \ + --reset \ + --quiet & + +VALIDATOR_PID=$! +echo $VALIDATOR_PID > "$SOLANA_DIR/validator.pid" + +# Wait for validator to be ready +echo "-> Waiting for validator to start..." +if ! kill -0 "$VALIDATOR_PID" 2>/dev/null; then + echo "❌ solana-test-validator failed to start (pid exited early)." + exit 1 +fi +while ! curl -s http://127.0.0.1:8899 > /dev/null; do + sleep 1 +done +echo "-> Validator is up!" + +# Set connection pointing to our local node +export RPC_URL="http://127.0.0.1:8899" +export WS_URL="ws://127.0.0.1:8900" + +# 2. Run Test Suite +echo "-> Running Vitest suite sequentially..." +cd "$TEST_DIR" + +# Allow passing a specific test file or directory as argument +TEST_TARGET=${1:-"tests/"} + +npm run test -- "$TEST_TARGET" --fileParallelism=false --testTimeout=30000 --hookTimeout=30000 + +echo "✅ All tests completed!" diff --git a/tests-v1-rpc/test-ecdsa.js b/tests-v1-rpc/test-ecdsa.js new file mode 100644 index 0000000..2704a9f --- /dev/null +++ b/tests-v1-rpc/test-ecdsa.js @@ -0,0 +1,15 @@ +const ECDSA = require('ecdsa-secp256r1'); + +async function main() { + const key = await ECDSA.generateKey(); + const pub = key.toCompressedPublicKey(); + console.log("pub base64 len:", pub.length, pub); + console.log("pub buf len:", Buffer.from(pub, 'base64').length); + + const msg = Buffer.alloc(32, 1); + const sig = await key.sign(msg); + console.log("sig base64:", sig); + console.log("sig buffer len:", Buffer.from(sig, 'base64').length); +} + +main().catch(console.error); diff --git a/tests-v1-rpc/tests/01-config.test.ts b/tests-v1-rpc/tests/01-config.test.ts new file mode 100644 index 0000000..a4e281b --- /dev/null +++ b/tests-v1-rpc/tests/01-config.test.ts @@ -0,0 +1,134 @@ +import { expect, describe, it, beforeAll } from "vitest"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { setupTest, sendTx, tryProcessInstructions, type TestContext, PROGRAM_ID, getSystemTransferIx } from "./common"; +import { + findConfigPda, + findTreasuryShardPda, + findWalletPda, + LazorClient, // <--- Add LazorClient +} from "@lazorkit/solita-client"; + +describe("Config and Treasury Instructions", () => { + let ctx: TestContext; + // <--- Add highClient + + beforeAll(async () => { + ctx = await setupTest(); + // <--- Initialize + }); + + it("should fail to initialize an already initialized Config PDA", async () => { + const initConfigIx = await ctx.highClient.initializeConfig({ + admin: ctx.payer, + walletFee: 10000n, + actionFee: 1000n, + numShards: 16 + }); + + // This should fail because setupTest already initialized it + const result = await tryProcessInstructions(ctx, [initConfigIx], [ctx.payer]); + expect(result.result).not.toBe("ok"); + }); + + it("should update config parameters by admin", async () => { + const updateConfigIx = await ctx.highClient.updateConfig({ + admin: ctx.payer, + walletFee: 20000n, + actionFee: 2000n, + numShards: 32, + }); + + const result = await tryProcessInstructions(ctx, [updateConfigIx], [ctx.payer]); + expect(result.result).toBe("ok"); + + // state change check omitted for simplicity as long as transaction succeeds + }); + + it("should reject update config from non-admin", async () => { + const nonAdmin = Keypair.generate(); + + const updateConfigIx = await ctx.highClient.updateConfig({ + admin: nonAdmin, + walletFee: 50000n, + }); + + const result = await tryProcessInstructions(ctx, [updateConfigIx], [nonAdmin]); + expect(result.result).not.toBe("ok"); + }); + + it("should reject update config if a wrong account type is passed (discriminator check)", async () => { + // We'll use the Wallet PDA of some wallet (or just random seed) as fake config + const userSeed = new Uint8Array(32); + crypto.getRandomValues(userSeed); + const [walletPda] = findWalletPda(userSeed); + + const updateConfigIx = await ctx.highClient.updateConfig({ + admin: ctx.payer, + walletFee: 50000n, + configPda: walletPda, // WRONG Account + }); + + const result = await tryProcessInstructions(ctx, [updateConfigIx], [ctx.payer]); + expect(result.result).not.toBe("ok"); + }); + + it("should initialize a new treasury shard", async () => { + let treasuryShardPda = PublicKey.default; + let shardId = 0; + + for (let i = 0; i < 16; i++) { + shardId = i; + const [pda] = findTreasuryShardPda(shardId, PROGRAM_ID); + treasuryShardPda = pda; + + const shardInfo = await ctx.connection.getAccountInfo(treasuryShardPda); + if (!shardInfo) { + break; + } + } + + const initShardIx = await ctx.highClient.initTreasuryShard({ + payer: ctx.payer, + shardId, + }); + + const result = await tryProcessInstructions(ctx, [initShardIx], [ctx.payer]); + expect(result.result).toBe("ok"); + }); + + it("should sweep treasury shard funds as admin", async () => { + const pubkeyBytes = ctx.payer.publicKey.toBytes(); + const sum = pubkeyBytes.reduce((a, b) => a + b, 0); + const shardId = sum % 16; + const [treasuryShardPda] = findTreasuryShardPda(shardId, PROGRAM_ID); + + // Fund shard directly to simulate fees + await sendTx(ctx, [getSystemTransferIx(ctx.payer.publicKey, treasuryShardPda, 10000n)]); + + const sweepIx = await ctx.highClient.sweepTreasury({ + admin: ctx.payer, + destination: ctx.payer.publicKey, + shardId, + }); + + const result = await tryProcessInstructions(ctx, [sweepIx], [ctx.payer]); + expect(result.result).toBe("ok"); + + const shardBalance = await ctx.connection.getBalance(treasuryShardPda); + expect(shardBalance).toBeGreaterThan(0); // Standard rent exemption preserved + }); + + it("should reject sweep treasury from non-admin", async () => { + const nonAdmin = Keypair.generate(); + const shardId = 0; + + const sweepIx = await ctx.highClient.sweepTreasury({ + admin: nonAdmin, + destination: nonAdmin.publicKey, + shardId, + }); + + const result = await tryProcessInstructions(ctx, [sweepIx], [nonAdmin]); + expect(result.result).not.toBe("ok"); + }); +}); diff --git a/tests-v1-rpc/tests/02-wallet.test.ts b/tests-v1-rpc/tests/02-wallet.test.ts new file mode 100644 index 0000000..239a222 --- /dev/null +++ b/tests-v1-rpc/tests/02-wallet.test.ts @@ -0,0 +1,387 @@ +/** + * 02-wallet.test.ts + * + * Tests for: CreateWallet, Wallet Discovery, Account Data Integrity, LazorClient wrapper (create/execute/txn) + * Merged from: wallet.test.ts (create + discovery), discovery.test.ts, full_flow.test.ts, integrity.test.ts, high_level.test.ts + */ + +import { Keypair, PublicKey } from "@solana/web3.js"; +import { describe, it, expect, beforeAll } from "vitest"; +import { + findWalletPda, + findVaultPda, + findAuthorityPda, + AuthorityAccount, + LazorClient, + AuthType, + Role, +} from "@lazorkit/solita-client"; +import { setupTest, sendTx, getRandomSeed, tryProcessInstruction, type TestContext, getSystemTransferIx, PROGRAM_ID } from "./common"; + +describe("CreateWallet & Discovery", () => { + let ctx: TestContext; + + beforeAll(async () => { + ctx = await setupTest(); + }, 30_000); + + // ─── Create Wallet ───────────────────────────────────────────────────────── + + it("Create wallet with Ed25519 owner", async () => { + const userSeed = getRandomSeed(); + const owner = Keypair.generate(); + + const { ix, walletPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey, + userSeed + }); + + await sendTx(ctx, [ix]); + + const [authPda] = findAuthorityPda(walletPda, owner.publicKey.toBytes()); + const authAcc = await AuthorityAccount.fromAccountAddress(ctx.connection, authPda); + expect(authAcc.authorityType).toBe(0); // Ed25519 + expect(authAcc.role).toBe(0); // Owner + }, 30_000); + + it("Create wallet with Secp256r1 (WebAuthn) owner", async () => { + const userSeed = getRandomSeed(); + const credentialIdHash = getRandomSeed(); + const p256Pubkey = new Uint8Array(33).map(() => Math.floor(Math.random() * 256)); + p256Pubkey[0] = 0x02; + + const { ix, walletPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Secp256r1, + pubkey: p256Pubkey, + credentialHash: credentialIdHash, + userSeed + }); + + await sendTx(ctx, [ix]); + + const [authPda] = findAuthorityPda(walletPda, credentialIdHash); + const authAcc = await AuthorityAccount.fromAccountAddress(ctx.connection, authPda); + expect(authAcc.authorityType).toBe(1); // Secp256r1 + expect(authAcc.role).toBe(0); // Owner + }, 30_000); + + it("Create wallet with Web Crypto P-256 keypair (real RPC)", async () => { + const userSeed = new Uint8Array(32); + crypto.getRandomValues(userSeed); + + const [walletPda] = findWalletPda(userSeed); + + const p256Keypair = await crypto.subtle.generateKey( + { name: "ECDSA", namedCurve: "P-256" }, + true, + ["sign", "verify"] + ); + + const rpId = "lazorkit.valid"; + const rpIdHashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(rpId)); + const credentialIdHash = new Uint8Array(rpIdHashBuffer); + + const spki = await crypto.subtle.exportKey("spki", p256Keypair.publicKey); + let rawP256Pubkey = new Uint8Array(spki).slice(-64); + let p256PubkeyCompressed = new Uint8Array(33); + p256PubkeyCompressed[0] = (rawP256Pubkey[63] % 2 === 0) ? 0x02 : 0x03; + p256PubkeyCompressed.set(rawP256Pubkey.slice(0, 32), 1); + + const { ix } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Secp256r1, + pubkey: p256PubkeyCompressed, + credentialHash: credentialIdHash, + userSeed + }); + + const txResult = await sendTx(ctx, [ix]); + expect(txResult).toBeDefined(); + + const res = await ctx.connection.getAccountInfo(walletPda); + expect(res).toBeDefined(); + expect(res!.data).toBeDefined(); + }, 30_000); + + it("Failure: Cannot create wallet with same seed twice", async () => { + const userSeed = getRandomSeed(); + const o = Keypair.generate(); + const { ix: createIx } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: o.publicKey, + userSeed + }); + await sendTx(ctx, [createIx]); + + const o2 = Keypair.generate(); + const { ix: create2Ix } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: o2.publicKey, + userSeed + }); + + const result = await tryProcessInstruction(ctx, [create2Ix]); + expect(result.result).toMatch(/simulation failed|already in use|AccountAlreadyInitialized/i); + }, 30_000); + + // ─── Discovery ───────────────────────────────────────────────────────────── + + it("Discovery: Ed25519 — pubkey → PDA → wallet", async () => { + const userSeed = getRandomSeed(); + const owner = Keypair.generate(); + + const { ix, walletPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey, + userSeed + }); + await sendTx(ctx, [ix]); + + const discoveredWallets = await LazorClient.findWalletsByEd25519Pubkey(ctx.connection, owner.publicKey); + expect(discoveredWallets).toContainEqual(walletPda); + }, 30_000); + + it("Discovery: Secp256r1 — credential hash → authorities", async () => { + const userSeed = new Uint8Array(32); + crypto.getRandomValues(userSeed); + const credentialIdHash = new Uint8Array(32); + crypto.getRandomValues(credentialIdHash); + const [walletPda] = findWalletPda(userSeed); + const [authPda] = findAuthorityPda(walletPda, credentialIdHash); + + const authPubkey = new Uint8Array(33).fill(7); + + const { ix: createIx } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Secp256r1, + pubkey: authPubkey, + credentialHash: credentialIdHash, + userSeed + }); + await sendTx(ctx, [createIx]); + + // Use SDK's findAllAuthoritiesByCredentialHash + const discovered = await LazorClient.findAllAuthoritiesByCredentialHash(ctx.connection, credentialIdHash); + + expect(discovered.length).toBeGreaterThanOrEqual(1); + const found = discovered.find((d: any) => d.authority.equals(authPda)); + expect(found).toBeDefined(); + expect(found?.wallet.equals(walletPda)).toBe(true); + expect(found?.role).toBe(0); // Owner + expect(found?.authorityType).toBe(1); // Secp256r1 + }, 60_000); + + // ─── Data Integrity ──────────────────────────────────────────────────────── + + const HEADER_SIZE = 48; + const DATA_OFFSET = HEADER_SIZE; + const SECP256R1_PUBKEY_OFFSET = DATA_OFFSET + 32; + + async function getRawAccountData(ctx: TestContext, address: PublicKey): Promise { + const acc = await ctx.connection.getAccountInfo(address); + if (!acc) throw new Error(`Account ${address.toBase58()} not found`); + return acc.data; + } + + it("Integrity: Ed25519 pubkey stored at correct offset", async () => { + const userSeed = getRandomSeed(); + const owner = Keypair.generate(); + + const { ix, walletPda, authorityPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey, + userSeed + }); + await sendTx(ctx, [ix]); + + const data = await getRawAccountData(ctx, authorityPda); + + expect(data[0]).toBe(2); // discriminator = Authority + expect(data[1]).toBe(0); // authority_type = Ed25519 + expect(data[2]).toBe(0); // role = Owner + + const storedWallet = data.subarray(16, 48); + expect(Uint8Array.from(storedWallet)).toEqual(walletPda.toBytes()); + + const storedPubkey = data.subarray(DATA_OFFSET, DATA_OFFSET + 32); + expect(Uint8Array.from(storedPubkey)).toEqual(owner.publicKey.toBytes()); + }); + + it("Integrity: Secp256r1 credential_id_hash + pubkey stored at correct offsets", async () => { + const userSeed = getRandomSeed(); + const credentialIdHash = getRandomSeed(); + const p256Pubkey = new Uint8Array(33); + p256Pubkey[0] = 0x02; + crypto.getRandomValues(p256Pubkey.subarray(1)); + + const { ix, walletPda, authorityPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Secp256r1, + pubkey: p256Pubkey, + credentialHash: credentialIdHash, + userSeed + }); + await sendTx(ctx, [ix]); + + const data = await getRawAccountData(ctx, authorityPda); + + expect(data[0]).toBe(2); + expect(data[1]).toBe(1); + expect(data[2]).toBe(0); + + const storedCredHash = data.subarray(DATA_OFFSET, DATA_OFFSET + 32); + expect(Uint8Array.from(storedCredHash)).toEqual(credentialIdHash); + + const storedPubkey = data.subarray(SECP256R1_PUBKEY_OFFSET, SECP256R1_PUBKEY_OFFSET + 33); + expect(Uint8Array.from(storedPubkey)).toEqual(p256Pubkey); + }); + + it("Integrity: Multiple Secp256r1 authorities with different credential_id_hash", async () => { + const userSeed = getRandomSeed(); + const owner = Keypair.generate(); + + const { ix, walletPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey, + userSeed + }); + await sendTx(ctx, [ix]); + + const credHash1 = getRandomSeed(); + const pubkey1 = new Uint8Array(33); pubkey1[0] = 0x02; crypto.getRandomValues(pubkey1.subarray(1)); + + const { ix: ixAdd1, newAuthority: authPda1 } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + walletPda, + adminType: AuthType.Ed25519, + adminSigner: owner, + newAuthPubkey: pubkey1, + newAuthType: AuthType.Secp256r1, + role: Role.Admin, + newCredentialHash: credHash1 + }); + await sendTx(ctx, [ixAdd1], [owner]); + + const credHash2 = getRandomSeed(); + const pubkey2 = new Uint8Array(33); pubkey2[0] = 0x03; crypto.getRandomValues(pubkey2.subarray(1)); + + const { ix: ixAdd2, newAuthority: authPda2 } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + walletPda, + adminType: AuthType.Ed25519, + adminSigner: owner, + newAuthPubkey: pubkey2, + newAuthType: AuthType.Secp256r1, + role: Role.Spender, + newCredentialHash: credHash2 + }); + await sendTx(ctx, [ixAdd2], [owner]); + + expect(authPda1.toBase58()).not.toEqual(authPda2.toBase58()); + + const data1 = await getRawAccountData(ctx, authPda1); + expect(data1[1]).toBe(1); + expect(data1[2]).toBe(1); + expect(Uint8Array.from(data1.subarray(DATA_OFFSET, DATA_OFFSET + 32))).toEqual(credHash1); + + const data2 = await getRawAccountData(ctx, authPda2); + expect(data2[1]).toBe(1); + expect(data2[2]).toBe(2); + expect(Uint8Array.from(data2.subarray(DATA_OFFSET, DATA_OFFSET + 32))).toEqual(credHash2); + }); + + // ─── LazorClient Wrapper ────────────────────────────────────────────────── + + it("LazorClient: create wallet + execute with simplified APIs", async () => { + const owner = Keypair.generate(); + + const { ix, walletPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey + }); + await sendTx(ctx, [ix]); + expect(walletPda).toBeDefined(); + + const [vaultPda] = findVaultPda(walletPda); + await sendTx(ctx, [getSystemTransferIx(ctx.payer.publicKey, vaultPda, 10_000_000n)]); + + const recipient = Keypair.generate().publicKey; + + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + innerInstructions: [getSystemTransferIx(vaultPda, recipient, 1_000_000n)], + signer: owner + }); + await sendTx(ctx, [executeIx], [owner]); + + const bal = await ctx.connection.getBalance(recipient); + expect(bal).toBe(1_000_000); + }); + + it("LazorClient: add authority using high-level methods", async () => { + const owner = Keypair.generate(); + + const { ix, walletPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey + }); + await sendTx(ctx, [ix]); + + const newAuthority = Keypair.generate(); + + const { ix: ixAdd } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + walletPda, + adminType: AuthType.Ed25519, + adminSigner: owner, + newAuthPubkey: newAuthority.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Admin, + }); + await sendTx(ctx, [ixAdd], [owner]); + + const [newAuthPda] = findAuthorityPda(walletPda, newAuthority.publicKey.toBytes()); + const accInfo = await ctx.connection.getAccountInfo(newAuthPda); + expect(accInfo).toBeDefined(); + expect(accInfo!.data[0]).toBe(2); + }); + + it("LazorClient: create wallet + execute via Transaction Builders (...Txn)", async () => { + const owner = Keypair.generate(); + + const { transaction, walletPda, authorityPda } = await ctx.highClient.createWalletTxn({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey + }); + await sendTx(ctx, transaction.instructions); + expect(walletPda).toBeDefined(); + + const [vaultPda] = findVaultPda(walletPda); + await sendTx(ctx, [getSystemTransferIx(ctx.payer.publicKey, vaultPda, 10_000_000n)]); + + const recipient = Keypair.generate().publicKey; + + const execTx = await ctx.highClient.executeTxn({ + payer: ctx.payer, + walletPda, + innerInstructions: [getSystemTransferIx(vaultPda, recipient, 1_000_000n)], + signer: owner + }); + await sendTx(ctx, execTx.instructions, [owner]); + + const bal = await ctx.connection.getBalance(recipient); + expect(bal).toBe(1_000_000); + }); +}); diff --git a/tests-v1-rpc/tests/03-authority.test.ts b/tests-v1-rpc/tests/03-authority.test.ts new file mode 100644 index 0000000..c483fd4 --- /dev/null +++ b/tests-v1-rpc/tests/03-authority.test.ts @@ -0,0 +1,502 @@ +import { Keypair, PublicKey } from "@solana/web3.js"; +import { describe, it, expect, beforeAll } from "vitest"; +import { + findWalletPda, + findVaultPda, + findAuthorityPda, + AuthorityAccount, + AuthType, + Role // <--- Add AuthType, Role +} from "@lazorkit/solita-client"; +import { setupTest, sendTx, getRandomSeed, tryProcessInstruction, tryProcessInstructions, type TestContext, PROGRAM_ID } from "./common"; + +describe("LazorKit V1 — Authority", () => { + let ctx: TestContext; + + let ownerKeypair: Keypair; + let userSeed: Uint8Array; + let walletPda: PublicKey; + let vaultPda: PublicKey; + let ownerAuthPda: PublicKey; + // <--- Add highClient + + beforeAll(async () => { + ctx = await setupTest(); + // <--- Initialize + + ownerKeypair = Keypair.generate(); + userSeed = getRandomSeed(); + + const { ix, walletPda: w } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: ownerKeypair.publicKey, + userSeed + }); + await sendTx(ctx, [ix]); + walletPda = w; + + const [v] = findVaultPda(walletPda); + vaultPda = v; + + const [oPda] = findAuthorityPda(walletPda, ownerKeypair.publicKey.toBytes()); + ownerAuthPda = oPda; + + console.log("Wallet created for authority tests"); + }, 30_000); + + it("Success: Owner adds an Admin (Ed25519)", async () => { + const newAdmin = Keypair.generate(); + + const { ix } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: newAdmin.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Admin, + walletPda + }); + + await sendTx(ctx, [ix], [ownerKeypair]); + + const [newAdminPda] = findAuthorityPda(walletPda, newAdmin.publicKey.toBytes()); + const acc = await AuthorityAccount.fromAccountAddress(ctx.connection, newAdminPda); + expect(acc.role).toBe(1); // Admin + }, 30_000); + + it("Success: Admin adds a Spender", async () => { + // First, Owner adds an Admin to the wallet + const admin = Keypair.generate(); + const { ix: ixAddAdmin } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: admin.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Admin, + walletPda + }); + await sendTx(ctx, [ixAddAdmin], [ownerKeypair]); + + const spender = Keypair.generate(); + + // The newly created Admin adds a Spender + const { ix } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: admin, // FIXED: Admin is actually signing! + newAuthPubkey: spender.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda + }); + + await sendTx(ctx, [ix], [admin]); + + const [spenderPda] = findAuthorityPda(walletPda, spender.publicKey.toBytes()); + const acc = await AuthorityAccount.fromAccountAddress(ctx.connection, spenderPda); + expect(acc.role).toBe(2); // Spender + }, 30_000); + + it("Success: Owner adds a Secp256r1 Admin", async () => { + const credentialIdHash = getRandomSeed(); + const p256Pubkey = new Uint8Array(33); + crypto.getRandomValues(p256Pubkey); + p256Pubkey[0] = 0x02; + + const { ix } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: p256Pubkey, + newAuthType: AuthType.Secp256r1, + role: Role.Admin, + walletPda, + newCredentialHash: credentialIdHash + }); + + await sendTx(ctx, [ix], [ownerKeypair]); + + const [newAdminPda] = findAuthorityPda(walletPda, credentialIdHash); + const acc = await AuthorityAccount.fromAccountAddress(ctx.connection, newAdminPda); + expect(acc.authorityType).toBe(1); // Secp256r1 + expect(acc.role).toBe(1); // Admin + }, 30_000); + + it("Failure: Admin tries to add an Admin", async () => { + const admin = Keypair.generate(); + + // First, add the Admin + const { ix: ixAdd } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: admin.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Admin, + walletPda + }); + + await sendTx(ctx, [ixAdd], [ownerKeypair]); + + const anotherAdmin = Keypair.generate(); + + // Admin tries to add another Admin -> should fail + const { ix: ixFail } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: admin, + newAuthPubkey: anotherAdmin.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Admin, + walletPda + }); + + const result = await tryProcessInstruction(ctx, [ixFail], [admin]); + expect(result.result).toMatch(/simulation failed|3002|0xbba/i); // PermissionDenied + }, 30_000); + + it("Success: Admin removes a Spender", async () => { + // Add Admin + const admin = Keypair.generate(); + const { ix: ixAddAdmin } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: admin.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Admin, + walletPda + }); + await sendTx(ctx, [ixAddAdmin], [ownerKeypair]); + + // Add Spender + const spender = Keypair.generate(); + const { ix: ixAddSpender } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: spender.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda + }); + await sendTx(ctx, [ixAddSpender], [ownerKeypair]); + + const [spenderPda] = findAuthorityPda(walletPda, spender.publicKey.toBytes()); + + // Admin removes Spender + const ixRemove = await ctx.highClient.removeAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: admin, + authorityToRemovePda: spenderPda, + refundDestination: ctx.payer.publicKey, + walletPda + }); + await sendTx(ctx, [ixRemove], [admin]); + + const info = await ctx.connection.getAccountInfo(spenderPda); + expect(info).toBeNull(); + }, 30_000); + + it("Failure: Spender tries to remove another Spender", async () => { + const s1 = Keypair.generate(); + const { ix: ixAdd1 } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: s1.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda + }); + await sendTx(ctx, [ixAdd1], [ownerKeypair]); + + const s2 = Keypair.generate(); + const { ix: ixAdd2 } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: s2.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda + }); + await sendTx(ctx, [ixAdd2], [ownerKeypair]); + + const [s1Pda] = findAuthorityPda(walletPda, s1.publicKey.toBytes()); + const [s2Pda] = findAuthorityPda(walletPda, s2.publicKey.toBytes()); + + const removeIx = await ctx.highClient.removeAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: s1, + authorityToRemovePda: s2Pda, + refundDestination: ctx.payer.publicKey, + walletPda + }); + + const result = await tryProcessInstruction(ctx, [removeIx], [s1]); + expect(result.result).toMatch(/simulation failed|3002|0xbba/i); + }, 30_000); + + it("Success: Secp256r1 Admin removes a Spender", async () => { + const { generateMockSecp256r1Signer, buildAuthPayload, buildSecp256r1Message, buildSecp256r1PrecompileIx, buildAuthenticatorData, readCurrentSlot, appendSecp256r1Sysvars } = await import("./secp256r1Utils"); + const secpAdmin = await generateMockSecp256r1Signer(); + const [secpAdminPda] = findAuthorityPda(walletPda, secpAdmin.credentialIdHash); + + // Add Secp256r1 Admin + const { ix: ixAddSecp } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: secpAdmin.publicKeyBytes, + newAuthType: AuthType.Secp256r1, + role: Role.Admin, + walletPda, + newCredentialHash: secpAdmin.credentialIdHash + }); + await sendTx(ctx, [ixAddSecp], [ownerKeypair]); + + // Create a disposable Spender + const victim = Keypair.generate(); + const [victimPda] = findAuthorityPda(walletPda, victim.publicKey.toBytes()); + + const { ix: ixAddVictim } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: victim.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda + }); + await sendTx(ctx, [ixAddVictim], [ownerKeypair]); + + // Secp256r1 Admin removes the victim + const removeAuthIx = await ctx.highClient.removeAuthority({ + payer: ctx.payer, + adminType: AuthType.Secp256r1, + authorityToRemovePda: victimPda, + refundDestination: ctx.payer.publicKey, + walletPda, + adminCredentialHash: secpAdmin.credentialIdHash, + }); + + // Append sysvars via SDK helper + const { ix: ixWithSysvars, sysvarIxIndex, sysvarSlotIndex } = appendSecp256r1Sysvars(removeAuthIx); + + // Read slot via SDK + const currentSlot = await readCurrentSlot(ctx.connection); + + // Build auth payload via SDK + const authenticatorData = await buildAuthenticatorData("example.com"); + const authPayload = buildAuthPayload({ + sysvarIxIndex, + sysvarSlotIndex, + authenticatorData, + slot: currentSlot, + }); + + // signedPayload: target_auth_pda (32) + refund_dest (32) + const signedPayload = new Uint8Array(64); + signedPayload.set(victimPda.toBytes(), 0); + signedPayload.set(ctx.payer.publicKey.toBytes(), 32); + + // Build message via SDK + const msgToSign = await buildSecp256r1Message({ + discriminator: 2, // RemoveAuthority + authPayload, + signedPayload, + payer: ctx.payer.publicKey, + programId: PROGRAM_ID, + slot: currentSlot, + }); + + // Build precompile instruction via SDK + const sysvarIx = await buildSecp256r1PrecompileIx(secpAdmin, msgToSign); + + // Append authPayload to data + const newIxData = Buffer.alloc(ixWithSysvars.data.length + authPayload.length); + ixWithSysvars.data.copy(newIxData, 0); + newIxData.set(authPayload, ixWithSysvars.data.length); + ixWithSysvars.data = newIxData; + + const result = await tryProcessInstructions(ctx, [sysvarIx, ixWithSysvars]); + expect(result.result).toBe("ok"); + + const info = await ctx.connection.getAccountInfo(victimPda); + expect(info).toBeNull(); + }, 30_000); + + it("Failure: Spender cannot add any authority", async () => { + const spender = Keypair.generate(); + const { ix: ixAdd } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: spender.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda + }); + await sendTx(ctx, [ixAdd], [ownerKeypair]); + + const victim = Keypair.generate(); + const [spenderPda] = findAuthorityPda(walletPda, spender.publicKey.toBytes()); + + // Spender tries to add -> should fail + const { ix: ixFail } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: spender, + newAuthPubkey: victim.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda + }); + + const result = await tryProcessInstruction(ctx, [ixFail], [spender]); + expect(result.result).toMatch(/simulation failed|3002|0xbba/i); + }, 30_000); + + it("Failure: Admin cannot remove Owner", async () => { + const admin = Keypair.generate(); + const { ix: ixAddAdmin } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: admin.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Admin, + walletPda + }); + await sendTx(ctx, [ixAddAdmin], [ownerKeypair]); + + // Admin tries to remove Owner + const removeIx = await ctx.highClient.removeAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: admin, + authorityToRemovePda: ownerAuthPda, + refundDestination: ctx.payer.publicKey, + walletPda + }); + + const result = await tryProcessInstruction(ctx, [removeIx], [admin]); + expect(result.result).toMatch(/simulation failed|3002|0xbba/i); + }, 30_000); + + it("Failure: Admin cannot remove another Admin", async () => { + const admin1 = Keypair.generate(); + const { ix: ixAddAdmin1 } = await ctx.highClient.addAuthority({ payer: ctx.payer, + adminType: AuthType.Ed25519, adminSigner: ownerKeypair, + newAuthPubkey: admin1.publicKey.toBytes(), newAuthType: AuthType.Ed25519, role: Role.Admin, walletPda + }); + await sendTx(ctx, [ixAddAdmin1], [ownerKeypair]); + + const admin2 = Keypair.generate(); + const { ix: ixAddAdmin2 } = await ctx.highClient.addAuthority({ payer: ctx.payer, + adminType: AuthType.Ed25519, adminSigner: ownerKeypair, + newAuthPubkey: admin2.publicKey.toBytes(), newAuthType: AuthType.Ed25519, role: Role.Admin, walletPda + }); + await sendTx(ctx, [ixAddAdmin2], [ownerKeypair]); + + const [admin2Pda] = findAuthorityPda(walletPda, admin2.publicKey.toBytes()); + + // Admin1 tries to remove Admin2 + const removeIx = await ctx.highClient.removeAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: admin1, + authorityToRemovePda: admin2Pda, + refundDestination: ctx.payer.publicKey, + walletPda + }); + + const result = await tryProcessInstruction(ctx, [removeIx], [admin1]); + expect(result.result).toMatch(/simulation failed|3002|0xbba/i); + }, 30_000); + + it("Failure: Authority from Wallet A cannot add authority to Wallet B", async () => { + const userSeedB = getRandomSeed(); + const [walletPdaB] = findWalletPda(userSeedB); + const ownerB = Keypair.generate(); + + const { ix: ixCreateB } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: ownerB.publicKey, + userSeed: userSeedB + }); + await sendTx(ctx, [ixCreateB]); + + const victim = Keypair.generate(); + const [victimPda] = findAuthorityPda(walletPdaB, victim.publicKey.toBytes()); + + const { ix } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, // Wallet A + newAuthPubkey: victim.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda: walletPdaB, // target is B + adminAuthorityPda: ownerAuthPda // Pass the INITIALIZED authority from Wallet A + }); + + const result = await tryProcessInstruction(ctx, [ix], [ownerKeypair]); + expect(result.result).toMatch(/simulation failed|InvalidAccountData/i); + }, 30_000); + + it("Failure: Cannot add same authority twice", async () => { + const newUser = Keypair.generate(); + + const { ix: addIx } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: newUser.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda + }); + + await sendTx(ctx, [addIx], [ownerKeypair]); + + const result = await tryProcessInstruction(ctx, [addIx], [ownerKeypair]); + expect(result.result).toMatch(/simulation failed|already in use|AccountAlreadyInitialized/i); + }, 30_000); + + it("Edge: Owner can remove itself (leaves wallet ownerless)", async () => { + const userSeed2 = getRandomSeed(); + const [wPda] = findWalletPda(userSeed2); + const [vPda] = findVaultPda(wPda); + const o = Keypair.generate(); + const [oPda] = findAuthorityPda(wPda, o.publicKey.toBytes()); + + const { ix: ixCreate } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: o.publicKey, + userSeed: userSeed2 + }); + await sendTx(ctx, [ixCreate]); + + const removeIx = await ctx.highClient.removeAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: o, + authorityToRemovePda: oPda, + refundDestination: ctx.payer.publicKey, + walletPda: wPda + }); + + await sendTx(ctx, [removeIx], [o]); + + const info = await ctx.connection.getAccountInfo(oPda); + expect(info).toBeNull(); + }, 30_000); +}); diff --git a/tests-v1-rpc/tests/04-execute.test.ts b/tests-v1-rpc/tests/04-execute.test.ts new file mode 100644 index 0000000..16beb56 --- /dev/null +++ b/tests-v1-rpc/tests/04-execute.test.ts @@ -0,0 +1,435 @@ +/** + * LazorKit V1 Client — Execute tests + * + * Tests: Execute instruction with SOL transfer inner instructions, batches, re-entrancy, and Secp256r1. + */ + +import { Keypair, PublicKey, SystemProgram, LAMPORTS_PER_SOL } from "@solana/web3.js"; +import { describe, it, expect, beforeAll } from "vitest"; +import { + findWalletPda, + findVaultPda, + findAuthorityPda, + findSessionPda, + AuthType, + Role // <--- Add AuthType, Role +} from "@lazorkit/solita-client"; +import { setupTest, sendTx, tryProcessInstruction, tryProcessInstructions, type TestContext, getSystemTransferIx, PROGRAM_ID } from "./common"; + +function getRandomSeed() { + const seed = new Uint8Array(32); + crypto.getRandomValues(seed); + return seed; +} + +describe("LazorKit V1 — Execute", () => { + let ctx: TestContext; + + let ownerKeypair: Keypair; + let walletPda: PublicKey; + let vaultPda: PublicKey; + let ownerAuthPda: PublicKey; + // <--- Add highClient + + beforeAll(async () => { + ctx = await setupTest(); + // <--- Initialize + + ownerKeypair = Keypair.generate(); + + const { ix, walletPda: w, authorityPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: ownerKeypair.publicKey, + }); + await sendTx(ctx, [ix]); + walletPda = w; + ownerAuthPda = authorityPda; + + const [v] = findVaultPda(walletPda); + vaultPda = v; + + // Fund vault + const [vPda] = findVaultPda(walletPda); + await sendTx(ctx, [getSystemTransferIx(ctx.payer.publicKey, vPda, 200_000_000n)]); + console.log("Wallet created and funded for execute tests"); + }, 30_000); + + it("Success: Owner executes a transfer", async () => { + const recipient = Keypair.generate().publicKey; + + const ix = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: ownerAuthPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 1_000_000n) + ], + signer: ownerKeypair + }); + await sendTx(ctx, [ix], [ownerKeypair]); + + const balance = await ctx.connection.getBalance(recipient); + expect(balance).toBe(1_000_000); + }, 30_000); + + it("Success: Spender executes a transfer", async () => { + const spender = Keypair.generate(); + const [spenderPda] = findAuthorityPda(walletPda, spender.publicKey.toBytes()); + + const { ix: ixAdd } = await ctx.highClient.addAuthority({ payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: spender.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda + }); + await sendTx(ctx, [ixAdd], [ownerKeypair]); + + const recipient = Keypair.generate().publicKey; + + // Execute using spender key + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: spenderPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 1_000_000n) + ], + signer: spender + }); + await sendTx(ctx, [executeIx], [spender]); + + const balance = await ctx.connection.getBalance(recipient); + expect(balance).toBe(1_000_000); + }, 30_000); + + it("Success: Session key executes a transfer", async () => { + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletPda, sessionKey.publicKey); + + const { ix: ixCreate } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + sessionKey: sessionKey.publicKey, + expiresAt: BigInt(2 ** 62), + walletPda + }); + await sendTx(ctx, [ixCreate], [ownerKeypair]); + + const recipient = Keypair.generate().publicKey; + + // Execute using session key + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: sessionPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 1_000_000n) + ], + signer: sessionKey + }); + await sendTx(ctx, [executeIx], [sessionKey]); + + const balance = await ctx.connection.getBalance(recipient); + expect(balance).toBe(1_000_000); + }, 30_000); + + it("Success: Secp256r1 Admin executes a transfer", async () => { + const { generateMockSecp256r1Signer, buildAuthPayload, buildSecp256r1Message, buildSecp256r1PrecompileIx, buildAuthenticatorData, readCurrentSlot, appendSecp256r1Sysvars } = await import("./secp256r1Utils"); + const { computeAccountsHash } = await import("@lazorkit/solita-client"); + const secpAdmin = await generateMockSecp256r1Signer(); + const [secpAdminPda] = findAuthorityPda(walletPda, secpAdmin.credentialIdHash); + + // Add Secp256r1 Admin + const { ix: ixAddSecp } = await ctx.highClient.addAuthority({ payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: secpAdmin.publicKeyBytes, + newAuthType: AuthType.Secp256r1, + role: Role.Admin, + walletPda, + newCredentialHash: secpAdmin.credentialIdHash + }); + await sendTx(ctx, [ixAddSecp], [ownerKeypair]); + + const recipient = Keypair.generate().publicKey; + const innerInstructions = [ + getSystemTransferIx(vaultPda, recipient, 2_000_000n) + ]; + + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: secpAdminPda, + innerInstructions, + }); + + // Append sysvars via SDK helper + const { ix: ixWithSysvars, sysvarIxIndex, sysvarSlotIndex } = appendSecp256r1Sysvars(executeIx); + + const argsDataExecute = ixWithSysvars.data.subarray(1); // after discriminator + + // Read slot via SDK + const currentSlot = await readCurrentSlot(ctx.connection); + + // Build auth payload via SDK + const authenticatorData = await buildAuthenticatorData("example.com"); + const authPayload = buildAuthPayload({ + sysvarIxIndex, + sysvarSlotIndex, + authenticatorData, + slot: currentSlot, + }); + + // Compute accounts hash using SDK function + // Build compact instructions to match what buildExecute produced + const compactIxs = [{ + programIdIndex: ixWithSysvars.keys.findIndex(k => k.pubkey.equals(SystemProgram.programId)), + accountIndexes: [ + ixWithSysvars.keys.findIndex(k => k.pubkey.equals(vaultPda)), + ixWithSysvars.keys.findIndex(k => k.pubkey.equals(recipient)), + ], + data: innerInstructions[0].data, + }]; + const accountsHash = await computeAccountsHash(ixWithSysvars.keys, compactIxs); + + // signedPayload: compact_instructions + accounts_hash + const signedPayload = new Uint8Array(argsDataExecute.length + 32); + signedPayload.set(argsDataExecute, 0); + signedPayload.set(accountsHash, argsDataExecute.length); + + // Build message via SDK + const msgToSign = await buildSecp256r1Message({ + discriminator: 4, // Execute + authPayload, + signedPayload, + payer: ctx.payer.publicKey, + programId: PROGRAM_ID, + slot: currentSlot, + }); + + // Build precompile instruction via SDK + const sysvarIx = await buildSecp256r1PrecompileIx(secpAdmin, msgToSign); + + // Pack the payload into executeIx.data + const finalExecuteData = new Uint8Array(1 + argsDataExecute.length + authPayload.length); + finalExecuteData[0] = 4; // discriminator + finalExecuteData.set(argsDataExecute, 1); + finalExecuteData.set(authPayload, 1 + argsDataExecute.length); + ixWithSysvars.data = Buffer.from(finalExecuteData); + + const result = await tryProcessInstructions(ctx, [sysvarIx, ixWithSysvars]); + expect(result.result).toBe("ok"); + + const balance = await ctx.connection.getBalance(recipient); + expect(balance).toBe(2_000_000); + }, 30_000); + + it("Failure: Session expired", async () => { + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletPda, sessionKey.publicKey); + + // Create session that is immediately expired (slot 0 or far past) + const { ix: ixCreate } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + sessionKey: sessionKey.publicKey, + expiresAt: 0n, + walletPda + }); + await sendTx(ctx, [ixCreate], [ownerKeypair]); + + const recipient = Keypair.generate().publicKey; + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: sessionPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 100n) + ], + signer: sessionKey + }); + + const result = await tryProcessInstruction(ctx, [executeIx], [sessionKey]); + expect(result.result).toMatch(/3009|0xbc1|simulation failed/i); + }, 30_000); + + it("Failure: Unauthorized signatory", async () => { + const thief = Keypair.generate(); + const recipient = Keypair.generate().publicKey; + + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: ownerAuthPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient, 100n) + ], + signer: thief + }); + + const result = await tryProcessInstruction(ctx, [executeIx], [thief]); + expect(result.result).toMatch(/signature|unauthorized|simulation failed/i); + }, 30_000); + + it("Failure: Authority from Wallet A cannot execute on Wallet B's vault", async () => { + const userSeedB = getRandomSeed(); + const [walletPdaB] = findWalletPda(userSeedB); + const [vaultPdaB] = findVaultPda(walletPdaB); + const ownerB = Keypair.generate(); + const [ownerBAuthPda] = findAuthorityPda(walletPdaB, ownerB.publicKey.toBytes()); + + const { ix: ixCreateB } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: ownerB.publicKey, + userSeed: userSeedB + }); + await sendTx(ctx, [ixCreateB]); + + await sendTx(ctx, [getSystemTransferIx(ctx.payer.publicKey, vaultPdaB, 100_000_000n)]); + + const recipient = Keypair.generate().publicKey; + + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda: walletPdaB, // Target B + authorityPda: ownerAuthPda, // Auth A + innerInstructions: [ + getSystemTransferIx(vaultPdaB, recipient, 1_000_000n) + ], + signer: ownerKeypair + }); + + const result = await tryProcessInstruction(ctx, [executeIx], [ownerKeypair]); + expect(result.result).toMatch(/simulation failed|InvalidAccountData/i); + }, 30_000); + + it("Success: Execute batch — multiple transfers", async () => { + const recipient1 = Keypair.generate().publicKey; + const recipient2 = Keypair.generate().publicKey; + + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: ownerAuthPda, + innerInstructions: [ + getSystemTransferIx(vaultPda, recipient1, 1_000_000n), + getSystemTransferIx(vaultPda, recipient2, 2_000_000n), + ], + signer: ownerKeypair + }); + + await sendTx(ctx, [executeIx], [ownerKeypair]); + + const bal1 = await ctx.connection.getBalance(recipient1); + const bal2 = await ctx.connection.getBalance(recipient2); + expect(bal1).toBe(1_000_000); + expect(bal2).toBe(2_000_000); + }, 30_000); + + it("Success: Execute with empty inner instructions", async () => { + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: ownerAuthPda, + innerInstructions: [], + signer: ownerKeypair + }); + + await sendTx(ctx, [executeIx], [ownerKeypair]); + }, 30_000); + + it("Failure: Execute with wrong vault PDA", async () => { + const fakeVault = Keypair.generate(); + const recipient = Keypair.generate().publicKey; + + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: ownerAuthPda, + innerInstructions: [ + getSystemTransferIx(fakeVault.publicKey, recipient, 1_000_000n) + ], + signer: ownerKeypair, + vaultPda: fakeVault.publicKey + }); + + const result = await tryProcessInstruction(ctx, [executeIx], [ownerKeypair, fakeVault]); + expect(result.result).toMatch(/simulation failed|InvalidSeeds/i); + }, 30_000); + + it("Failure: Secp256r1 execution fails if inner instructions are tampered with (AccountsHash mismatch)", async () => { + const { generateMockSecp256r1Signer, buildAuthPayload, buildSecp256r1Message, buildSecp256r1PrecompileIx, buildAuthenticatorData, readCurrentSlot, appendSecp256r1Sysvars } = await import("./secp256r1Utils"); + const { computeAccountsHash } = await import("@lazorkit/solita-client"); + const secpAdmin = await generateMockSecp256r1Signer(); + + // Add Secp256r1 Admin + const { ix: ixAddSecp } = await ctx.highClient.addAuthority({ payer: ctx.payer, + adminType: AuthType.Ed25519, adminSigner: ownerKeypair, + newAuthPubkey: secpAdmin.publicKeyBytes, newAuthType: AuthType.Secp256r1, + role: Role.Admin, walletPda, newCredentialHash: secpAdmin.credentialIdHash + }); + await sendTx(ctx, [ixAddSecp], [ownerKeypair]); + const [secpAdminPda] = findAuthorityPda(walletPda, secpAdmin.credentialIdHash); + + const recipient = Keypair.generate().publicKey; + + // LEGITIMATE INSTRUCTION + const legitInstructions = [ getSystemTransferIx(vaultPda, recipient, 10_000n) ]; + // MALICIOUS INSTRUCTION (Tampering value or recipient) + const maliciousInstructions = [ getSystemTransferIx(vaultPda, recipient, 50_000_000n) ]; + + const executeIxOriginal = await ctx.highClient.execute({ + payer: ctx.payer, walletPda, authorityPda: secpAdminPda, innerInstructions: legitInstructions, + }); + const { ix: ixWithSysvarsOriginal, sysvarIxIndex, sysvarSlotIndex } = appendSecp256r1Sysvars(executeIxOriginal); + const argsDataExecute = ixWithSysvarsOriginal.data.subarray(1); // after discriminator + + const currentSlot = await readCurrentSlot(ctx.connection); + const authenticatorData = await buildAuthenticatorData("example.com"); + const authPayload = buildAuthPayload({ sysvarIxIndex, sysvarSlotIndex, authenticatorData, slot: currentSlot }); + + // Compute legitimate accounts hash + const compactIxs = [{ + programIdIndex: ixWithSysvarsOriginal.keys.findIndex(k => k.pubkey.equals(SystemProgram.programId)), + accountIndexes: [ + ixWithSysvarsOriginal.keys.findIndex(k => k.pubkey.equals(vaultPda)), + ixWithSysvarsOriginal.keys.findIndex(k => k.pubkey.equals(recipient)), + ], + data: legitInstructions[0].data, + }]; + const legitAccountsHash = await computeAccountsHash(ixWithSysvarsOriginal.keys, compactIxs); + + // SIGN the legit payload + const signedPayload = new Uint8Array(argsDataExecute.length + 32); + signedPayload.set(argsDataExecute, 0); signedPayload.set(legitAccountsHash, argsDataExecute.length); + const msgToSign = await buildSecp256r1Message({ + discriminator: 4, authPayload, signedPayload, payer: ctx.payer.publicKey, programId: PROGRAM_ID, slot: currentSlot, + }); + const sysvarIx = await buildSecp256r1PrecompileIx(secpAdmin, msgToSign); + + // BUILD Malicious transaction + const executeIxMalicious = await ctx.highClient.execute({ + payer: ctx.payer, walletPda, authorityPda: secpAdminPda, innerInstructions: maliciousInstructions, + }); + const { ix: ixWithSysvarsMalicious } = appendSecp256r1Sysvars(executeIxMalicious); + + // Package the legit signature/payload into the malicious execution + const argsDataExecuteMalicious = ixWithSysvarsMalicious.data.subarray(1); + const finalExecuteData = new Uint8Array(1 + argsDataExecuteMalicious.length + authPayload.length); + finalExecuteData[0] = 4; + finalExecuteData.set(argsDataExecuteMalicious, 1); + finalExecuteData.set(authPayload, 1 + argsDataExecuteMalicious.length); + ixWithSysvarsMalicious.data = Buffer.from(finalExecuteData); + + const result = await tryProcessInstructions(ctx, [sysvarIx, ixWithSysvarsMalicious]); + + // Expect failure because the derived hash from the malicious instruction won't match the legit signed hash! + expect(result.result).toMatch(/simulation failed|3004|InstructionDidNotDeserialize|Data/i); + }, 30_000); +}); diff --git a/tests-v1-rpc/tests/05-session.test.ts b/tests-v1-rpc/tests/05-session.test.ts new file mode 100644 index 0000000..a102be0 --- /dev/null +++ b/tests-v1-rpc/tests/05-session.test.ts @@ -0,0 +1,390 @@ +/** + * 05-session.test.ts + * + * Tests for: CreateSession, CloseSession (Ed25519 + Secp256r1) + * Merged from: session.test.ts + close-session tests from cleanup.test.ts + */ + +import { Keypair, PublicKey } from "@solana/web3.js"; +import { describe, it, expect, beforeAll } from "vitest"; +import { + findVaultPda, + findAuthorityPda, + findSessionPda, + SessionAccount, + AuthType, + Role, +} from "@lazorkit/solita-client"; +import { setupTest, sendTx, getRandomSeed, tryProcessInstruction, tryProcessInstructions, type TestContext, getSystemTransferIx, PROGRAM_ID } from "./common"; + +describe("Session Management", () => { + let ctx: TestContext; + let ownerKeypair: Keypair; + let userSeed: Uint8Array; + let walletPda: PublicKey; + let vaultPda: PublicKey; + let ownerAuthPda: PublicKey; + + beforeAll(async () => { + ctx = await setupTest(); + ownerKeypair = Keypair.generate(); + userSeed = getRandomSeed(); + + const { ix, walletPda: w } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: ownerKeypair.publicKey, + userSeed + }); + await sendTx(ctx, [ix]); + walletPda = w; + + const [v] = findVaultPda(walletPda); + vaultPda = v; + + const [oPda] = findAuthorityPda(walletPda, ownerKeypair.publicKey.toBytes()); + ownerAuthPda = oPda; + + await sendTx(ctx, [getSystemTransferIx(ctx.payer.publicKey, vaultPda, 500_000_000n)]); + }, 30_000); + + // ─── Create Session ──────────────────────────────────────────────────────── + + it("Success: Owner creates a session key", async () => { + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletPda, sessionKey.publicKey); + + const { ix } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + sessionKey: sessionKey.publicKey, + expiresAt: 999999999n, + walletPda + }); + + await sendTx(ctx, [ix], [ownerKeypair]); + + const sessionAccInfo = await ctx.connection.getAccountInfo(sessionPda); + expect(sessionAccInfo).not.toBeNull(); + + // Verification of data mapping using Solita classes + const sessionAcc = await SessionAccount.fromAccountAddress(ctx.connection, sessionPda); + expect(sessionAcc.sessionKey.toBase58()).toBe(sessionKey.publicKey.toBase58()); + expect(sessionAcc.wallet.toBase58()).toBe(walletPda.toBase58()); + expect(sessionAcc.expiresAt.toString()).toBe("999999999"); + }, 30_000); + + it("Success: Execution using session key", async () => { + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletPda, sessionKey.publicKey); + + const { ix: createIx } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + sessionKey: sessionKey.publicKey, + expiresAt: BigInt(2 ** 62), + walletPda + }); + await sendTx(ctx, [createIx], [ownerKeypair]); + + const recipient = Keypair.generate().publicKey; + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: sessionPda, + innerInstructions: [getSystemTransferIx(vaultPda, recipient, 1_000_000n)], + signer: sessionKey + }); + await sendTx(ctx, [executeIx], [sessionKey]); + + const balance = await ctx.connection.getBalance(recipient); + expect(balance).toBe(1_000_000); + }, 30_000); + + it("Failure: Spender cannot create session", async () => { + const spender = Keypair.generate(); + const { ix: ixAdd } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: spender.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + walletPda + }); + await sendTx(ctx, [ixAdd], [ownerKeypair]); + + const sessionKey = Keypair.generate(); + const { ix: ixFail } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: spender, + sessionKey: sessionKey.publicKey, + expiresAt: BigInt(2 ** 62), + walletPda + }); + + const result = await tryProcessInstruction(ctx, [ixFail], [spender]); + expect(result.result).toMatch(/simulation failed|3002|0xbba/i); + }, 30_000); + + it("Failure: Session PDA cannot create another session", async () => { + const sessionKey1 = Keypair.generate(); + const [sessionPda1] = findSessionPda(walletPda, sessionKey1.publicKey); + + const { ix: ixCreate1 } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + sessionKey: sessionKey1.publicKey, + expiresAt: BigInt(2 ** 62), + walletPda + }); + await sendTx(ctx, [ixCreate1], [ownerKeypair]); + + const sessionKey2 = Keypair.generate(); + const [sessionPda2] = findSessionPda(walletPda, sessionKey2.publicKey); + + const configPda = ctx.highClient.getConfigPda(); + const shardId = ctx.payer.publicKey.toBytes().reduce((a: number, b: number) => a + b, 0) % 16; + const treasuryShard = ctx.highClient.getTreasuryShardPda(shardId); + + const ix = ctx.highClient.builder.createSession({ + payer: ctx.payer.publicKey, + wallet: walletPda, + adminAuthority: sessionPda1, + session: sessionPda2, + config: configPda, + treasuryShard: treasuryShard, + sessionKey: Array.from(sessionKey2.publicKey.toBytes()), + expiresAt: BigInt(2 ** 62), + authorizerSigner: sessionKey1.publicKey, + }); + + const result = await tryProcessInstruction(ctx, ix, [sessionKey1]); + expect(result.result).toMatch(/simulation failed|InvalidAccountData/i); + }, 30_000); + + it("Failure: Session key cannot add authority", async () => { + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletPda, sessionKey.publicKey); + + const { ix: ixCreate } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + sessionKey: sessionKey.publicKey, + expiresAt: BigInt(2 ** 62), + walletPda + }); + await sendTx(ctx, [ixCreate], [ownerKeypair]); + + const newUser = Keypair.generate(); + + const { ix } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + walletPda: walletPda, + newAuthPubkey: newUser.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: Role.Spender, + adminType: AuthType.Ed25519, + adminSigner: sessionKey as any, + adminAuthorityPda: sessionPda + }); + + const result = await tryProcessInstruction(ctx, ix, [sessionKey]); + expect(result.result).toMatch(/simulation failed|InvalidAccountData|0x1770/i); + }, 30_000); + + it("Success: Secp256r1 Admin creates a session", async () => { + const { generateMockSecp256r1Signer, buildAuthPayload, buildSecp256r1Message, buildSecp256r1PrecompileIx, buildAuthenticatorData, readCurrentSlot, appendSecp256r1Sysvars } = await import("./secp256r1Utils"); + const secpAdmin = await generateMockSecp256r1Signer(); + const [secpAdminPda] = findAuthorityPda(walletPda, secpAdmin.credentialIdHash); + + const { ix: ixAddSecp } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: ownerKeypair, + newAuthPubkey: secpAdmin.publicKeyBytes, + newAuthType: AuthType.Secp256r1, + role: Role.Admin, + walletPda, + newCredentialHash: secpAdmin.credentialIdHash + }); + await sendTx(ctx, [ixAddSecp], [ownerKeypair]); + + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletPda, sessionKey.publicKey); + const expiresAt = 999999999n; + + const { ix: createSessionIx } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Secp256r1, + adminCredentialHash: secpAdmin.credentialIdHash, + sessionKey: sessionKey.publicKey, + expiresAt, + walletPda + }); + + const adminMeta = createSessionIx.keys.find(k => k.pubkey.equals(secpAdminPda)); + if (adminMeta) adminMeta.isWritable = true; + + const { ix: ixWithSysvars, sysvarIxIndex, sysvarSlotIndex } = appendSecp256r1Sysvars(createSessionIx); + const currentSlot = await readCurrentSlot(ctx.connection); + + const authenticatorData = await buildAuthenticatorData("example.com"); + const authPayload = buildAuthPayload({ sysvarIxIndex, sysvarSlotIndex, authenticatorData, slot: currentSlot }); + + const signedPayload = new Uint8Array(32 + 8 + 32 + 32); + signedPayload.set(sessionKey.publicKey.toBytes(), 0); + new DataView(signedPayload.buffer).setBigUint64(32, expiresAt, true); + signedPayload.set(ctx.payer.publicKey.toBytes(), 40); + signedPayload.set(walletPda.toBytes(), 72); + + const msgToSign = await buildSecp256r1Message({ + discriminator: 5, + authPayload, signedPayload, + payer: ctx.payer.publicKey, + programId: PROGRAM_ID, + slot: currentSlot, + }); + + const sysvarIx = await buildSecp256r1PrecompileIx(secpAdmin, msgToSign); + + const newIxData = Buffer.alloc(ixWithSysvars.data.length + authPayload.length); + ixWithSysvars.data.copy(newIxData, 0); + newIxData.set(authPayload, ixWithSysvars.data.length); + ixWithSysvars.data = newIxData; + + const result = await tryProcessInstructions(ctx, [sysvarIx, ixWithSysvars]); + expect(result.result).toBe("ok"); + + const sessionAcc = await ctx.connection.getAccountInfo(sessionPda); + expect(sessionAcc).not.toBeNull(); + }, 30_000); + + it("Failure: Execution fails if the session has been closed", async () => { + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletPda, sessionKey.publicKey); + + const { ix: createIx } = await ctx.highClient.createSession({ + payer: ctx.payer, adminType: AuthType.Ed25519, adminSigner: ownerKeypair, sessionKey: sessionKey.publicKey, expiresAt: 9999999999n, walletPda + }); + await sendTx(ctx, [createIx], [ownerKeypair]); + + // Close the session immediately + const closeIx = await ctx.highClient.closeSession({ + payer: ctx.payer, walletPda, sessionPda, authorizer: { authorizerPda: ownerAuthPda, signer: ownerKeypair } + }); + await sendTx(ctx, [closeIx], [ownerKeypair]); + + const recipient = Keypair.generate().publicKey; + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, walletPda, authorityPda: sessionPda, innerInstructions: [getSystemTransferIx(vaultPda, recipient, 100n)], signer: sessionKey + }); + + const result = await tryProcessInstruction(ctx, [executeIx], [sessionKey]); + expect(result.result).toMatch(/simulation failed|UninitializedAccount|InvalidAccountData|0xbc4/i); + }, 30_000); + + // ─── Close Session ───────────────────────────────────────────────────────── + + it("CloseSession: wallet owner closes an active session", async () => { + const owner = Keypair.generate(); + const { ix: ixCreate, walletPda: wPda, authorityPda: oAuthPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey + }); + await sendTx(ctx, [ixCreate]); + + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(wPda, sessionKey.publicKey); + + const { ix: ixCreateSession } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: owner, + sessionKey: sessionKey.publicKey, + expiresAt: BigInt(Math.floor(Date.now() / 1000) + 3600), + walletPda: wPda + }); + await sendTx(ctx, [ixCreateSession], [owner]); + + const closeSessionIx = await ctx.highClient.closeSession({ + payer: ctx.payer, + walletPda: wPda, + sessionPda, + authorizer: { authorizerPda: oAuthPda, signer: owner } + }); + + const result = await tryProcessInstructions(ctx, [closeSessionIx], [ctx.payer, owner]); + expect(result.result).toBe("ok"); + }); + + it("CloseSession: cranker (anyone) closes an expired session", async () => { + const owner = Keypair.generate(); + const { ix: ixCreate, walletPda: wPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey + }); + await sendTx(ctx, [ixCreate]); + + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(wPda, sessionKey.publicKey); + + const { ix: ixCreateSession } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: owner, + sessionKey: sessionKey.publicKey, + expiresAt: 0n, + walletPda: wPda + }); + await sendTx(ctx, [ixCreateSession], [owner]); + + const closeSessionIx = await ctx.highClient.closeSession({ + payer: ctx.payer, + walletPda: wPda, + sessionPda, + }); + + const result = await tryProcessInstructions(ctx, [closeSessionIx], [ctx.payer]); + expect(result.result).toBe("ok"); + }); + + it("CloseSession: rejects cranker (anyone) closing an active session", async () => { + const owner = Keypair.generate(); + const { ix: ixCreate, walletPda: wPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey + }); + await sendTx(ctx, [ixCreate]); + + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(wPda, sessionKey.publicKey); + + const { ix: ixCreateSession } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: owner, + sessionKey: sessionKey.publicKey, + expiresAt: BigInt(Math.floor(Date.now() / 1000) + 3600), + walletPda: wPda + }); + await sendTx(ctx, [ixCreateSession], [owner]); + + const closeSessionIx = await ctx.highClient.closeSession({ + payer: ctx.payer, + walletPda: wPda, + sessionPda, + }); + + const result = await tryProcessInstructions(ctx, [closeSessionIx], [ctx.payer]); + expect(result.result).not.toBe("ok"); + }); +}); diff --git a/tests-v1-rpc/tests/06-ownership.test.ts b/tests-v1-rpc/tests/06-ownership.test.ts new file mode 100644 index 0000000..ec56417 --- /dev/null +++ b/tests-v1-rpc/tests/06-ownership.test.ts @@ -0,0 +1,345 @@ +/** + * 06-ownership.test.ts + * + * Tests for: TransferOwnership (Ed25519 + Secp256r1), CloseWallet + * Merged from: wallet.test.ts (transfer ownership tests) + cleanup.test.ts (close wallet tests) + */ + +import { Keypair, PublicKey } from "@solana/web3.js"; +import { describe, it, expect, beforeAll } from "vitest"; +import { + findWalletPda, + findVaultPda, + findAuthorityPda, + AuthorityAccount, + AuthType, + Role, +} from "@lazorkit/solita-client"; +import { setupTest, sendTx, getRandomSeed, tryProcessInstruction, tryProcessInstructions, type TestContext, getSystemTransferIx, PROGRAM_ID } from "./common"; +import { generateMockSecp256r1Signer, buildSecp256r1PrecompileIx, buildAuthPayload, buildSecp256r1Message, buildAuthenticatorData, readCurrentSlot, appendSecp256r1Sysvars } from "./secp256r1Utils"; + +describe("Ownership & Wallet Lifecycle", () => { + let ctx: TestContext; + let ownerKeypair: Keypair; + let walletPda: PublicKey; + let ownerAuthPda: PublicKey; + + beforeAll(async () => { + ctx = await setupTest(); + ownerKeypair = Keypair.generate(); + + const { ix, walletPda: w, authorityPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: ownerKeypair.publicKey, + }); + await sendTx(ctx, [ix]); + walletPda = w; + ownerAuthPda = authorityPda; + }, 30_000); + + // ─── Transfer Ownership ──────────────────────────────────────────────────── + + it("Transfer ownership (Ed25519 → Ed25519)", async () => { + const userSeed = getRandomSeed(); + const o = Keypair.generate(); + const { ix, walletPda: wPda, authorityPda: oPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: o.publicKey, + userSeed + }); + await sendTx(ctx, [ix]); + + const newOwner = Keypair.generate(); + const [newAuthPda] = findAuthorityPda(wPda, newOwner.publicKey.toBytes()); + + const transferIx = await ctx.highClient.transferOwnership({ + payer: ctx.payer, + walletPda: wPda, + currentOwnerAuthority: oPda, + newOwnerAuthority: newAuthPda, + newAuthType: AuthType.Ed25519, + newAuthPubkey: newOwner.publicKey.toBytes(), + signer: o + }); + await sendTx(ctx, [transferIx], [o]); + + const acc = await AuthorityAccount.fromAccountAddress(ctx.connection, newAuthPda); + expect(acc.role).toBe(0); + }, 30_000); + + it("Failure: Admin cannot transfer ownership", async () => { + const userSeed = getRandomSeed(); + const o = Keypair.generate(); + const admin = Keypair.generate(); + + const { ix, walletPda: wPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: o.publicKey, + userSeed + }); + await sendTx(ctx, [ix]); + + const { ix: ixAdd } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: o, + newAuthPubkey: admin.publicKey.toBytes(), + newAuthType: AuthType.Ed25519, + role: 1, + walletPda: wPda + }); + await sendTx(ctx, [ixAdd], [o]); + + const [adminPda] = findAuthorityPda(wPda, admin.publicKey.toBytes()); + const newOwner = Keypair.generate(); + const [newAuthPda] = findAuthorityPda(wPda, newOwner.publicKey.toBytes()); + + const transferIx = await ctx.highClient.transferOwnership({ + payer: ctx.payer, + walletPda: wPda, + currentOwnerAuthority: adminPda, + newOwnerAuthority: newAuthPda, + newAuthType: AuthType.Ed25519, + newAuthPubkey: newOwner.publicKey.toBytes(), + signer: admin + }); + + const result = await tryProcessInstruction(ctx, [transferIx], [admin]); + expect(result.result).toMatch(/simulation failed|3002|0xbba|PermissionDenied/i); + }, 30_000); + + it("Failure: Cannot transfer ownership to zero address", async () => { + const userSeed = getRandomSeed(); + const o = Keypair.generate(); + const { ix, walletPda: wPda, authorityPda: oPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: o.publicKey, + userSeed + }); + await sendTx(ctx, [ix]); + + const zeroKey = new Uint8Array(32); + const [zeroPda] = findAuthorityPda(wPda, zeroKey); + + const transferIx = await ctx.highClient.transferOwnership({ + payer: ctx.payer, + walletPda: wPda, + currentOwnerAuthority: oPda, + newOwnerAuthority: zeroPda, + newAuthType: AuthType.Ed25519, + newAuthPubkey: zeroKey, + signer: o + }); + + const result = await tryProcessInstruction(ctx, [transferIx], [o]); + expect(result.result).toMatch(/simulation failed|InvalidArgument|zero|0x0/i); + }, 30_000); + + it("After transfer ownership, old owner account is closed", async () => { + const userSeed = getRandomSeed(); + const o = Keypair.generate(); + const { ix, walletPda: wPda, authorityPda: oPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: o.publicKey, + userSeed + }); + await sendTx(ctx, [ix]); + + const newOwner = Keypair.generate(); + const [newAuthPda] = findAuthorityPda(wPda, newOwner.publicKey.toBytes()); + + const transferIx = await ctx.highClient.transferOwnership({ + payer: ctx.payer, + walletPda: wPda, + currentOwnerAuthority: oPda, + newOwnerAuthority: newAuthPda, + newAuthType: AuthType.Ed25519, + newAuthPubkey: newOwner.publicKey.toBytes(), + signer: o + }); + await sendTx(ctx, [transferIx], [o]); + + const oldAuthInfo = await ctx.connection.getAccountInfo(oPda); + expect(oldAuthInfo).toBeNull(); + }, 30_000); + + it("Secp256r1 Owner transfers ownership to Ed25519", async () => { + const userSeed = getRandomSeed(); + const [wPda] = findWalletPda(userSeed); + + const secpOwner = await generateMockSecp256r1Signer(); + const [secpOwnerPda] = findAuthorityPda(wPda, secpOwner.credentialIdHash); + + const { ix: createIx } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Secp256r1, + pubkey: secpOwner.publicKeyBytes, + credentialHash: secpOwner.credentialIdHash, + userSeed + }); + await sendTx(ctx, [createIx]); + + const newOwner = Keypair.generate(); + const newOwnerBytes = newOwner.publicKey.toBytes(); + const [newAuthPda] = findAuthorityPda(wPda, newOwnerBytes); + + const transferIx = await ctx.highClient.transferOwnership({ + payer: ctx.payer, + walletPda: wPda, + currentOwnerAuthority: secpOwnerPda, + newOwnerAuthority: newAuthPda, + newAuthType: AuthType.Ed25519, + newAuthPubkey: newOwnerBytes, + }); + + const { ix: ixWithSysvars, sysvarIxIndex, sysvarSlotIndex } = appendSecp256r1Sysvars(transferIx); + ixWithSysvars.keys.push( + { pubkey: new PublicKey("SysvarRent111111111111111111111111111111111"), isSigner: false, isWritable: false }, + ); + + const currentSlot = await readCurrentSlot(ctx.connection); + const authenticatorData = await buildAuthenticatorData("example.com"); + const authPayload = buildAuthPayload({ sysvarIxIndex, sysvarSlotIndex, authenticatorData, slot: currentSlot }); + + const signedPayload = new Uint8Array(1 + 32 + 32 + 32); + signedPayload[0] = 0; + signedPayload.set(newOwnerBytes, 1); + signedPayload.set(ctx.payer.publicKey.toBytes(), 33); + signedPayload.set(wPda.toBytes(), 65); + + const msgToSign = await buildSecp256r1Message({ + discriminator: 3, + authPayload, signedPayload, + payer: ctx.payer.publicKey, + programId: new PublicKey(PROGRAM_ID), + slot: currentSlot, + }); + + const sysvarIx = await buildSecp256r1PrecompileIx(secpOwner, msgToSign); + const originalData = Buffer.from(ixWithSysvars.data); + + ixWithSysvars.data = Buffer.concat([originalData, Buffer.from(authPayload)]); + + const result = await tryProcessInstructions(ctx, [sysvarIx, ixWithSysvars]); + expect(result.result).toBe("ok"); + + const acc = await AuthorityAccount.fromAccountAddress(ctx.connection, newAuthPda); + expect(acc.role).toBe(0); + }, 30_000); + + // ─── Close Wallet ────────────────────────────────────────────────────────── + + it("CloseWallet: owner closes wallet and sweeps rent", async () => { + const owner = Keypair.generate(); + const userSeed = getRandomSeed(); + + const { ix, walletPda: wPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey, + userSeed + }); + await sendTx(ctx, [ix]); + + const [vPda] = findVaultPda(wPda); + await sendTx(ctx, [getSystemTransferIx(ctx.payer.publicKey, vPda, 25_000_000n)]); + + const destWallet = Keypair.generate(); + const closeIx = await ctx.highClient.closeWallet({ + payer: ctx.payer, + walletPda: wPda, + destination: destWallet.publicKey, + adminType: AuthType.Ed25519, + adminSigner: owner + }); + await sendTx(ctx, [closeIx], [owner]); + + const destBalance = await ctx.connection.getBalance(destWallet.publicKey); + expect(destBalance).toBeGreaterThan(25_000_000); + }); + + it("CloseWallet: rejects Admin closing wallet", async () => { + const owner = Keypair.generate(); + const admin = Keypair.generate(); + const { ix: ixCreate, walletPda: wPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey + }); + await sendTx(ctx, [ixCreate]); + + const { ix: ixAddAdmin } = await ctx.highClient.addAuthority({ + payer: ctx.payer, walletPda: wPda, + adminType: AuthType.Ed25519, adminSigner: owner, + newAuthPubkey: admin.publicKey.toBytes(), newAuthType: AuthType.Ed25519, role: Role.Admin + }); + await sendTx(ctx, [ixAddAdmin], [owner]); + + const closeWalletIx = await ctx.highClient.closeWallet({ + payer: ctx.payer, + walletPda: wPda, + destination: ctx.payer.publicKey, + adminType: AuthType.Ed25519, + adminSigner: admin // Try closing with Valid Admin! + }); + + const result = await tryProcessInstructions(ctx, [closeWalletIx], [admin]); + expect(result.result).toMatch(/simulation failed|3002|0xbba|PermissionDenied/i); + }); + + it("Failure: Cannot transfer ownership to an already existing authority", async () => { + const o = Keypair.generate(); + const { ix, walletPda: wPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, authType: AuthType.Ed25519, owner: o.publicKey + }); + await sendTx(ctx, [ix]); + + const spender = Keypair.generate(); + const { ix: ixAdd } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, adminSigner: o, + newAuthPubkey: spender.publicKey.toBytes(), newAuthType: AuthType.Ed25519, role: Role.Spender, walletPda: wPda + }); + await sendTx(ctx, [ixAdd], [o]); + + const [oPda] = findAuthorityPda(wPda, o.publicKey.toBytes()); + const [spenderPda] = findAuthorityPda(wPda, spender.publicKey.toBytes()); + + const transferIx = await ctx.highClient.transferOwnership({ + payer: ctx.payer, walletPda: wPda, + currentOwnerAuthority: oPda, newOwnerAuthority: spenderPda, + newAuthType: AuthType.Ed25519, newAuthPubkey: spender.publicKey.toBytes(), signer: o + }); + + const result = await tryProcessInstruction(ctx, [transferIx], [o]); + expect(result.result).toMatch(/simulation failed|already in use|AccountAlreadyInitialized/i); + }); + + it("CloseWallet: rejects if destination is the vault PDA", async () => { + const owner = Keypair.generate(); + const { ix: ixCreate, walletPda: wPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey, + }); + await sendTx(ctx, [ixCreate]); + + const [vaultPda_] = findVaultPda(wPda); + + const closeWalletIx = await ctx.highClient.closeWallet({ + payer: ctx.payer, + walletPda: wPda, + destination: vaultPda_, + adminType: AuthType.Ed25519, + adminSigner: owner + }); + + const result = await tryProcessInstructions(ctx, [closeWalletIx], [ctx.payer, owner]); + expect(result.result).not.toBe("ok"); + }); +}); diff --git a/tests-v1-rpc/tests/07-security.test.ts b/tests-v1-rpc/tests/07-security.test.ts new file mode 100644 index 0000000..4bc24cc --- /dev/null +++ b/tests-v1-rpc/tests/07-security.test.ts @@ -0,0 +1,228 @@ +/** + * 07-security.test.ts + * + * Tests for: Security checklist gaps + Audit regression suite + * Merged from: security_checklist.test.ts + audit_regression.test.ts + */ + +import { expect, describe, it, beforeAll } from "vitest"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { setupTest, sendTx, tryProcessInstructions, type TestContext, getSystemTransferIx, PROGRAM_ID } from "./common"; +import { + findVaultPda, + findSessionPda, + AuthType, +} from "@lazorkit/solita-client"; + +describe("Security & Audit Regression", () => { + let ctx: TestContext; + let walletPda: PublicKey; + let vaultPda: PublicKey; + let owner: Keypair; + let ownerAuthPda: PublicKey; + + beforeAll(async () => { + ctx = await setupTest(); + owner = Keypair.generate(); + + const { ix: ixCreate, walletPda: w, authorityPda } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey, + }); + await sendTx(ctx, [ixCreate]); + walletPda = w; + ownerAuthPda = authorityPda; + + const [v] = findVaultPda(walletPda); + vaultPda = v; + + await sendTx(ctx, [getSystemTransferIx(ctx.payer.publicKey, vaultPda, 100_000_000n)]); + }, 30_000); + + // ─── Security Checklist ──────────────────────────────────────────────────── + + it("CreateSession rejects System Program spoofing", async () => { + const sessionKey = Keypair.generate(); + + const { ix } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: owner, + sessionKey: sessionKey.publicKey, + expiresAt: 999999999n, + walletPda + }); + + const spoofedSystemProgram = Keypair.generate().publicKey; + ix.keys = ix.keys.map((k: any, i: number) => + i === 4 ? { ...k, pubkey: spoofedSystemProgram } : k + ); + + const result = await tryProcessInstructions(ctx, [ix], [ctx.payer, owner]); + expect(result.result).not.toBe("ok"); + }); + + it("CloseSession: protocol admin cannot close an active session without wallet auth", async () => { + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletPda, sessionKey.publicKey); + + const { ix: ixCreateSession } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: owner, + sessionKey: sessionKey.publicKey, + expiresAt: BigInt(2 ** 62), + walletPda + }); + await sendTx(ctx, [ixCreateSession], [owner]); + + const closeIx = await ctx.highClient.closeSession({ + payer: ctx.payer, + walletPda, + sessionPda, + }); + + const result = await tryProcessInstructions(ctx, [closeIx], [ctx.payer]); + expect(result.result).not.toBe("ok"); + }); + + // ─── Audit Regression ───────────────────────────────────────────────────── + + it("Failure: Unauthorized user cannot sweep protocol treasury", async () => { + const thief = Keypair.generate(); + const pubkeyBytes = thief.publicKey.toBytes(); + const shardId = pubkeyBytes.reduce((a: number, b: number) => a + b, 0) % 16; + + const sweepIx = await ctx.highClient.sweepTreasury({ + admin: thief, // Thief trying to pretend to be the Protocol Admin + destination: thief.publicKey, + shardId, + }); + + const result = await tryProcessInstructions(ctx, [sweepIx], [thief]); + expect(result.result).toMatch(/simulation failed|ConstraintHasOne|PermissionDenied|0x7d1|0x7dc/i); + }); + + it("Regression: SweepTreasury preserves rent-exemption and remains operational", async () => { + const pubkeyBytes = ctx.payer.publicKey.toBytes(); + const sum = pubkeyBytes.reduce((a: number, b: number) => a + b, 0); + const shardId = sum % 16; + + const sweepIx = await ctx.highClient.sweepTreasury({ + admin: ctx.payer, + destination: ctx.payer.publicKey, + shardId, + }); + await sendTx(ctx, [sweepIx]); + + const shardInfo = await ctx.connection.getAccountInfo(ctx.treasuryShard); + const RENT_EXEMPT_MIN = await ctx.connection.getMinimumBalanceForRentExemption(shardInfo!.data.length); + const postSweepBalance = await ctx.connection.getBalance(ctx.treasuryShard); + expect(postSweepBalance).toBeGreaterThanOrEqual(RENT_EXEMPT_MIN); + + // Operationality Check + const recipient = Keypair.generate().publicKey; + const executeIx = await ctx.highClient.execute({ + payer: ctx.payer, + walletPda, + authorityPda: ownerAuthPda, + innerInstructions: [getSystemTransferIx(vaultPda, recipient, BigInt(RENT_EXEMPT_MIN))], + signer: owner + }); + await sendTx(ctx, [executeIx], [owner]); + + const configInfo = await ctx.connection.getAccountInfo(ctx.configPda); + const actionFee = configInfo!.data.readBigUInt64LE(48); + + const finalBalance = await ctx.connection.getBalance(ctx.treasuryShard); + expect(finalBalance).toBeGreaterThanOrEqual(postSweepBalance + Number(actionFee)); + }); + + it("Regression: CloseWallet rejects self-transfer to prevent burn", async () => { + const closeIx = await ctx.highClient.closeWallet({ + payer: ctx.payer, + walletPda, + destination: vaultPda, + adminType: AuthType.Ed25519, + adminSigner: owner + }); + + const result = await tryProcessInstructions(ctx, [closeIx], [ctx.payer, owner]); + expect(result.result).not.toBe("ok"); + }); + + it("Regression: CloseSession rejects Config PDA spoofing", async () => { + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletPda, sessionKey.publicKey); + + const { ix: ixCreateSession } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: owner, + sessionKey: sessionKey.publicKey, + expiresAt: BigInt(2 ** 62), + walletPda + }); + await sendTx(ctx, [ixCreateSession], [owner]); + + const [fakeConfigPda] = await PublicKey.findProgramAddress( + [Buffer.from("fake_config")], + ctx.payer.publicKey + ); + + const closeSessionIx = await ctx.highClient.closeSession({ + payer: ctx.payer, + walletPda, + sessionPda, + configPda: fakeConfigPda, + authorizer: { authorizerPda: ownerAuthPda, signer: owner } + }); + + const result = await tryProcessInstructions(ctx, [closeSessionIx], [ctx.payer, owner]); + expect(result.result).not.toBe("ok"); + }); + + it("Regression: No protocol fees on cleanup instructions", async () => { + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletPda, sessionKey.publicKey); + + const { ix: ixCreateSession } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: owner, + sessionKey: sessionKey.publicKey, + expiresAt: BigInt(2 ** 62), + walletPda + }); + await sendTx(ctx, [ixCreateSession], [owner]); + + const payerBalanceBefore = await ctx.connection.getBalance(ctx.payer.publicKey); + + const closeSessionIx = await ctx.highClient.closeSession({ + payer: ctx.payer, + walletPda, + sessionPda, + authorizer: { authorizerPda: ownerAuthPda, signer: owner } + }); + // This transaction shouldn't charge the 1000 lamports action fee + const txId1 = await sendTx(ctx, [closeSessionIx], [owner]); + + const closeWalletIx = await ctx.highClient.closeWallet({ + payer: ctx.payer, + walletPda, + destination: ctx.payer.publicKey, + adminType: AuthType.Ed25519, + adminSigner: owner + }); + // This transaction shouldn't charge the 10000 lamports action fee + const txId2 = await sendTx(ctx, [closeWalletIx], [owner]); + + const payerBalanceAfter = await ctx.connection.getBalance(ctx.payer.publicKey); + + // The only cost should be the 2 transaction signature fees (usually 5000 lamports each) + // plus potential rent refunds. We can just verify the payer didn't lose more than network fees. + const expectedMaxCost = 15000; + expect(payerBalanceBefore - payerBalanceAfter).toBeLessThan(expectedMaxCost); + }); +}); diff --git a/tests-v1-rpc/tests/08-security-config.test.ts b/tests-v1-rpc/tests/08-security-config.test.ts new file mode 100644 index 0000000..061efec --- /dev/null +++ b/tests-v1-rpc/tests/08-security-config.test.ts @@ -0,0 +1,267 @@ +/** + * 09-security-config.test.ts + * + * Demonstrates the critical security flaws found in the Config & Fee audit: + * 1. Cross-Wallet Signature Replay on CreateSession (and others) + * 2. CloseSession Denial of Service for Secp256r1 Passkeys + */ + +import { Keypair, PublicKey } from '@solana/web3.js'; +import { describe, it, expect, beforeAll } from 'vitest'; +import { + findAuthorityPda, + findVaultPda, + findSessionPda, + AuthType, + Role, +} from '@lazorkit/solita-client'; +import { + setupTest, + sendTx, + tryProcessInstruction, + tryProcessInstructions, + type TestContext, + getSystemTransferIx, + PROGRAM_ID, +} from './common'; + +describe('Security Vulnerabilities - Config & Fee Extension', () => { + let ctx: TestContext; + + beforeAll(async () => { + console.log('🔐 Starting Security Vulnerabilities Test Suite...'); + ctx = await setupTest(); + }); + + it('Exploit: Cross-Wallet Replay Attack on CreateSession (Ed25519 Relayer)', async () => { + // Alice creates Wallet A + const alice = Keypair.generate(); + const { + ix: ixA, + walletPda: walletA, + userSeed: walletSeedA, + } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: alice.publicKey, + }); + await sendTx(ctx, [ixA]); + + // Alice creates Wallet B with the SAME key + const { ix: ixB, walletPda: walletB } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: alice.publicKey, + }); + await sendTx(ctx, [ixB]); + + expect(walletA.toBase58()).not.toEqual(walletB.toBase58()); + + // Alice authorizes a session for Wallet A using a Relayer (payer) + const sessionKey = Keypair.generate(); + const relayer = Keypair.generate(); + // pre-fund relayer (generous to cover fees and rent) + await sendTx(ctx, [ + getSystemTransferIx( + ctx.payer.publicKey, + relayer.publicKey, + 100_000_000_000n, + ), + ]); + + // alice authority funding for rent safety + const [aliceAuthPda] = findAuthorityPda(walletA, alice.publicKey.toBytes()); + await sendTx(ctx, [ + getSystemTransferIx(ctx.payer.publicKey, aliceAuthPda, 1_000_000_000n), + ]); + + const vaultPda = findVaultPda(walletA)[0]; + await sendTx(ctx, [ + getSystemTransferIx(ctx.payer.publicKey, vaultPda, 1_000_000_000n), + ]); + + const { ix: createSessionIxA } = await ctx.highClient.createSession({ + payer: relayer, + adminType: AuthType.Ed25519, + adminSigner: alice, // Alice signs the PDA payload + sessionKey: sessionKey.publicKey, + expiresAt: BigInt(Date.now() + 10000000), + walletPda: walletA, + }); + + // The transaction goes through successfully for Wallet A! + try { + const [sessionPdaA] = findSessionPda(walletA, sessionKey.publicKey); + console.log( + 'createSessionIxA keys:', + createSessionIxA.keys.map((k) => ({ + pubkey: k.pubkey.toBase58(), + isWritable: k.isWritable, + isSigner: k.isSigner, + })), + ); + console.log('sessionPdaA:', sessionPdaA.toBase58()); + + const log = await sendTx(ctx, [createSessionIxA], [relayer, alice], { + // skipPreflight: true, + }); + console.log(log); + } catch (e: any) { + console.error('CreateSessionA failed:', e?.logs ?? e?.message ?? e); + throw e; + } + + const [sessionPdaA] = findSessionPda(walletA, sessionKey.publicKey); + const accInfoA = await ctx.connection.getAccountInfo(sessionPdaA); + expect(accInfoA).not.toBeNull(); + + // THE EXPLOIT: + // The Relayer (attacker) takes Alice's signature from `createSessionIxA` + // and replays it exactly on Wallet B, which Alice also owns. + // Because the payload ONLY hashes [payer.key, session_key], it does NOT bind to Wallet A! + const { ix: createSessionIxB } = await ctx.highClient.createSession({ + payer: relayer, // Same relayer + adminType: AuthType.Ed25519, + adminSigner: alice, // We need the structure, but we'll swap the signature + sessionKey: sessionKey.publicKey, // Same session key + expiresAt: BigInt(Date.now() + 10000000), // Same expiry + walletPda: walletB, // DIFFERENT WALLET! + }); + + // We simulate the replay by having Alice sign (since she uses the same key). + // If this was a raw tx interception, the attacker would just take the signature bytes. + // Since Vitest signs using the Keypair, it generates the same valid signature + // that the attacker would have intercepted! + const result = await tryProcessInstruction( + ctx, + [createSessionIxB], + [relayer, alice], + ); + + // This SHOULD FAIL because Alice never intended to authorize Wallet B. + // But because of the vulnerability, it SUCCEEDS! + + const [sessionPdaB] = findSessionPda(walletB, sessionKey.publicKey); + const accInfoB = await ctx.connection.getAccountInfo(sessionPdaB); + + // Expose the vulnerability + expect(accInfoB).not.toBeNull(); + }, 30000); + + it('Bug: CloseSession Passkey DoS', async () => { + const { + generateMockSecp256r1Signer, + buildAuthPayload, + buildSecp256r1Message, + buildSecp256r1PrecompileIx, + buildAuthenticatorData, + readCurrentSlot, + appendSecp256r1Sysvars, + } = await import('./secp256r1Utils'); + + // 1. Setup Wallet with Ed25519 owner (Alice) + const alice = Keypair.generate(); + const { + ix: ixA, + walletPda: walletA, + authorityPda: aliceAuthPda, + } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: alice.publicKey, + }); + await sendTx(ctx, [ixA]); + + // 2. Alice adds a Secp256r1 Admin (Bob) + const bob = await generateMockSecp256r1Signer(); + const [bobAuthPda] = findAuthorityPda(walletA, bob.credentialIdHash); + const { ix: ixAddSecp } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: alice, + newAuthPubkey: bob.publicKeyBytes, + newAuthType: AuthType.Secp256r1, + role: Role.Admin, + walletPda: walletA, + newCredentialHash: bob.credentialIdHash, + }); + await sendTx(ctx, [ixAddSecp], [alice]); + + // 3. Create a Session account + const sessionKey = Keypair.generate(); + const [sessionPda] = findSessionPda(walletA, sessionKey.publicKey); + const { ix: ixCreateSession } = await ctx.highClient.createSession({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: alice, + sessionKey: sessionKey.publicKey, + walletPda: walletA, + }); + await sendTx(ctx, [ixCreateSession], [alice]); + + // 4. Bob (Secp256r1) attempts to close the session + const closeSessionIx = await ctx.highClient.closeSession({ + payer: ctx.payer, + walletPda: walletA, + sessionPda: sessionPda, + }); + + closeSessionIx.keys = [ + { pubkey: ctx.payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: walletA, isSigner: false, isWritable: false }, + { pubkey: sessionPda, isSigner: false, isWritable: true }, + { + pubkey: ctx.highClient.getConfigPda(), + isSigner: false, + isWritable: false, + }, + { pubkey: bobAuthPda, isSigner: false, isWritable: true }, + ]; + + // Tweak direct keys or structure if needed + const adminMeta = closeSessionIx.keys.find((k) => + k.pubkey.equals(bobAuthPda), + ); + if (adminMeta) adminMeta.isWritable = true; + + const { + ix: ixWithSysvars, + sysvarIxIndex, + sysvarSlotIndex, + } = appendSecp256r1Sysvars(closeSessionIx); + const currentSlot = await readCurrentSlot(ctx.connection); + const authenticatorData = await buildAuthenticatorData('example.com'); + const authPayload = buildAuthPayload({ + sysvarIxIndex, + sysvarSlotIndex, + authenticatorData, + slot: currentSlot, + }); + + // closeSession uses ONLY the session PDA for validation + const signedPayload = sessionPda.toBytes(); + + const msgToSign = await buildSecp256r1Message({ + discriminator: 8, // CloseSession uses discriminator index 8 on-chain + authPayload, + signedPayload, + payer: ctx.payer.publicKey, + programId: new PublicKey(PROGRAM_ID), + slot: currentSlot, + }); + + const sysvarIx = await buildSecp256r1PrecompileIx(bob, msgToSign); + + const originalData = Buffer.from(ixWithSysvars.data); + ixWithSysvars.data = Buffer.concat([ + originalData, + Buffer.from(authPayload), + ]); + + const result = await tryProcessInstructions(ctx, [sysvarIx, ixWithSysvars]); + expect(result.result).toBe('ok'); + + const sessionInfo = await ctx.connection.getAccountInfo(sessionPda); + expect(sessionInfo).toBeNull(); // Should be closed + }, 30000); +}); diff --git a/tests-v1-rpc/tests/09-counter-replay.test.ts b/tests-v1-rpc/tests/09-counter-replay.test.ts new file mode 100644 index 0000000..295d58f --- /dev/null +++ b/tests-v1-rpc/tests/09-counter-replay.test.ts @@ -0,0 +1,171 @@ +/** + * Secp256r1 Counter & Replay Protection Tests + * + * Verifies that the monotonically increasing counter prevents replay attacks. + */ + +import { + Keypair, + PublicKey, + SystemProgram, + LAMPORTS_PER_SOL, + type TransactionInstruction, +} from '@solana/web3.js'; +import { describe, it, expect, beforeAll } from 'vitest'; +import { + findVaultPda, + findAuthorityPda, + AuthType, + Role, + AuthorityAccount, +} from '@lazorkit/solita-client'; +import { + setupTest, + sendTx, + tryProcessInstructions, + type TestContext, +} from './common'; +import { generateMockSecp256r1Signer } from './secp256r1Utils'; + +function getRandomSeed() { + const seed = new Uint8Array(32); + crypto.getRandomValues(seed); + return seed; +} + +/** + * Helper: Build Secp256r1 execute instruction using SDK. + * Counter is automatically fetched from Authority account and incremented. + * + * Inputs: + * - signer: Secp256r1 signer + * - walletPda: Wallet account + * - innerInstructions: Instructions to execute + * + * Returns: + * - { precompileIx, executeIx }: Two instructions (precompile + execute) + */ +async function buildSecp256r1Execute( + ctx: TestContext, + signer: any, + walletPda: PublicKey, + innerInstructions: TransactionInstruction[], + rpId = 'example.com', +) { + return ctx.highClient.executeWithSecp256r1({ + payer: ctx.payer, + walletPda, + innerInstructions, + signer, + rpId, + }); +} + +describe('Counter & Replay Protection (Secp256r1)', () => { + let ctx: TestContext; + + let owner: Keypair; + let walletPda: PublicKey; + let vaultPda: PublicKey; + let secpAdmin: any; + let secpAdminAuthPda: PublicKey; + + beforeAll(async () => { + ctx = await setupTest(); + + owner = Keypair.generate(); + const userSeed = getRandomSeed(); + + // Create wallet with Owner (Ed25519) + const { ix, walletPda: w } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey, + userSeed, + }); + await sendTx(ctx, [ix]); + walletPda = w; + + const [v] = findVaultPda(walletPda); + vaultPda = v; + + // Generate Secp256r1 signer + secpAdmin = await generateMockSecp256r1Signer(); + + // Owner adds Secp256r1 Admin + const { ix: ixAddAdmin } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: owner, + newAuthPubkey: secpAdmin.publicKeyBytes, + newAuthType: AuthType.Secp256r1, + newCredentialHash: secpAdmin.credentialIdHash, + role: Role.Admin, + walletPda, + }); + await sendTx(ctx, [ixAddAdmin], [owner]); + + // Derive Secp256r1 Admin authority PDA (use credentialIdHash as seed, not pubkey) + const [aPda] = findAuthorityPda(walletPda, secpAdmin.credentialIdHash); + secpAdminAuthPda = aPda; + + console.log('🔐 Secp256r1 Admin added, testing counter...'); + }, 30_000); + + it('Executes increment counter correctly 10 times', async () => { + // Fund vault for transfer + const transferAmount = 2 * LAMPORTS_PER_SOL; + const fundIx = SystemProgram.transfer({ + fromPubkey: ctx.payer.publicKey, + toPubkey: vaultPda, + lamports: transferAmount, + }); + await sendTx(ctx, [fundIx]); + + // Read counter before + const authBefore = await AuthorityAccount.fromAccountAddress( + ctx.connection, + secpAdminAuthPda, + ); + expect(Number(authBefore.counter)).toBe(0); + + // Loop execute 10 times + let currentCounter = 0; + for (let i = 1; i <= 10; i++) { + const recipient = Keypair.generate(); + const transferIx = SystemProgram.transfer({ + fromPubkey: vaultPda, + toPubkey: recipient.publicKey, + lamports: 0.05 * LAMPORTS_PER_SOL, + }); + + const { precompileIx, executeIx } = await buildSecp256r1Execute( + ctx, + secpAdmin, + walletPda, + [transferIx], + ); + const result = await tryProcessInstructions(ctx, [ + precompileIx, + executeIx, + ]); + expect(result.result).toBe('ok'); + + const authAfter = await AuthorityAccount.fromAccountAddress( + ctx.connection, + secpAdminAuthPda, + ); + expect(Number(authAfter.counter)).toBe(i); + currentCounter = Number(authAfter.counter); + } + expect(currentCounter).toBe(10); + }, 120_000); + + // No additional test - covered by loop above + + // Replay/reuse not testable when user cannot control counter + + // Out-of-order test not relevant when counter auto-incremented only + + // Counter=0 test not relevant (user cannot submit custom counter) +}); diff --git a/tests-v1-rpc/tests/10-counter-all-instructions.test.ts b/tests-v1-rpc/tests/10-counter-all-instructions.test.ts new file mode 100644 index 0000000..064f43e --- /dev/null +++ b/tests-v1-rpc/tests/10-counter-all-instructions.test.ts @@ -0,0 +1,236 @@ +import { + Keypair, + PublicKey, + SystemProgram, + LAMPORTS_PER_SOL, +} from '@solana/web3.js'; +import { describe, it, expect, beforeAll } from 'vitest'; +import { + findVaultPda, + findAuthorityPda, + AuthType, + Role, + AuthorityAccount, +} from '@lazorkit/solita-client'; +import { setupTest, sendTx, TestContext } from './common'; +import { generateMockSecp256r1Signer } from './secp256r1Utils'; + +function getRandomSeed() { + const seed = new Uint8Array(32); + crypto.getRandomValues(seed); + return seed; +} + +describe('Counter increases for all Secp256r1 authority instructions', () => { + let ctx: TestContext; + let walletPda: PublicKey; + let secpAdmin: any; + let secpAdminAuthPda: PublicKey; + let owner: Keypair; + + beforeAll(async () => { + ctx = await setupTest(); + owner = Keypair.generate(); + const userSeed = getRandomSeed(); + // Create wallet + const { ix, walletPda: w } = await ctx.highClient.createWallet({ + payer: ctx.payer, + authType: AuthType.Ed25519, + owner: owner.publicKey, + userSeed, + }); + await sendTx(ctx, [ix]); + walletPda = w; + // Generate Secp256r1 signer + secpAdmin = await generateMockSecp256r1Signer(); + // Add Secp256r1 Admin + const { ix: ixAddAdmin } = await ctx.highClient.addAuthority({ + payer: ctx.payer, + adminType: AuthType.Ed25519, + adminSigner: owner, + newAuthPubkey: secpAdmin.publicKeyBytes, + newAuthType: AuthType.Secp256r1, + newCredentialHash: secpAdmin.credentialIdHash, + role: Role.Admin, + walletPda, + }); + await sendTx(ctx, [ixAddAdmin], [owner]); + // derive admin PDA + const [aPda] = findAuthorityPda(walletPda, secpAdmin.credentialIdHash); + secpAdminAuthPda = aPda; + }, 30_000); + + it('counter increases for AddAuthority', async () => { + const signer2 = await generateMockSecp256r1Signer(); + const { precompileIx, addIx } = + await ctx.highClient.addAuthorityWithSecp256r1({ + payer: ctx.payer, + walletPda, + signer: secpAdmin, + newAuthType: AuthType.Secp256r1, + newAuthPubkey: signer2.publicKeyBytes, + newCredentialHash: signer2.credentialIdHash, + role: Role.Spender, + }); + try { + await sendTx(ctx, [precompileIx, addIx]); + } catch (e: any) { + console.error('AddAuthority tx failed:', e?.logs ?? e?.message ?? e); + throw e; + } + const authAfter = await AuthorityAccount.fromAccountAddress( + ctx.connection, + secpAdminAuthPda, + ); + expect(Number(authAfter.counter)).toBe(1); + }); + + it('counter increases for RemoveAuthority', async () => { + // Use AddAuthority to add then Remove it + const signer3 = await generateMockSecp256r1Signer(); + const { precompileIx: addPIx, addIx } = + await ctx.highClient.addAuthorityWithSecp256r1({ + payer: ctx.payer, + walletPda, + signer: secpAdmin, + newAuthType: AuthType.Secp256r1, + newAuthPubkey: signer3.publicKeyBytes, + newCredentialHash: signer3.credentialIdHash, + role: Role.Spender, + }); + await sendTx(ctx, [addPIx, addIx]); + // Remove: derive authority PDA for the Secp credential hash and remove it + const [toRemovePda] = findAuthorityPda(walletPda, signer3.credentialIdHash); + const { precompileIx, removeIx } = + await ctx.highClient.removeAuthorityWithSecp256r1({ + payer: ctx.payer, + walletPda, + signer: secpAdmin, + authorityToRemovePda: toRemovePda, + }); + try { + await sendTx(ctx, [precompileIx, removeIx]); + } catch (e: any) { + console.error('RemoveAuthority tx failed:', e?.logs ?? e?.message ?? e); + throw e; + } + const authAfter = await AuthorityAccount.fromAccountAddress( + ctx.connection, + secpAdminAuthPda, + ); + expect(Number(authAfter.counter)).toBe(3); + }); + + it('counter increases for TransferOwnership', async () => { + // Create a fresh Secp owner and transfer ownership to it using Ed25519 owner, + // then have that Secp owner perform a Secp transfer to another Secp key. We assert + // the original `secpAdmin` counter remains unchanged (should be 3 from previous ops). + const signer4 = await generateMockSecp256r1Signer(); + const signer5 = await generateMockSecp256r1Signer(); + + const [ownerAuthorityPda] = findAuthorityPda( + walletPda, + owner.publicKey.toBytes(), + ); + const [newOwnerPda] = findAuthorityPda(walletPda, signer4.credentialIdHash); + + // Transfer ownership from Ed25519 owner -> signer4 (Secp) + const transferToSigner4 = await ctx.highClient.transferOwnership({ + payer: ctx.payer, + walletPda, + currentOwnerAuthority: ownerAuthorityPda, + newOwnerAuthority: newOwnerPda, + newAuthType: AuthType.Secp256r1, + newAuthPubkey: signer4.publicKeyBytes, + newCredentialHash: signer4.credentialIdHash, + signer: owner, + }); + await sendTx(ctx, [transferToSigner4], [owner]); + + // Now signer4 (Secp owner) transfers ownership to signer5 using Secp flow + const { precompileIx, transferIx } = + await ctx.highClient.transferOwnershipWithSecp256r1({ + payer: ctx.payer, + walletPda, + signer: signer4, + newAuthPubkey: signer5.publicKeyBytes, + newCredentialHash: signer5.credentialIdHash, + newAuthType: AuthType.Secp256r1, + }); + try { + await sendTx(ctx, [precompileIx, transferIx]); + } catch (e: any) { + console.error('TransferOwnership tx failed:', e?.logs ?? e?.message ?? e); + throw e; + } + + const authAfter = await AuthorityAccount.fromAccountAddress( + ctx.connection, + secpAdminAuthPda, + ); + expect(Number(authAfter.counter)).toBe(3); + }); + + it('counter increases for CreateSession', async () => { + const sessionKey = Keypair.generate().publicKey; + const expiresAt = Math.floor(Date.now() / 1000) + 1000; + const { precompileIx, sessionIx } = + await ctx.highClient.createSessionWithSecp256r1({ + payer: ctx.payer, + walletPda, + signer: secpAdmin, + sessionKey, + expiresAt, + }); + try { + await sendTx(ctx, [precompileIx, sessionIx]); + } catch (e: any) { + console.error('CreateSession tx failed:', e?.logs ?? e?.message ?? e); + throw e; + } + const authAfter = await AuthorityAccount.fromAccountAddress( + ctx.connection, + secpAdminAuthPda, + ); + expect(Number(authAfter.counter)).toBe(4); + }); + + it('counter multi-increment: loop 10 executions', async () => { + let before = await AuthorityAccount.fromAccountAddress( + ctx.connection, + secpAdminAuthPda, + ); + const vaultPda = findVaultPda(walletPda)[0]; + + // Ensure vault has sufficient funds for inner transfers used in the loop + const fundIx = SystemProgram.transfer({ + fromPubkey: ctx.payer.publicKey, + toPubkey: vaultPda, + lamports: 2 * LAMPORTS_PER_SOL, + }); + await sendTx(ctx, [fundIx]); + + for (let i = 1; i <= 10; i++) { + const recipient = Keypair.generate(); + const transferIx = SystemProgram.transfer({ + fromPubkey: vaultPda, + toPubkey: recipient.publicKey, + lamports: 0.01 * LAMPORTS_PER_SOL, + }); + const { precompileIx, executeIx } = + await ctx.highClient.executeWithSecp256r1({ + payer: ctx.payer, + walletPda, + signer: secpAdmin, + innerInstructions: [transferIx], + }); + await sendTx(ctx, [precompileIx, executeIx]); + const after = await AuthorityAccount.fromAccountAddress( + ctx.connection, + secpAdminAuthPda, + ); + expect(Number(after.counter)).toBe(Number(before.counter) + 1); + before = after; + } + }); +}); diff --git a/tests-v1-rpc/tests/common.ts b/tests-v1-rpc/tests/common.ts new file mode 100644 index 0000000..c62476b --- /dev/null +++ b/tests-v1-rpc/tests/common.ts @@ -0,0 +1,227 @@ +/** + * Shared test setup for LazorKit V1 (web3.js) tests. + * + * - Connection + payer with airdrop + * - Config PDA + Treasury Shard initialization + * - sendTx() helper + */ + +import { + Connection, + Keypair, + PublicKey, + Transaction, + sendAndConfirmTransaction, + SystemProgram, + type TransactionInstruction, +} from '@solana/web3.js'; +import { + findConfigPda, + findTreasuryShardPda, + LazorClient, + PROGRAM_ID, +} from '@lazorkit/solita-client'; + +export { PROGRAM_ID }; +import * as dotenv from 'dotenv'; + +dotenv.config(); + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +/** Generates a random 32-byte seed. Shared across all test files. */ +export function getRandomSeed(): Uint8Array { + const seed = new Uint8Array(32); + crypto.getRandomValues(seed); + return seed; +} + +export interface TestContext { + connection: Connection; + payer: Keypair; + configPda: PublicKey; + treasuryShard: PublicKey; + shardId: number; + highClient: LazorClient; +} + +/** + * Send one or more instructions wrapped in a single Transaction. + */ +export async function sendTx( + ctx: TestContext, + instructions: TransactionInstruction[], + signers: Keypair[] = [], + options: { skipPreflight?: boolean } = {}, +): Promise { + const tx = new Transaction(); + for (const ix of instructions) { + tx.add(ix); + } + const allSigners = [ctx.payer, ...signers]; + return sendAndConfirmTransaction(ctx.connection, tx, allSigners, { + commitment: 'confirmed', + ...options, + }); +} + +/** + * Send instructions expecting a failure, returning error string for matching. + */ +export async function tryProcessInstruction( + ctx: TestContext, + instructions: + | import('@solana/web3.js').TransactionInstruction + | import('@solana/web3.js').TransactionInstruction[], + signers: Keypair[] = [], +): Promise<{ result: string }> { + try { + const ixs = Array.isArray(instructions) ? instructions : [instructions]; + await sendTx(ctx, ixs, signers); + return { result: 'ok' }; + } catch (e: any) { + return { result: e.message || 'simulation failed' }; + } +} + +/** + * Multiple instructions variant + */ +export async function tryProcessInstructions( + ctx: TestContext, + instructions: TransactionInstruction[], + signers: Keypair[] = [], +): Promise<{ result: string; error?: any }> { + try { + await sendTx(ctx, instructions, signers); + return { result: 'ok' }; + } catch (e: any) { + return { result: e.message || 'simulation failed', error: e }; + } +} + +/** + * Initialize test context: + * - Create connection + * - Generate or load payer and airdrop + * - Derive and initialize Config PDA + * - Derive and initialize Treasury Shard PDA + */ +export async function setupTest(): Promise { + const rpcUrl = process.env.RPC_URL || 'http://127.0.0.1:8899'; + const connection = new Connection(rpcUrl, 'confirmed'); + + // ── Payer ───────────────────────────────────────────────────────── + let payer: Keypair; + if (process.env.PRIVATE_KEY) { + let keyBytes: Uint8Array; + if (process.env.PRIVATE_KEY.startsWith('[')) { + keyBytes = new Uint8Array(JSON.parse(process.env.PRIVATE_KEY)); + } else { + // base58 + const bs58 = await import('bs58'); + keyBytes = bs58.default.decode(process.env.PRIVATE_KEY); + } + payer = Keypair.fromSecretKey(keyBytes); + console.log(`Using fixed payer: ${payer.publicKey.toBase58()}`); + } else { + payer = Keypair.generate(); + } + + // Airdrop if needed + try { + const balance = await connection.getBalance(payer.publicKey); + console.log(`Payer balance: ${balance / 1e9} SOL`); + + if (balance < 500_000_000 && !rpcUrl.includes('mainnet')) { + console.log('Balance low, requesting airdrop...'); + const sig = await connection.requestAirdrop( + payer.publicKey, + 2_000_000_000, + ); + const latestBlockHash = await connection.getLatestBlockhash(); + await connection.confirmTransaction({ + blockhash: latestBlockHash.blockhash, + lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, + signature: sig, + }); + await sleep(1000); + console.log( + `New balance: ${(await connection.getBalance(payer.publicKey)) / 1e9} SOL`, + ); + } + } catch (e) { + console.warn('Could not check balance or airdrop:', e); + } + + // ── Client ──────────────────────────────────────────────────────── + const highClient = new LazorClient(connection, PROGRAM_ID); + + // ── Config PDA ──────────────────────────────────────────────────── + const [configPda] = findConfigPda(PROGRAM_ID); + + // ── Treasury Shard ──────────────────────────────────────────────── + const pubkeyBytes = payer.publicKey.toBytes(); + const sum = pubkeyBytes.reduce((a, b) => a + b, 0); + const shardId = sum % 16; + const [treasuryShard] = findTreasuryShardPda(shardId, PROGRAM_ID); + + const ctx: TestContext = { + connection, + payer, + configPda, + treasuryShard, + shardId, + highClient, + }; + + // ── Initialize Config if not yet ────────────────────────────────── + try { + const accInfo = await connection.getAccountInfo(configPda); + if (!accInfo) throw new Error('Not initialized'); + } catch { + console.log('Initializing Global Config...'); + try { + const initConfigIx = await highClient.initializeConfig({ + admin: payer, + walletFee: 10000n, + actionFee: 1000n, + numShards: 16, + }); + await sendTx(ctx, [initConfigIx]); + } catch (e: any) { + console.warn('Config init skipped:', e.message); + } + } + + // ── Initialize Treasury Shard if not yet ────────────────────────── + try { + const accInfo = await connection.getAccountInfo(treasuryShard); + if (!accInfo) throw new Error('Not initialized'); + } catch { + console.log(`Initializing Treasury Shard ${shardId}...`); + try { + const initShardIx = await highClient.initTreasuryShard({ + payer: payer, + shardId, + }); + await sendTx(ctx, [initShardIx]); + } catch (e: any) { + console.warn(`Shard ${shardId} init skipped:`, e.message); + } + } + + return ctx; +} + +export function getSystemTransferIx( + fromPubkey: PublicKey, + toPubkey: PublicKey, + lamports: bigint, +) { + return SystemProgram.transfer({ + fromPubkey, + toPubkey, + lamports: Number(lamports), + }); +} diff --git a/tests-v1-rpc/tests/secp256r1Utils.ts b/tests-v1-rpc/tests/secp256r1Utils.ts new file mode 100644 index 0000000..0f567e0 --- /dev/null +++ b/tests-v1-rpc/tests/secp256r1Utils.ts @@ -0,0 +1,105 @@ +/** + * Test-only Secp256r1 mock utilities for LazorKit V1 tests. + * + * This file provides: + * - A mock signer that implements the SDK's `Secp256r1Signer` interface using Web Crypto API. + * - Re-exports of SDK functions so tests use the real SDK path. + */ + +import { PublicKey, TransactionInstruction } from "@solana/web3.js"; +import { type Secp256r1Signer, buildSecp256r1Message, buildAuthenticatorData, buildAuthPayload, buildSecp256r1PrecompileIx, appendSecp256r1Sysvars, readCurrentSlot } from "@lazorkit/solita-client"; + +// Re-export SDK functions so tests can just import them from here +export { + buildAuthenticatorData, + buildAuthPayload, + buildSecp256r1Message, + buildSecp256r1PrecompileIx, + appendSecp256r1Sysvars, + readCurrentSlot +}; + +// ─── Mock Signer ───────────────────────────────────────────────────────────── + +interface MockSecp256r1Signer extends Secp256r1Signer { + privateKey: CryptoKey; +} + +/** + * Generates a mock Secp256r1 signer that implements the SDK's `Secp256r1Signer` interface. + * Uses Web Crypto API for key generation and signing. + */ +export async function generateMockSecp256r1Signer(credentialIdHash?: Uint8Array): Promise { + const keyPair = await crypto.subtle.generateKey( + { name: "ECDSA", namedCurve: "P-256" }, + true, + ["sign", "verify"] + ); + + const spki = await crypto.subtle.exportKey("spki", keyPair.publicKey); + const rawP256Pubkey = new Uint8Array(spki).slice(-64); + const compressedPubKey = new Uint8Array(33); + compressedPubKey[0] = (rawP256Pubkey[63] % 2 === 0) ? 0x02 : 0x03; + compressedPubKey.set(rawP256Pubkey.slice(0, 32), 1); + + const credHash = credentialIdHash || new Uint8Array(32).map(() => Math.floor(Math.random() * 256)); + + return { + privateKey: keyPair.privateKey, + publicKeyBytes: compressedPubKey, + credentialIdHash: credHash, + sign: async (message: Uint8Array): Promise => { + return signWithLowS(keyPair.privateKey, message); + }, + }; +} + +// ─── Low-S signing helper ──────────────────────────────────────────────────── + +/** + * Signs a message with the given private key and enforces low-S as required + * by the Solana Secp256r1 precompile. + */ +async function signWithLowS(privateKey: CryptoKey, message: Uint8Array): Promise { + // Note: crypto.subtle.sign("ECDSA") automatically hashes the message using the specified hash + const rawSigBuffer = await crypto.subtle.sign( + { name: "ECDSA", hash: "SHA-256" }, + privateKey, + message as any + ); + const rawSig = new Uint8Array(rawSigBuffer); + + // crypto.subtle outputs exactly 64 bytes (r || s) + if (rawSig.length !== 64) { + throw new Error(`Unexpected signature length from crypto.subtle: ${rawSig.length}`); + } + + // SECP256R1 curve order n + const SECP256R1_N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n; + const HALF_N = SECP256R1_N / 2n; + + const rBuffer = rawSig.slice(0, 32); + const sBufferLocal = rawSig.slice(32, 64); + + // Convert s to bigint and enforce low-S + let sBigInt = 0n; + for (let i = 0; i < 32; i++) { + sBigInt = (sBigInt << 8n) + BigInt(sBufferLocal[i]); + } + + if (sBigInt > HALF_N) { + sBigInt = SECP256R1_N - sBigInt; + } + + const modifiedSBuffer = new Uint8Array(32); + for (let i = 31; i >= 0; i--) { + modifiedSBuffer[i] = Number(sBigInt & 0xffn); + sBigInt >>= 8n; + } + + const lowSSig = new Uint8Array(64); + lowSSig.set(rBuffer, 0); + lowSSig.set(modifiedSBuffer, 32); + + return lowSSig; +} diff --git a/tests-v1-rpc/tsconfig.json b/tests-v1-rpc/tsconfig.json new file mode 100644 index 0000000..216b365 --- /dev/null +++ b/tests-v1-rpc/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "types": ["node"] + }, + "include": ["tests/**/*", "scripts/**/*"] +} diff --git a/tests/change_rule.test.ts b/tests/change_rule.test.ts deleted file mode 100644 index f8fd08a..0000000 --- a/tests/change_rule.test.ts +++ /dev/null @@ -1,1010 +0,0 @@ -import * as anchor from "@coral-xyz/anchor"; -import ECDSA from "ecdsa-secp256r1"; -import { - Keypair, - LAMPORTS_PER_SOL, - sendAndConfirmTransaction, -} from "@solana/web3.js"; -import * as dotenv from "dotenv"; -import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; -import { LazorKitProgram } from "../sdk/lazor-kit"; -import { DefaultRuleProgram } from "../sdk/default-rule-program"; - -import { ExecuteAction } from "../sdk/types"; -import { TransferLimitProgram } from "../sdk/transfer_limit"; -dotenv.config(); - -describe.skip("Test smart wallet with transfer limit", () => { - const connection = new anchor.web3.Connection( - process.env.RPC_URL || "http://localhost:8899", - "confirmed" - ); - - const lazorkitProgram = new LazorKitProgram(connection); - - const defaultRuleProgram = new DefaultRuleProgram(connection); - - const transferLimitProgram = new TransferLimitProgram(connection); - - const payer = anchor.web3.Keypair.fromSecretKey( - bs58.decode(process.env.PRIVATE_KEY!) - ); - - before(async () => { - const smartWalletSeqAccountInfo = await connection.getAccountInfo( - lazorkitProgram.smartWalletSeq - ); - - if (smartWalletSeqAccountInfo === null) { - const txn = await lazorkitProgram.initializeTxn( - payer.publicKey, - defaultRuleProgram.programId - ); - - await sendAndConfirmTransaction(connection, txn, [payer], { - commitment: "confirmed", - }); - } - - const whitelistRuleProgramData = - await lazorkitProgram.program.account.whitelistRulePrograms.fetch( - lazorkitProgram.whitelistRulePrograms - ); - - const listPrograms = whitelistRuleProgramData.list.map((programId) => - programId.toBase58() - ); - - // check if already have transfer limit program - if (!listPrograms.includes(transferLimitProgram.programId.toBase58())) { - const txn = await lazorkitProgram.upsertWhitelistRuleProgramsTxn( - payer.publicKey, - transferLimitProgram.programId - ); - - await sendAndConfirmTransaction(connection, txn, [payer], { - commitment: "confirmed", - }); - } - }); - - xit("Change default to transfer limit", async () => { - const privateKey = ECDSA.generateKey(); - - const publicKeyBase64 = privateKey.toCompressedPublicKey(); - - const pubkey = Array.from(Buffer.from(publicKeyBase64, "base64")); - - const smartWallet = await lazorkitProgram.getLastestSmartWallet(); - - const smartWalletConfig = lazorkitProgram.smartWalletConfig(smartWallet); - - const [smartWalletAuthenticator] = lazorkitProgram.smartWalletAuthenticator( - pubkey, - smartWallet - ); - - // the user has deposit 0.01 SOL to the smart-wallet - const depositSolIns = anchor.web3.SystemProgram.transfer({ - fromPubkey: payer.publicKey, - toPubkey: smartWallet, - lamports: LAMPORTS_PER_SOL / 100, - }); - - await sendAndConfirmTransaction( - connection, - new anchor.web3.Transaction().add(depositSolIns), - [payer], - { - commitment: "confirmed", - } - ); - - const initRuleIns = await defaultRuleProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const createSmartWalletTxn = await lazorkitProgram.createSmartWalletTxn( - pubkey, - initRuleIns, - payer.publicKey - ); - - const sig = await sendAndConfirmTransaction( - connection, - createSmartWalletTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - - console.log("Create smart-wallet: ", sig); - - // Change rule - const destroyRuleDefaultIns = await defaultRuleProgram.destroyIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const initTransferLimitRule = await transferLimitProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator, - smartWalletConfig, - { - passkeyPubkey: pubkey, - token: anchor.web3.PublicKey.default, - limitAmount: new anchor.BN(100), - limitPeriod: new anchor.BN(1000), - } - ); - - const message = Buffer.from("hello"); - const signatureBytes = Buffer.from(privateKey.sign(message), "base64"); - - const executeTxn = await lazorkitProgram.executeInstructionTxn( - pubkey, - message, - signatureBytes, - destroyRuleDefaultIns, - initTransferLimitRule, - payer.publicKey, - smartWallet, - ExecuteAction.ChangeProgramRule - ); - - const sig2 = await sendAndConfirmTransaction( - connection, - executeTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - console.log("Execute instruction: ", sig2); - }); - - xit("Change default to transfer limit and add member", async () => { - const privateKey = ECDSA.generateKey(); - - const publicKeyBase64 = privateKey.toCompressedPublicKey(); - - const pubkey = Array.from(Buffer.from(publicKeyBase64, "base64")); - - const smartWallet = await lazorkitProgram.getLastestSmartWallet(); - - const smartWalletConfig = lazorkitProgram.smartWalletConfig(smartWallet); - - const [smartWalletAuthenticator] = lazorkitProgram.smartWalletAuthenticator( - pubkey, - smartWallet - ); - - // the user has deposit 0.01 SOL to the smart-wallet - const depositSolIns = anchor.web3.SystemProgram.transfer({ - fromPubkey: payer.publicKey, - toPubkey: smartWallet, - lamports: LAMPORTS_PER_SOL / 100, - }); - - await sendAndConfirmTransaction( - connection, - new anchor.web3.Transaction().add(depositSolIns), - [payer], - { - commitment: "confirmed", - } - ); - - const initRuleIns = await defaultRuleProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const createSmartWalletTxn = await lazorkitProgram.createSmartWalletTxn( - pubkey, - initRuleIns, - payer.publicKey - ); - - const sig = await sendAndConfirmTransaction( - connection, - createSmartWalletTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - - console.log("Create smart-wallet: ", sig); - - // Change rule - const destroyRuleDefaultIns = await defaultRuleProgram.destroyIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const initTransferLimitRule = await transferLimitProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator, - smartWalletConfig, - { - passkeyPubkey: pubkey, - token: anchor.web3.PublicKey.default, - limitAmount: new anchor.BN(100), - limitPeriod: new anchor.BN(1000), - } - ); - - const message = Buffer.from("hello"); - const signatureBytes = Buffer.from(privateKey.sign(message), "base64"); - - const executeTxn = await lazorkitProgram.executeInstructionTxn( - pubkey, - message, - signatureBytes, - destroyRuleDefaultIns, - initTransferLimitRule, - payer.publicKey, - smartWallet, - ExecuteAction.ChangeProgramRule - ); - - const sig2 = await sendAndConfirmTransaction( - connection, - executeTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - console.log("Change rule instruction: ", sig2); - - const newPasskeyPubkey = Array.from( - Buffer.from(ECDSA.generateKey().toCompressedPublicKey(), "base64") - ); - const [newSmartWalletAuthenticator, bump] = - lazorkitProgram.smartWalletAuthenticator(newPasskeyPubkey, smartWallet); - - const addMemberIns = await transferLimitProgram.addMemeberIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator, - newSmartWalletAuthenticator, - lazorkitProgram.programId, - newPasskeyPubkey, - bump - ); - - const newMessage = Buffer.from("add member"); - const newSignatureBytes = Buffer.from( - privateKey.sign(newMessage), - "base64" - ); - - const addMemberTxn = await lazorkitProgram.executeInstructionTxn( - pubkey, - newMessage, - newSignatureBytes, - addMemberIns, - null, - payer.publicKey, - smartWallet, - ExecuteAction.CallRuleProgram, - newPasskeyPubkey - ); - - const addMemberSig = await sendAndConfirmTransaction( - connection, - addMemberTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - - console.log("Add member: ", addMemberSig); - }); - - xit("Change default to transfer limit and admin execute", async () => { - const privateKey = ECDSA.generateKey(); - - const publicKeyBase64 = privateKey.toCompressedPublicKey(); - - const pubkey = Array.from(Buffer.from(publicKeyBase64, "base64")); - - const smartWallet = await lazorkitProgram.getLastestSmartWallet(); - - const smartWalletConfig = lazorkitProgram.smartWalletConfig(smartWallet); - - const [smartWalletAuthenticator] = lazorkitProgram.smartWalletAuthenticator( - pubkey, - smartWallet - ); - - // the user has deposit 0.01 SOL to the smart-wallet - const depositSolIns = anchor.web3.SystemProgram.transfer({ - fromPubkey: payer.publicKey, - toPubkey: smartWallet, - lamports: LAMPORTS_PER_SOL, - }); - - await sendAndConfirmTransaction( - connection, - new anchor.web3.Transaction().add(depositSolIns), - [payer], - { - commitment: "confirmed", - } - ); - - const initRuleIns = await defaultRuleProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const createSmartWalletTxn = await lazorkitProgram.createSmartWalletTxn( - pubkey, - initRuleIns, - payer.publicKey - ); - - const sig = await sendAndConfirmTransaction( - connection, - createSmartWalletTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - - console.log("Create smart-wallet: ", sig); - - // Change rule - const destroyRuleDefaultIns = await defaultRuleProgram.destroyIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const initTransferLimitRule = await transferLimitProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator, - smartWalletConfig, - { - passkeyPubkey: pubkey, - token: anchor.web3.PublicKey.default, - limitAmount: new anchor.BN(LAMPORTS_PER_SOL), - limitPeriod: new anchor.BN(1000), - } - ); - - const message = Buffer.from("hello"); - const signatureBytes = Buffer.from(privateKey.sign(message), "base64"); - - const executeTxn = await lazorkitProgram.executeInstructionTxn( - pubkey, - message, - signatureBytes, - destroyRuleDefaultIns, - initTransferLimitRule, - payer.publicKey, - smartWallet, - ExecuteAction.ChangeProgramRule - ); - - const sig2 = await sendAndConfirmTransaction( - connection, - executeTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - console.log("Change rule instruction: ", sig2); - - const transferSolIns = anchor.web3.SystemProgram.transfer({ - fromPubkey: smartWallet, - toPubkey: payer.publicKey, - lamports: LAMPORTS_PER_SOL / 100, - }); - - const checkRuleIns = await transferLimitProgram.checkRuleIns( - smartWallet, - smartWalletAuthenticator, - transferSolIns - ); - - const executeTxn2 = await lazorkitProgram.executeInstructionTxn( - pubkey, - message, - signatureBytes, - checkRuleIns, - transferSolIns, - payer.publicKey, - smartWallet, - ExecuteAction.ExecuteCpi - ); - - const sig3 = await sendAndConfirmTransaction( - connection, - executeTxn2, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - console.log("Execute transfer: ", sig3); - }); - - xit("Change default to transfer limit and add member and member execute exceeded", async () => { - const privateKey = ECDSA.generateKey(); - - const pubkey = Array.from( - Buffer.from(privateKey.toCompressedPublicKey(), "base64") - ); - - const smartWallet = await lazorkitProgram.getLastestSmartWallet(); - - const smartWalletConfig = lazorkitProgram.smartWalletConfig(smartWallet); - - const [smartWalletAuthenticator] = lazorkitProgram.smartWalletAuthenticator( - pubkey, - smartWallet - ); - - // the user has deposit 0.01 SOL to the smart-wallet - const depositSolIns = anchor.web3.SystemProgram.transfer({ - fromPubkey: payer.publicKey, - toPubkey: smartWallet, - lamports: LAMPORTS_PER_SOL, - }); - - await sendAndConfirmTransaction( - connection, - new anchor.web3.Transaction().add(depositSolIns), - [payer], - { - commitment: "confirmed", - } - ); - - const initRuleIns = await defaultRuleProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const createSmartWalletTxn = await lazorkitProgram.createSmartWalletTxn( - pubkey, - initRuleIns, - payer.publicKey - ); - - const sig = await sendAndConfirmTransaction( - connection, - createSmartWalletTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - - console.log("Create smart-wallet: ", sig); - - // Change rule - const destroyRuleDefaultIns = await defaultRuleProgram.destroyIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const initTransferLimitRule = await transferLimitProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator, - smartWalletConfig, - { - passkeyPubkey: pubkey, - token: anchor.web3.PublicKey.default, - limitAmount: new anchor.BN(LAMPORTS_PER_SOL / 100 - 1), - limitPeriod: new anchor.BN(1000), - } - ); - - const message = Buffer.from("hello"); - const signatureBytes = Buffer.from(privateKey.sign(message), "base64"); - - const executeTxn = await lazorkitProgram.executeInstructionTxn( - pubkey, - message, - signatureBytes, - destroyRuleDefaultIns, - initTransferLimitRule, - payer.publicKey, - smartWallet, - ExecuteAction.ChangeProgramRule - ); - - const sig2 = await sendAndConfirmTransaction( - connection, - executeTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - console.log("Change rule instruction: ", sig2); - - const newPasskey = ECDSA.generateKey(); - - const newPasskeyPubkey = Array.from( - Buffer.from(newPasskey.toCompressedPublicKey(), "base64") - ); - - const [newSmartWalletAuthenticator, bump] = - lazorkitProgram.smartWalletAuthenticator(newPasskeyPubkey, smartWallet); - - const addMemberIns = await transferLimitProgram.addMemeberIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator, - newSmartWalletAuthenticator, - lazorkitProgram.programId, - newPasskeyPubkey, - bump - ); - - const addMemberTxn = await lazorkitProgram.executeInstructionTxn( - pubkey, - message, - signatureBytes, - addMemberIns, - null, - payer.publicKey, - smartWallet, - ExecuteAction.CallRuleProgram, - newPasskeyPubkey - ); - - const addMemberSig = await sendAndConfirmTransaction( - connection, - addMemberTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - - console.log("Add member: ", addMemberSig); - - const transferSolIns = anchor.web3.SystemProgram.transfer({ - fromPubkey: smartWallet, - toPubkey: payer.publicKey, - lamports: LAMPORTS_PER_SOL / 100, - }); - - const checkRuleIns = await transferLimitProgram.checkRuleIns( - smartWallet, - newSmartWalletAuthenticator, - transferSolIns - ); - - const memberSignatureBytes = Buffer.from( - newPasskey.sign(message), - "base64" - ); - - const executeTxn2 = await lazorkitProgram.executeInstructionTxn( - newPasskeyPubkey, - message, - memberSignatureBytes, - checkRuleIns, - transferSolIns, - payer.publicKey, - smartWallet, - ExecuteAction.ExecuteCpi - ); - - const sig3 = await sendAndConfirmTransaction( - connection, - executeTxn2, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - console.log("Execute transfer: ", sig3); - }); - - xit("Change default to transfer limit and add member and member execute success", async () => { - const privateKey = ECDSA.generateKey(); - - const pubkey = Array.from( - Buffer.from(privateKey.toCompressedPublicKey(), "base64") - ); - - const smartWallet = await lazorkitProgram.getLastestSmartWallet(); - - const smartWalletConfig = lazorkitProgram.smartWalletConfig(smartWallet); - - const [smartWalletAuthenticator] = lazorkitProgram.smartWalletAuthenticator( - pubkey, - smartWallet - ); - - // the user has deposit 0.01 SOL to the smart-wallet - const depositSolIns = anchor.web3.SystemProgram.transfer({ - fromPubkey: payer.publicKey, - toPubkey: smartWallet, - lamports: LAMPORTS_PER_SOL, - }); - - await sendAndConfirmTransaction( - connection, - new anchor.web3.Transaction().add(depositSolIns), - [payer], - { - commitment: "confirmed", - } - ); - - const initRuleIns = await defaultRuleProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const createSmartWalletTxn = await lazorkitProgram.createSmartWalletTxn( - pubkey, - initRuleIns, - payer.publicKey - ); - - const sig = await sendAndConfirmTransaction( - connection, - createSmartWalletTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - - console.log("Create smart-wallet: ", sig); - - // Change rule - const destroyRuleDefaultIns = await defaultRuleProgram.destroyIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const initTransferLimitRule = await transferLimitProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator, - smartWalletConfig, - { - passkeyPubkey: pubkey, - token: anchor.web3.PublicKey.default, - limitAmount: new anchor.BN(LAMPORTS_PER_SOL), - limitPeriod: new anchor.BN(1000), - } - ); - - const message = Buffer.from("hello"); - const signatureBytes = Buffer.from(privateKey.sign(message), "base64"); - - const executeTxn = await lazorkitProgram.executeInstructionTxn( - pubkey, - message, - signatureBytes, - destroyRuleDefaultIns, - initTransferLimitRule, - payer.publicKey, - smartWallet, - ExecuteAction.ChangeProgramRule - ); - - const sig2 = await sendAndConfirmTransaction( - connection, - executeTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - console.log("Change rule instruction: ", sig2); - - const newPasskey = ECDSA.generateKey(); - - const newPasskeyPubkey = Array.from( - Buffer.from(newPasskey.toCompressedPublicKey(), "base64") - ); - - const [newSmartWalletAuthenticator, bump] = - lazorkitProgram.smartWalletAuthenticator(newPasskeyPubkey, smartWallet); - - const addMemberIns = await transferLimitProgram.addMemeberIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator, - newSmartWalletAuthenticator, - lazorkitProgram.programId, - newPasskeyPubkey, - bump - ); - - const addMemberTxn = await lazorkitProgram.executeInstructionTxn( - pubkey, - message, - signatureBytes, - addMemberIns, - null, - payer.publicKey, - smartWallet, - ExecuteAction.CallRuleProgram, - newPasskeyPubkey - ); - - const addMemberSig = await sendAndConfirmTransaction( - connection, - addMemberTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - - console.log("Add member: ", addMemberSig); - - const transferSolIns = anchor.web3.SystemProgram.transfer({ - fromPubkey: smartWallet, - toPubkey: payer.publicKey, - lamports: LAMPORTS_PER_SOL / 100, - }); - - const checkRuleIns = await transferLimitProgram.checkRuleIns( - smartWallet, - newSmartWalletAuthenticator, - transferSolIns - ); - - const memberSignatureBytes = Buffer.from( - newPasskey.sign(message), - "base64" - ); - - const executeTxn2 = await lazorkitProgram.executeInstructionTxn( - newPasskeyPubkey, - message, - memberSignatureBytes, - checkRuleIns, - transferSolIns, - payer.publicKey, - smartWallet, - ExecuteAction.ExecuteCpi - ); - - const sig3 = await sendAndConfirmTransaction( - connection, - executeTxn2, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - console.log("Execute transfer: ", sig3); - }); - - it("Change default to transfer limit and add member and member execute but not transfer", async () => { - const privateKey = ECDSA.generateKey(); - - const pubkey = Array.from( - Buffer.from(privateKey.toCompressedPublicKey(), "base64") - ); - - const smartWallet = await lazorkitProgram.getLastestSmartWallet(); - - const smartWalletConfig = lazorkitProgram.smartWalletConfig(smartWallet); - - const [smartWalletAuthenticator] = lazorkitProgram.smartWalletAuthenticator( - pubkey, - smartWallet - ); - - // the user has deposit 0.01 SOL to the smart-wallet - const depositSolIns = anchor.web3.SystemProgram.transfer({ - fromPubkey: payer.publicKey, - toPubkey: smartWallet, - lamports: LAMPORTS_PER_SOL, - }); - - await sendAndConfirmTransaction( - connection, - new anchor.web3.Transaction().add(depositSolIns), - [payer], - { - commitment: "confirmed", - } - ); - - const initRuleIns = await defaultRuleProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const createSmartWalletTxn = await lazorkitProgram.createSmartWalletTxn( - pubkey, - initRuleIns, - payer.publicKey - ); - - const sig = await sendAndConfirmTransaction( - connection, - createSmartWalletTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - - console.log("Create smart-wallet: ", sig); - - // Change rule - const destroyRuleDefaultIns = await defaultRuleProgram.destroyIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const initTransferLimitRule = await transferLimitProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator, - smartWalletConfig, - { - passkeyPubkey: pubkey, - token: anchor.web3.PublicKey.default, - limitAmount: new anchor.BN(LAMPORTS_PER_SOL), - limitPeriod: new anchor.BN(1000), - } - ); - - const message = Buffer.from("hello"); - const signatureBytes = Buffer.from(privateKey.sign(message), "base64"); - - const executeTxn = await lazorkitProgram.executeInstructionTxn( - pubkey, - message, - signatureBytes, - destroyRuleDefaultIns, - initTransferLimitRule, - payer.publicKey, - smartWallet, - ExecuteAction.ChangeProgramRule - ); - - const sig2 = await sendAndConfirmTransaction( - connection, - executeTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - console.log("Change rule instruction: ", sig2); - - const newPasskey = ECDSA.generateKey(); - - const newPasskeyPubkey = Array.from( - Buffer.from(newPasskey.toCompressedPublicKey(), "base64") - ); - - const [newSmartWalletAuthenticator, bump] = - lazorkitProgram.smartWalletAuthenticator(newPasskeyPubkey, smartWallet); - - const addMemberIns = await transferLimitProgram.addMemeberIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator, - newSmartWalletAuthenticator, - lazorkitProgram.programId, - newPasskeyPubkey, - bump - ); - - const addMemberTxn = await lazorkitProgram.executeInstructionTxn( - pubkey, - message, - signatureBytes, - addMemberIns, - null, - payer.publicKey, - smartWallet, - ExecuteAction.CallRuleProgram, - newPasskeyPubkey - ); - - const addMemberSig = await sendAndConfirmTransaction( - connection, - addMemberTxn, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - - console.log("Add member: ", addMemberSig); - - const createAccount = anchor.web3.SystemProgram.createAccount({ - fromPubkey: smartWallet, - newAccountPubkey: Keypair.generate().publicKey, - lamports: LAMPORTS_PER_SOL / 100, - space: 0, - programId: lazorkitProgram.programId, - }); - - const checkRuleIns = await transferLimitProgram.checkRuleIns( - smartWallet, - newSmartWalletAuthenticator, - createAccount - ); - - const memberSignatureBytes = Buffer.from( - newPasskey.sign(message), - "base64" - ); - - const executeTxn2 = await lazorkitProgram.executeInstructionTxn( - newPasskeyPubkey, - message, - memberSignatureBytes, - checkRuleIns, - createAccount, - payer.publicKey, - smartWallet, - ExecuteAction.ExecuteCpi - ); - - const sig3 = await sendAndConfirmTransaction( - connection, - executeTxn2, - [payer], - { - commitment: "confirmed", - skipPreflight: true, - } - ); - console.log("Execute transfer: ", sig3); - }); -}); diff --git a/tests/constants.ts b/tests/constants.ts deleted file mode 100644 index fe9df99..0000000 --- a/tests/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const SMART_WALLET_SEQ_SEED = "smart_wallet_seq"; -export const SMART_WALLET_SEED = "smart_wallet"; -export const SMART_WALLET_CONFIG_SEED = "smart_wallet_config"; -export const WHITELIST_RULE_PROGRAMS_SEED = "whitelist_rule_programs"; -export const RULE_DATA_SEED = "rule_data"; -export const MEMBER_SEED = "member"; diff --git a/tests/mockdata/kaypair.ts b/tests/mockdata/kaypair.ts deleted file mode 100644 index 76201ca..0000000 --- a/tests/mockdata/kaypair.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const ADMIN_KEYPAIR = [ - 94, 1, 141, 116, 251, 238, 93, 200, 92, 40, 207, 140, 174, 243, 253, 248, 22, - 27, 12, 126, 86, 141, 180, 168, 170, 80, 244, 131, 92, 196, 49, 145, 10, 110, - 105, 124, 119, 177, 170, 188, 200, 181, 240, 114, 164, 84, 171, 34, 107, 174, - 210, 246, 229, 193, 172, 86, 42, 121, 139, 156, 66, 12, 179, 172, -]; - -export const USER1 = [ - 71, 52, 60, 92, 220, 54, 244, 48, 35, 97, 150, 200, 141, 206, 167, 81, 245, - 180, 31, 49, 202, 106, 215, 48, 191, 108, 67, 232, 117, 155, 44, 174, 215, - 212, 12, 171, 73, 250, 46, 94, 124, 127, 71, 222, 224, 101, 148, 224, 49, 26, - 172, 38, 82, 75, 192, 36, 144, 60, 89, 239, 43, 251, 181, 145, -]; - -export const USER2 = [ - 122, 143, 102, 54, 0, 208, 211, 8, 170, 6, 9, 240, 16, 122, 43, 139, 27, 69, - 29, 47, 228, 225, 32, 5, 173, 191, 192, 248, 46, 83, 146, 135, 203, 192, 110, - 115, 14, 231, 75, 218, 88, 157, 63, 228, 160, 148, 244, 128, 97, 225, 219, - 138, 128, 203, 130, 49, 174, 159, 244, 151, 111, 33, 234, 101, -]; - -export const USER3 = [ - 5, 183, 100, 57, 90, 47, 243, 113, 7, 100, 233, 68, 99, 173, 63, 75, 243, 130, - 67, 87, 179, 84, 212, 113, 229, 215, 86, 179, 253, 2, 64, 148, 239, 89, 254, - 175, 115, 85, 209, 206, 130, 188, 35, 250, 35, 92, 183, 249, 165, 208, 17, - 201, 169, 133, 227, 220, 198, 213, 8, 178, 17, 83, 92, 227, -]; diff --git a/tests/smart_wallet_with_default_rule.test.ts b/tests/smart_wallet_with_default_rule.test.ts deleted file mode 100644 index d8dc29f..0000000 --- a/tests/smart_wallet_with_default_rule.test.ts +++ /dev/null @@ -1,334 +0,0 @@ -import * as anchor from '@coral-xyz/anchor'; -import ECDSA from 'ecdsa-secp256r1'; -import { expect } from 'chai'; -import { - Keypair, - LAMPORTS_PER_SOL, - sendAndConfirmTransaction, -} from '@solana/web3.js'; -import * as dotenv from 'dotenv'; -import { bs58 } from '@coral-xyz/anchor/dist/cjs/utils/bytes'; -import { LazorKitProgram } from '../sdk/lazor-kit'; -import { DefaultRuleProgram } from '../sdk/default-rule-program'; -import { createNewMint, mintTokenTo } from './utils'; -import { createTransferCheckedInstruction } from '@solana/spl-token'; -dotenv.config(); - -describe('Test smart wallet with default rule', () => { - const connection = new anchor.web3.Connection( - process.env.RPC_URL || 'http://localhost:8899', - 'confirmed' - ); - - const lazorkitProgram = new LazorKitProgram(connection); - - const defaultRuleProgram = new DefaultRuleProgram(connection); - - const payer = anchor.web3.Keypair.fromSecretKey( - bs58.decode(process.env.PRIVATE_KEY!) - ); - - before(async () => { - // airdrop some SOL to the payer - - const smartWalletSeqAccountInfo = await connection.getAccountInfo( - lazorkitProgram.smartWalletSeq - ); - - if (smartWalletSeqAccountInfo == null) { - const txn = await lazorkitProgram.initializeTxn( - payer.publicKey, - defaultRuleProgram.programId - ); - - await sendAndConfirmTransaction(connection, txn, [payer], { - commitment: 'confirmed', - }); - } - }); - - it('Initialize successfully', async () => { - const privateKey = ECDSA.generateKey(); - - const publicKeyBase64 = privateKey.toCompressedPublicKey(); - - const pubkey = Array.from(Buffer.from(publicKeyBase64, 'base64')); - - const SeqBefore = await lazorkitProgram.smartWalletSeqData; - - const smartWallet = await lazorkitProgram.getLastestSmartWallet(); - - const [smartWalletAuthenticator] = lazorkitProgram.smartWalletAuthenticator( - pubkey, - smartWallet - ); - - // the user has deposit 0.01 SOL to the smart-wallet - const depositSolIns = anchor.web3.SystemProgram.transfer({ - fromPubkey: payer.publicKey, - toPubkey: smartWallet, - lamports: LAMPORTS_PER_SOL / 100, - }); - - await sendAndConfirmTransaction( - connection, - new anchor.web3.Transaction().add(depositSolIns), - [payer], - { - commitment: 'confirmed', - } - ); - - const initRuleIns = await defaultRuleProgram.initRuleIns( - payer.publicKey, - smartWallet, - smartWalletAuthenticator - ); - - const createSmartWalletTxn = await lazorkitProgram.createSmartWalletTxn( - pubkey, - initRuleIns, - payer.publicKey - ); - - const sig = await sendAndConfirmTransaction( - connection, - createSmartWalletTxn, - [payer], - { - commitment: 'confirmed', - skipPreflight: true, - } - ); - - console.log('Create smart-wallet: ', sig); - - const SeqAfter = await lazorkitProgram.smartWalletSeqData; - - expect(SeqAfter.seq.toString()).to.be.equal( - SeqBefore.seq.add(new anchor.BN(1)).toString() - ); - - const smartWalletConfigData = - await lazorkitProgram.getSmartWalletConfigData(smartWallet); - - expect(smartWalletConfigData.id.toString()).to.be.equal( - SeqBefore.seq.toString() - ); - - const smartWalletAuthenticatorData = - await lazorkitProgram.getSmartWalletAuthenticatorData( - smartWalletAuthenticator - ); - - expect(smartWalletAuthenticatorData.passkeyPubkey.toString()).to.be.equal( - pubkey.toString() - ); - expect(smartWalletAuthenticatorData.smartWallet.toString()).to.be.equal( - smartWallet.toString() - ); - }); - - // xit('Spend SOL successfully', async () => { - // const privateKey = ECDSA.generateKey(); - - // const publicKeyBase64 = privateKey.toCompressedPublicKey(); - - // const pubkey = Array.from(Buffer.from(publicKeyBase64, 'base64')); - - // const smartWallet = await lazorkitProgram.getLastestSmartWallet(); - - // const [smartWalletAuthenticator] = lazorkitProgram.smartWalletAuthenticator( - // pubkey, - // smartWallet - // ); - - // // the user has deposit 0.01 SOL to the smart-wallet - // const depositSolIns = anchor.web3.SystemProgram.transfer({ - // fromPubkey: payer.publicKey, - // toPubkey: smartWallet, - // lamports: LAMPORTS_PER_SOL / 100, - // }); - - // await sendAndConfirmTransaction( - // connection, - // new anchor.web3.Transaction().add(depositSolIns), - // [payer], - // { - // commitment: 'confirmed', - // } - // ); - - // const initRuleIns = await defaultRuleProgram.initRuleIns( - // payer.publicKey, - // smartWallet, - // smartWalletAuthenticator - // ); - - // const createSmartWalletTxn = await lazorkitProgram.createSmartWalletTxn( - // pubkey, - // initRuleIns, - // payer.publicKey - // ); - - // const createSmartWalletSig = await sendAndConfirmTransaction( - // connection, - // createSmartWalletTxn, - // [payer], - // { - // commitment: 'confirmed', - // skipPreflight: true, - // } - // ); - - // console.log('Create smart-wallet: ', createSmartWalletSig); - - // const message = Buffer.from('hello'); - // const signatureBytes = Buffer.from(privateKey.sign(message), 'base64'); - - // const transferSolIns = anchor.web3.SystemProgram.transfer({ - // fromPubkey: smartWallet, - // toPubkey: Keypair.generate().publicKey, - // lamports: 4000000, - // }); - - // const checkRule = await defaultRuleProgram.checkRuleIns( - // smartWallet, - // smartWalletAuthenticator - // ); - - // const executeTxn = await lazorkitProgram.executeInstructionTxn( - // pubkey, - // message, - // signatureBytes, - // checkRule, - // transferSolIns, - // payer.publicKey, - // smartWallet - // ); - - // const sig = await sendAndConfirmTransaction( - // connection, - // executeTxn, - // [payer], - // { - // commitment: 'confirmed', - // } - // ); - - // console.log('Execute txn: ', sig); - // }); - - // xit('Spend Token successfully', async () => { - // const privateKey = ECDSA.generateKey(); - - // const publicKeyBase64 = privateKey.toCompressedPublicKey(); - - // const pubkey = Array.from(Buffer.from(publicKeyBase64, 'base64')); - - // const smartWallet = await lazorkitProgram.getLastestSmartWallet(); - - // const [smartWalletAuthenticator] = lazorkitProgram.smartWalletAuthenticator( - // pubkey, - // smartWallet - // ); - - // // the user has deposit 0.01 SOL to the smart-wallet - // const depositSolIns = anchor.web3.SystemProgram.transfer({ - // fromPubkey: payer.publicKey, - // toPubkey: smartWallet, - // lamports: LAMPORTS_PER_SOL / 100, - // }); - - // await sendAndConfirmTransaction( - // connection, - // new anchor.web3.Transaction().add(depositSolIns), - // [payer], - // { - // commitment: 'confirmed', - // } - // ); - - // const initRuleIns = await defaultRuleProgram.initRuleIns( - // payer.publicKey, - // smartWallet, - // smartWalletAuthenticator - // ); - - // const createSmartWalletTxn = await lazorkitProgram.createSmartWalletTxn( - // pubkey, - // initRuleIns, - // payer.publicKey - // ); - - // const createSmartWalletSig = await sendAndConfirmTransaction( - // connection, - // createSmartWalletTxn, - // [payer], - // { - // commitment: 'confirmed', - // skipPreflight: true, - // } - // ); - - // console.log('Create smart-wallet: ', createSmartWalletSig); - - // const message = Buffer.from('hello'); - // const signatureBytes = Buffer.from(privateKey.sign(message), 'base64'); - - // const mint = await createNewMint(connection, payer, 6); - // const smartWalletTokenAccount = await mintTokenTo( - // connection, - // mint, - // payer, - // payer, - // smartWallet, - // 100_000 * 10 ** 6 - // ); - - // const randomTokenAccount = await mintTokenTo( - // connection, - // mint, - // payer, - // payer, - // Keypair.generate().publicKey, - // 1_000_000 - // ); - - // const transferTokenIns = createTransferCheckedInstruction( - // smartWalletTokenAccount, - // mint, - // randomTokenAccount, - // smartWallet, - // 100_000 * 10 ** 6, - // 6, - // [] - // ); - - // const checkRule = await defaultRuleProgram.checkRuleIns( - // smartWallet, - // smartWalletAuthenticator - // ); - - // const executeTxn = await lazorkitProgram.executeInstructionTxn( - // pubkey, - // message, - // signatureBytes, - // checkRule, - // transferTokenIns, - // payer.publicKey, - // smartWallet - // ); - - // const sig = await sendAndConfirmTransaction( - // connection, - // executeTxn, - // [payer], - // { - // commitment: 'confirmed', - // } - // ); - - // console.log('Execute txn: ', sig); - // }); -}); diff --git a/tests/utils.ts b/tests/utils.ts deleted file mode 100644 index 4e9df39..0000000 --- a/tests/utils.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - createMint, - getOrCreateAssociatedTokenAccount, - mintTo, -} from "@solana/spl-token"; -import { Connection, Keypair, PublicKey, Signer } from "@solana/web3.js"; - -export const fundAccountSOL = async ( - connection: Connection, - publicKey: PublicKey, - amount: number -) => { - let fundSig = await connection.requestAirdrop(publicKey, amount); - - return getTxDetails(connection, fundSig); -}; - -export const getTxDetails = async (connection: Connection, sig) => { - const latestBlockHash = await connection.getLatestBlockhash("processed"); - - await connection.confirmTransaction( - { - blockhash: latestBlockHash.blockhash, - lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, - signature: sig, - }, - "confirmed" - ); - - return await connection.getTransaction(sig, { - maxSupportedTransactionVersion: 0, - commitment: "confirmed", - }); -}; - -export const createNewMint = async ( - connection: Connection, - creator: Signer, - decimals: number, - keypair?: Keypair -): Promise => { - const tokenMint = await createMint( - connection, - creator, // payer - creator.publicKey, // mintAuthority - creator.publicKey, // freezeAuthority - decimals, // decimals, - keypair - ); - return tokenMint; -}; - -export const mintTokenTo = async ( - connection: Connection, - tokenMint: PublicKey, - mintAuthority: Signer, - payer: Signer, - to: PublicKey, - amount: number -): Promise => { - const userTokenAccount = await getOrCreateAssociatedTokenAccount( - connection, - payer, - tokenMint, - to, - true - ); - - const txhash = await mintTo( - connection, - payer, - tokenMint, - userTokenAccount.address, - mintAuthority, - amount - ); - - return userTokenAccount.address; -}; diff --git a/tsconfig.json b/tsconfig.json index 247d160..24c3270 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,11 @@ { "compilerOptions": { + "jsx": "react", "types": ["mocha", "chai"], "typeRoots": ["./node_modules/@types"], - "lib": ["es2015"], + "lib": ["es2020"], "module": "commonjs", - "target": "es6", + "target": "es2020", "esModuleInterop": true, "resolveJsonModule": true } diff --git a/txtx.yml b/txtx.yml new file mode 100644 index 0000000..dce8a86 --- /dev/null +++ b/txtx.yml @@ -0,0 +1,17 @@ +--- +name: program-v2 +id: program-v2 +runbooks: + - name: deployment + description: Deploy programs + location: runbooks/deployment +environments: + localnet: + network_id: localnet + rpc_api_url: http://127.0.0.1:8899 + devnet: + network_id: devnet + rpc_api_url: https://api.devnet.solana.com + payer_keypair_json: ~/.config/solana/id.json + authority_keypair_json: ~/.config/solana/id.json +