diff --git a/Cargo.lock b/Cargo.lock index 37d76fc81a..6dca975bb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,7 +98,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -183,7 +183,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "syn-solidity 0.4.2", "tiny-keccak", ] @@ -199,7 +199,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -215,7 +215,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "syn-solidity 0.7.7", "tiny-keccak", ] @@ -231,7 +231,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "syn-solidity 0.7.7", ] @@ -360,7 +360,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -543,6 +543,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.4", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.3.0" @@ -563,6 +583,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.106", +] + [[package]] name = "ark-ff-macros" version = "0.3.0" @@ -588,6 +618,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "ark-models-ext" version = "0.4.1" @@ -650,6 +693,18 @@ dependencies = [ "num-bigint", ] +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec 0.7.4", + "digest 0.10.7", + "num-bigint", +] + [[package]] name = "ark-serialize-derive" version = "0.4.2" @@ -682,6 +737,16 @@ dependencies = [ "rayon", ] +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "array-bytes" version = "6.2.2" @@ -761,7 +826,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "synstructure 0.13.1", ] @@ -784,7 +849,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -1016,7 +1081,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -1057,14 +1122,13 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.106", ] [[package]] @@ -1175,7 +1239,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -1990,7 +2054,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -2007,7 +2071,7 @@ checksum = "71367d3385c716342014ad17e3d19f7788ae514885a1f4c24f500260fb365e1a" dependencies = [ "libc", "once_cell", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -2039,7 +2103,7 @@ dependencies = [ "nom", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -2099,15 +2163,14 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.14.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" dependencies = [ "cfg-if", "cpufeatures", - "hex", "proptest", - "serde", + "serde_core", ] [[package]] @@ -2819,7 +2882,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3151,7 +3214,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3191,7 +3254,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3208,7 +3271,7 @@ checksum = "5c6888cd161769d65134846d4d4981d5a6654307cc46ec83fb917e530aea5f84" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3316,7 +3379,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3329,7 +3392,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3349,7 +3412,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "unicode-xid", ] @@ -3445,7 +3508,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3469,7 +3532,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.90", + "syn 2.0.106", "termcolor", "toml 0.8.19", "walkdir", @@ -3581,6 +3644,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "either" version = "1.9.0" @@ -3634,7 +3709,27 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -3654,7 +3749,7 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3665,7 +3760,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3897,7 +3992,7 @@ dependencies = [ "prettyplease 0.2.25", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -3976,7 +4071,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4512,7 +4607,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4656,7 +4751,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4668,7 +4763,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4678,7 +4773,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4866,7 +4961,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -4962,19 +5057,19 @@ checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] [[package]] @@ -5589,6 +5684,7 @@ dependencies = [ "pallet-balances", "pallet-bonds", "pallet-broadcast", + "pallet-build-evm-tx", "pallet-circuit-breaker", "pallet-claims", "pallet-collator-rewards", @@ -5642,6 +5738,7 @@ dependencies = [ "pallet-route-executor", "pallet-scheduler", "pallet-session", + "pallet-signet", "pallet-stableswap", "pallet-staking 4.1.5", "pallet-state-trie-migration", @@ -6179,6 +6276,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -6323,7 +6429,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -6829,7 +6935,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7244,7 +7350,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7258,7 +7364,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7269,7 +7375,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7280,7 +7386,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7296,7 +7402,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7455,7 +7561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -7569,7 +7675,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -7979,7 +8085,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -8061,7 +8167,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -8165,7 +8271,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -8884,6 +8990,24 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-build-evm-tx" +version = "1.0.0" +dependencies = [ + "ethereum", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "parity-scale-codec", + "rlp", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-child-bounties" version = "37.0.0" @@ -9100,7 +9224,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -10667,7 +10791,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -10841,6 +10965,21 @@ dependencies = [ "sp-session", ] +[[package]] +name = "pallet-signet" +version = "1.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-skip-feeless-payment" version = "13.0.0" @@ -11715,7 +11854,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -11756,7 +11895,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13337,7 +13476,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13349,7 +13488,7 @@ dependencies = [ "polkavm-common 0.10.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13359,7 +13498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13369,7 +13508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9324fe036de37c17829af233b46ef6b5562d4a0c09bb7fdb9f8378856dee30cf" dependencies = [ "polkavm-derive-impl 0.10.0", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13606,7 +13745,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13715,7 +13854,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13726,7 +13865,7 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13772,7 +13911,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13854,7 +13993,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.90", + "syn 2.0.106", "tempfile", ] @@ -13881,7 +14020,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -13921,7 +14060,7 @@ dependencies = [ "libc", "once_cell", "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "web-sys", "winapi", ] @@ -14128,7 +14267,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -14273,7 +14412,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -14608,13 +14747,14 @@ dependencies = [ [[package]] name = "ruint" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" +checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", + "ark-ff 0.5.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -14628,7 +14768,7 @@ dependencies = [ "rand 0.9.2", "rlp", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -15216,7 +15356,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -16223,7 +16363,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -16595,10 +16735,11 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -16630,15 +16771,24 @@ dependencies = [ "serde", ] +[[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.215" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -17428,7 +17578,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -17703,7 +17853,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "quote", "sp-crypto-hashing", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -17722,7 +17872,7 @@ source = "git+https://github.com/galacticcouncil/polkadot-sdk?branch=stable2409- dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -17952,7 +18102,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18137,7 +18287,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18462,7 +18612,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18475,7 +18625,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18647,9 +18797,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -18665,7 +18815,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18677,7 +18827,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18700,7 +18850,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18854,7 +19004,7 @@ checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18865,7 +19015,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -18876,7 +19026,7 @@ checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -19047,7 +19197,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -19250,7 +19400,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -19293,7 +19443,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -19722,12 +19872,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -19751,7 +19901,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -19785,7 +19935,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -20640,13 +20790,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.6.0", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "wyz" @@ -20746,7 +20893,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -20847,7 +20994,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] @@ -20867,7 +21014,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.106", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 384fadc09b..5170f86dcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,9 @@ members = [ 'pallets/broadcast', 'liquidation-worker-support', 'pallets/hsm', + 'pallets/build-evm-tx', + "pallets/signet", + 'pallets/erc20-vault', ] resolver = "2" @@ -58,6 +61,7 @@ codec = { package = "parity-scale-codec", version = "3.6.12", default-features = scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } serde = { version = "1.0.209", default-features = false } primitive-types = { version = "0.12.2", default-features = false } +borsh = { version = "1.5.7", default-features = false, features = ["derive"] } affix = "0.1.2" alloy-primitives = { version = "0.7", default-features = false } @@ -157,7 +161,12 @@ pallet-liquidation = { path = "pallets/liquidation", default-features = false } pallet-broadcast = { path = "pallets/broadcast", default-features = false } liquidation-worker-support = { path = "liquidation-worker-support", default-features = false } pallet-hsm = { path = "pallets/hsm", default-features = false } +pallet-build-evm-tx = { path = "pallets/build-evm-tx", default-features = false } +pallet-build-btc-tx = { path = "pallets/build-btc-tx", default-features = false } pallet-parameters = { path = "pallets/parameters", default-features = false } +pallet-signet = { path = "pallets/signet", default-features = false } +pallet-erc20-vault = { path = "pallets/erc20-vault", default-features = false } +pallet-btc-vault = { path = "pallets/btc-vault", default-features = false } hydra-dx-build-script-utils = { path = "utils/build-script-utils", default-features = false } scraper = { path = "scraper", default-features = false } diff --git a/pallets/btc-vault/Cargo.toml b/pallets/btc-vault/Cargo.toml new file mode 100644 index 0000000000..1c931f5a70 --- /dev/null +++ b/pallets/btc-vault/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "pallet-btc-vault" +version = "1.0.0" +authors = ["Substrate DevHub "] +edition = "2021" +license = "Apache-2.0" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive"] } +scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } +hex = { version = "0.4", default-features = false, features = ["alloc"] } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2", default-features = false } + +alloy-sol-types = { version = "0.8.14", default-features = false } + +pallet-signet = { path = "../signet", default-features = false } +pallet-build-btc-tx = { path = "../build-btc-tx", default-features = false } + +[dev-dependencies] +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409-2" } +secp256k1 = { version = "0.29.1", features = ["rand", "recovery"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "hex/std", + "serde/std", + "serde_json/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "sp-io/std", + "sp-core/std", + "sp-std/std", + "sp-runtime/std", + "alloy-sol-types/std", + "pallet-signet/std", + "pallet-build-btc-tx/std" +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] + +[target.'cfg(not(feature = "std"))'.dependencies] +borsh = { version = "1.5", default-features = false, features = ["derive", "hashbrown"] } + +[target.'cfg(feature = "std")'.dependencies] +borsh = { version = "1.5", default-features = false, features = ["derive", "std"] } \ No newline at end of file diff --git a/pallets/btc-vault/src/lib.rs b/pallets/btc-vault/src/lib.rs new file mode 100644 index 0000000000..380a278949 --- /dev/null +++ b/pallets/btc-vault/src/lib.rs @@ -0,0 +1,444 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +use alloc::{format, vec}; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::pallet_prelude::*; +use frame_support::traits::Currency; +use frame_support::PalletId; +use frame_support::{dispatch::DispatchResult, pallet_prelude::Weight, BoundedVec}; +use frame_system::pallet_prelude::*; +use sp_runtime::traits::{AccountIdConversion, Saturating}; +use sp_std::vec::Vec; + +const MAX_SERIALIZED_OUTPUT_LENGTH: u32 = 65536; + +#[cfg(test)] +mod tests; + +pub use pallet::*; + +/// Bitcoin testnet vault address (P2WPKH pubkey hash - 20 bytes) +/// tb1q38c2sg5nc3v0tkcle2uncdyqspc4kuawjxpf0j +pub const TESTNET_VAULT_ADDRESS: [u8; 20] = [ + 0x89, 0xf0, 0xa8, 0x23, 0x93, 0x8c, 0x58, 0xcf, 0x5b, 0x17, 0xc8, 0xeb, 0x93, 0xc6, 0x82, 0x80, 0x63, 0x5b, 0x73, + 0x4e, +]; + +/// Bitcoin testnet CAIP-2 chain ID +const BITCOIN_TESTNET_CAIP2: &str = "bip122:000000000933ea01ad0ee984209779ba"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use sp_io::hashing; + + // ========================= Configuration ========================= + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_build_btc_tx::Config + pallet_signet::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type VaultPalletId: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); + + // ========================= Storage ========================= + + /// Global vault configuration + #[pallet::storage] + #[pallet::getter(fn vault_config)] + pub type VaultConfig = StorageValue<_, VaultConfigData, OptionQuery>; + + /// Pending deposits awaiting signature + #[pallet::storage] + #[pallet::getter(fn pending_deposits)] + pub type PendingDeposits = StorageMap< + _, + Blake2_128Concat, + [u8; 32], // request_id + PendingDepositData, + OptionQuery, + >; + + /// User BTC balances (in satoshis) + #[pallet::storage] + #[pallet::getter(fn user_balances)] + pub type UserBalances = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + u64, // satoshis + ValueQuery, + >; + + // ========================= Types ========================= + + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, MaxEncodedLen)] + pub struct VaultConfigData { + pub mpc_root_signer_address: [u8; 20], + } + + #[derive(Encode, Decode, TypeInfo, Clone, Debug, MaxEncodedLen)] + pub struct PendingDepositData { + pub requester: AccountId, + pub amount: u64, // satoshis + pub path: BoundedVec>, + } + + // ========================= Events ========================= + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + VaultInitialized { + mpc_address: [u8; 20], + initialized_by: T::AccountId, + }, + DepositRequested { + request_id: [u8; 32], + requester: T::AccountId, + amount: u64, + }, + DepositClaimed { + request_id: [u8; 32], + claimer: T::AccountId, + amount: u64, + }, + DebugTxid { + txid: [u8; 32], + }, + DebugTransaction { + tx_hex: BoundedVec>, + version: u32, + locktime: u32, + }, + } + + // ========================= Errors ========================= + + #[pallet::error] + pub enum Error { + NotInitialized, + AlreadyInitialized, + InvalidRequestId, + DuplicateRequest, + DepositNotFound, + UnauthorizedClaimer, + InvalidSignature, + InvalidSigner, + InvalidOutput, + TransferFailed, + Overflow, + SerializationError, + PathTooLong, + PalletAccountNotFunded, + NoVaultOutput, + InvalidVaultOutput, + } + + // ========================= Hooks ========================= + + #[pallet::hooks] + impl Hooks> for Pallet {} + + // ========================= Extrinsics ========================= + + #[pallet::call] + impl Pallet { + /// Initialize the vault with MPC signer address + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(10_000, 0))] + pub fn initialize(origin: OriginFor, mpc_root_signer_address: [u8; 20]) -> DispatchResult { + let initializer = ensure_signed(origin)?; + ensure!(VaultConfig::::get().is_none(), Error::::AlreadyInitialized); + + VaultConfig::::put(VaultConfigData { + mpc_root_signer_address, + }); + + Self::deposit_event(Event::VaultInitialized { + mpc_address: mpc_root_signer_address, + initialized_by: initializer, + }); + + Ok(()) + } + + /// Request to deposit BTC tokens + /// Note: The pallet account must be funded before calling this + #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(100_000, 0))] + pub fn deposit_btc( + origin: OriginFor, + request_id: [u8; 32], + inputs: BoundedVec, + outputs: BoundedVec, + lock_time: u32, + ) -> DispatchResult { + let requester = ensure_signed(origin)?; + + // Ensure vault is initialized + ensure!(VaultConfig::::get().is_some(), Error::::NotInitialized); + + // Ensure no duplicate request + ensure!( + PendingDeposits::::get(&request_id).is_none(), + Error::::DuplicateRequest + ); + + // Get signet deposit amount and pallet account + let signet_deposit = pallet_signet::Pallet::::signature_deposit(); + let pallet_account = Self::account_id(); + let existential_deposit = ::Currency::minimum_balance(); + + // Ensure pallet account has sufficient balance + let pallet_balance = ::Currency::free_balance(&pallet_account); + let required_balance = existential_deposit.saturating_add(signet_deposit); + ensure!(pallet_balance >= required_balance, Error::::PalletAccountNotFunded); + + // Transfer signet deposit from requester to pallet account + ::Currency::transfer( + &requester, + &pallet_account, + signet_deposit, + frame_support::traits::ExistenceRequirement::AllowDeath, + )?; + + // Find vault output and extract deposit amount + let vault_script_pubkey = Self::create_p2wpkh_script(&TESTNET_VAULT_ADDRESS); + let deposit_amount = outputs + .iter() + .find(|output| output.script_pubkey.to_vec() == vault_script_pubkey) + .map(|output| output.value) + .ok_or(Error::::NoVaultOutput)?; + + ensure!(deposit_amount > 0, Error::::InvalidVaultOutput); + + // Use requester account as path + let path = { + let encoded = requester.encode(); + format!("0x{}", hex::encode(encoded)).into_bytes() + }; + + // Build PSBT + let psbt_bytes = pallet_build_btc_tx::Pallet::::build_bitcoin_tx( + frame_system::RawOrigin::Signed(requester.clone()).into(), + inputs.clone(), + outputs.clone(), + lock_time, + )?; + + let txid = pallet_build_btc_tx::Pallet::::get_txid( + frame_system::RawOrigin::Signed(requester.clone()).into(), + inputs.clone(), + outputs.clone(), + lock_time, + )?; + + Self::deposit_event(Event::DebugTxid { txid }); + + Self::deposit_event(Event::DebugTransaction { + tx_hex: psbt_bytes.clone().try_into().unwrap_or_default(), + version: 2, + locktime: lock_time, + }); + + // Generate and verify request ID + let computed_request_id = Self::generate_request_id( + &Self::account_id(), + &txid, + BITCOIN_TESTNET_CAIP2, + 0, + &path, + b"ecdsa", + b"bitcoin", + b"", + ); + + ensure!(computed_request_id == request_id, Error::::InvalidRequestId); + + // Store pending deposit + PendingDeposits::::insert( + &request_id, + PendingDepositData { + requester: requester.clone(), + amount: deposit_amount, + path: path.clone().try_into().map_err(|_| Error::::PathTooLong)?, + }, + ); + + // Create schemas for the response (simple boolean for Bitcoin) + let explorer_schema = + serde_json::to_vec(&serde_json::json!("bool")).map_err(|_| Error::::SerializationError)?; + + let callback_schema = + serde_json::to_vec(&serde_json::json!("bool")).map_err(|_| Error::::SerializationError)?; + + // Call sign_bidirectional from the pallet account + pallet_signet::Pallet::::sign_bidirectional( + frame_system::RawOrigin::Signed(Self::account_id()).into(), + BoundedVec::try_from(psbt_bytes).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(BITCOIN_TESTNET_CAIP2.as_bytes().to_vec()) + .map_err(|_| Error::::SerializationError)?, + 0, // key_version + BoundedVec::try_from(path).map_err(|_| Error::::PathTooLong)?, + BoundedVec::try_from(b"ecdsa".to_vec()).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(b"bitcoin".to_vec()).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(vec![]).map_err(|_| Error::::SerializationError)?, + Self::account_id(), // program_id (use pallet account) + BoundedVec::try_from(explorer_schema).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(callback_schema).map_err(|_| Error::::SerializationError)?, + )?; + + Self::deposit_event(Event::DepositRequested { + request_id, + requester, + amount: deposit_amount, + }); + + Ok(()) + } + + /// Claim deposited BTC tokens after signature verification + #[pallet::call_index(2)] + #[pallet::weight(Weight::from_parts(50_000, 0))] + pub fn claim_btc( + origin: OriginFor, + request_id: [u8; 32], + serialized_output: BoundedVec>, + signature: pallet_signet::Signature, + ) -> DispatchResult { + let claimer = ensure_signed(origin)?; + + // Get pending deposit + let pending = PendingDeposits::::get(&request_id).ok_or(Error::::DepositNotFound)?; + + // Verify claimer is the original requester + ensure!(pending.requester == claimer, Error::::UnauthorizedClaimer); + + // Get vault config + let config = VaultConfig::::get().ok_or(Error::::NotInitialized)?; + + // Verify signature + let message_hash = Self::hash_message(&request_id, &serialized_output); + Self::verify_signature_from_address(&message_hash, &signature, &config.mpc_root_signer_address)?; + + // Check for error magic prefix + const ERROR_PREFIX: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF]; + + let success = if serialized_output.len() >= 4 && &serialized_output[..4] == ERROR_PREFIX { + false + } else { + // Decode boolean (Borsh serialized) + use borsh::BorshDeserialize; + bool::try_from_slice(&serialized_output).map_err(|_| Error::::InvalidOutput)? + }; + + ensure!(success, Error::::TransferFailed); + + // Update user balance + UserBalances::::mutate(&claimer, |balance| -> DispatchResult { + *balance = balance.checked_add(pending.amount).ok_or(Error::::Overflow)?; + Ok(()) + })?; + + // Clean up storage + PendingDeposits::::remove(&request_id); + + Self::deposit_event(Event::DepositClaimed { + request_id, + claimer, + amount: pending.amount, + }); + + Ok(()) + } + } + + // ========================= Helper Functions ========================= + + impl Pallet { + fn generate_request_id( + sender: &T::AccountId, + txid: &[u8; 32], + caip2_id: &str, + key_version: u32, + path: &[u8], + algo: &[u8], + dest: &[u8], + params: &[u8], + ) -> [u8; 32] { + use alloy_sol_types::SolValue; + use sp_core::crypto::Ss58Codec; + + let encoded = sender.encode(); + let mut account_bytes = [0u8; 32]; + let len = encoded.len().min(32); + account_bytes[..len].copy_from_slice(&encoded[..len]); + + let account_id32 = sp_runtime::AccountId32::from(account_bytes); + let sender_ss58 = account_id32.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(0)); + + let encoded = ( + sender_ss58.as_str(), + txid.as_ref(), + caip2_id, + key_version, + core::str::from_utf8(path).unwrap_or(""), + core::str::from_utf8(algo).unwrap_or(""), + core::str::from_utf8(dest).unwrap_or(""), + core::str::from_utf8(params).unwrap_or(""), + ) + .abi_encode_packed(); + + sp_io::hashing::keccak_256(&encoded) + } + + fn hash_message(request_id: &[u8; 32], output: &[u8]) -> [u8; 32] { + let mut data = Vec::with_capacity(32 + output.len()); + data.extend_from_slice(request_id); + data.extend_from_slice(output); + hashing::keccak_256(&data) + } + + fn verify_signature_from_address( + message_hash: &[u8; 32], + signature: &pallet_signet::Signature, + expected_address: &[u8; 20], + ) -> DispatchResult { + ensure!(signature.recovery_id < 4, Error::::InvalidSignature); + + let mut sig_bytes = [0u8; 65]; + sig_bytes[..32].copy_from_slice(&signature.big_r.x); + sig_bytes[32..64].copy_from_slice(&signature.s); + sig_bytes[64] = signature.recovery_id; + + let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig_bytes, message_hash) + .map_err(|_| Error::::InvalidSignature)?; + + let pubkey_hash = hashing::keccak_256(&pubkey); + let recovered_address = &pubkey_hash[12..]; + + ensure!(recovered_address == expected_address, Error::::InvalidSigner); + + Ok(()) + } + + fn create_p2wpkh_script(pubkey_hash: &[u8; 20]) -> Vec { + let mut script = Vec::with_capacity(22); + script.push(0x00); // OP_0 + script.push(0x14); // Push 20 bytes + script.extend_from_slice(pubkey_hash); + script + } + } + + impl Pallet { + pub fn account_id() -> T::AccountId { + T::VaultPalletId::get().into_account_truncating() + } + } +} diff --git a/pallets/btc-vault/src/tests.rs b/pallets/btc-vault/src/tests.rs new file mode 100644 index 0000000000..1ae820612b --- /dev/null +++ b/pallets/btc-vault/src/tests.rs @@ -0,0 +1,436 @@ +use crate::{self as pallet_btc_vault, *}; +use frame_support::{assert_noop, assert_ok, parameter_types, traits::Currency as CurrencyTrait, PalletId, BoundedVec}; +use frame_system as system; +use secp256k1::{Message, PublicKey, Secp256k1, SecretKey}; +use sp_core::H256; +use sp_io::hashing::keccak_256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +// Test secret key for signing +fn get_test_secret_key() -> SecretKey { + SecretKey::from_slice(&[42u8; 32]).expect("Valid secret key") +} + +fn bounded_u8(v: Vec) -> BoundedVec> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_chain_id(v: Vec) -> BoundedVec { + BoundedVec::try_from(v).unwrap() +} + +// Get public key from secret key +fn get_test_public_key() -> PublicKey { + let secp = Secp256k1::new(); + let secret_key = get_test_secret_key(); + PublicKey::from_secret_key(&secp, &secret_key) +} + +fn public_key_to_btc_address(public_key: &PublicKey) -> [u8; 20] { + let uncompressed = public_key.serialize_uncompressed(); + let hash = keccak_256(&uncompressed[1..]); + let mut address = [0u8; 20]; + address.copy_from_slice(&hash[12..]); + address +} + +// Create a valid signature for testing using secp256k1 directly +fn create_valid_signature(message_hash: &[u8; 32]) -> pallet_signet::Signature { + let secp = Secp256k1::new(); + let secret_key = get_test_secret_key(); + let message = Message::from_slice(message_hash).expect("Valid message hash"); + + let sig = secp.sign_ecdsa_recoverable(&message, &secret_key); + let (recovery_id, sig_bytes) = sig.serialize_compact(); + + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&sig_bytes[0..32]); + s.copy_from_slice(&sig_bytes[32..64]); + + pallet_signet::Signature { + big_r: pallet_signet::AffinePoint { + x: r, + y: [0u8; 32], + }, + s, + recovery_id: recovery_id.to_i32() as u8, + } +} + +type Block = frame_system::mocking::MockBlock; + +// Mock runtime construction +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + Balances: pallet_balances, + Signet: pallet_signet, + BuildBitcoinTx: pallet_build_btc_tx, + BtcVault: pallet_btc_vault, + } +); + +// System config +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +// Balances config +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +impl pallet_balances::Config for Test { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); +} + +// Signet config +parameter_types! { + pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); + pub const MaxChainIdLength: u32 = 128; +} + +impl pallet_signet::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type PalletId = SignetPalletId; + type MaxChainIdLength = MaxChainIdLength; + type WeightInfo = (); +} + +// Build Bitcoin TX config +parameter_types! { + pub const MaxInputs: u32 = 10; + pub const MaxOutputs: u32 = 10; +} + +impl pallet_build_btc_tx::Config for Test { + type MaxInputs = MaxInputs; + type MaxOutputs = MaxOutputs; +} + +// BTC Vault config +parameter_types! { + pub const BtcVaultPalletId: PalletId = PalletId(*b"py/btcvt"); +} + +impl pallet_btc_vault::Config for Test { + type RuntimeEvent = RuntimeEvent; + type VaultPalletId = BtcVaultPalletId; +} + +// Helper to build test externalities +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + // Fund test accounts + let _ = >::deposit_creating(&1, 1_000_000); + let _ = >::deposit_creating(&2, 1_000_000); + let _ = >::deposit_creating(&3, 100); + // Initialize signet pallet + let _ = pallet_signet::Pallet::::initialize( + RuntimeOrigin::signed(1), + 1, + 100, + bounded_chain_id(b"test-chain".to_vec()), + ); + // Fund pallet account + let pallet_account = BtcVault::account_id(); + let _ = >::deposit_creating(&pallet_account, 10_000); + }); + ext +} + +fn create_test_mpc_address() -> [u8; 20] { + let public_key = get_test_public_key(); + public_key_to_btc_address(&public_key) +} + +// ======================================== +// INITIALIZATION TESTS +// ======================================== + +#[test] +fn test_initialize_works() { + new_test_ext().execute_with(|| { + let initializer = 2u64; + let mpc_address = create_test_mpc_address(); + + assert_eq!(BtcVault::vault_config(), None); + + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(initializer), mpc_address)); + + assert_eq!( + BtcVault::vault_config(), + Some(VaultConfigData { + mpc_root_signer_address: mpc_address + }) + ); + + System::assert_last_event( + Event::VaultInitialized { + mpc_address, + initialized_by: initializer, + } + .into(), + ); + }); +} + +#[test] +fn test_cannot_initialize_twice() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + assert_noop!( + BtcVault::initialize(RuntimeOrigin::signed(2), [4u8; 20]), + Error::::AlreadyInitialized + ); + }); +} + +// ======================================== +// DEPOSIT TESTS +// ======================================== + +#[test] +fn test_deposit_btc_fails_without_initialization() { + new_test_ext().execute_with(|| { + let requester = 1u64; + let request_id = [1u8; 32]; + + let inputs = BoundedVec::try_from(vec![pallet_build_btc_tx::UtxoInput { + txid: [0x42; 32], + vout: 0, + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + sequence: 0xFFFFFFFF, + }]) + .unwrap(); + + let outputs = BoundedVec::try_from(vec![pallet_build_btc_tx::BitcoinOutput { + value: 99_900_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + }]) + .unwrap(); + + assert_noop!( + BtcVault::deposit_btc(RuntimeOrigin::signed(requester), request_id, inputs, outputs, 0), + Error::::NotInitialized + ); + }); +} + +#[test] +fn test_deposit_btc_fails_without_vault_output() { + new_test_ext().execute_with(|| { + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(1), create_test_mpc_address())); + + let requester = 2u64; + let request_id = [1u8; 32]; + + let inputs = BoundedVec::try_from(vec![pallet_build_btc_tx::UtxoInput { + txid: [0x42; 32], + vout: 0, + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + sequence: 0xFFFFFFFF, + }]) + .unwrap(); + + // Output that doesn't go to vault + let outputs = BoundedVec::try_from(vec![pallet_build_btc_tx::BitcoinOutput { + value: 99_900_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14, 0x99, 0x99]).unwrap(), + }]) + .unwrap(); + + assert_noop!( + BtcVault::deposit_btc(RuntimeOrigin::signed(requester), request_id, inputs, outputs, 0), + Error::::NoVaultOutput + ); + }); +} + +// ======================================== +// CLAIM TESTS +// ======================================== + +#[test] +fn test_claim_nonexistent_deposit_fails() { + new_test_ext().execute_with(|| { + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(1), create_test_mpc_address())); + + let claimer = 2u64; + let request_id = [99u8; 32]; + + assert_noop!( + BtcVault::claim_btc( + RuntimeOrigin::signed(claimer), + request_id, + bounded_u8::<65536>(vec![1u8]), + pallet_signet::Signature { + big_r: pallet_signet::AffinePoint { + x: [1u8; 32], + y: [2u8; 32], + }, + s: [3u8; 32], + recovery_id: 0, + }, + ), + Error::::DepositNotFound + ); + }); +} + +#[test] +fn test_claim_with_error_response_fails() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + // Manually insert a pending deposit for testing + let requester = 2u64; + let request_id = [1u8; 32]; + let amount = 50_000_000u64; + + PendingDeposits::::insert( + &request_id, + PendingDepositData { + requester, + amount, + path: bounded_u8::<256>(b"test".to_vec()), + }, + ); + + // Error response with magic prefix + let error_output = vec![0xDE, 0xAD, 0xBE, 0xEF, 1, 2, 3]; + + let message_hash = { + let mut data = Vec::with_capacity(32 + error_output.len()); + data.extend_from_slice(&request_id); + data.extend_from_slice(&error_output); + keccak_256(&data) + }; + + let valid_signature = create_valid_signature(&message_hash); + + assert_noop!( + BtcVault::claim_btc( + RuntimeOrigin::signed(requester), + request_id, + bounded_u8::<65536>(error_output), + valid_signature, + ), + Error::::TransferFailed + ); + + assert_eq!(BtcVault::user_balances(requester), 0); + }); +} + +#[test] +fn test_claim_successful_with_valid_signature() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + assert_ok!(BtcVault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + let requester = 2u64; + let request_id = [1u8; 32]; + let amount = 50_000_000u64; + + PendingDeposits::::insert( + &request_id, + PendingDepositData { + requester, + amount, + path: bounded_u8::<256>(b"test".to_vec()), + }, + ); + + // Success response: Borsh-encoded true (1u8) + let success_output = vec![1u8]; + + let message_hash = { + let mut data = Vec::with_capacity(32 + success_output.len()); + data.extend_from_slice(&request_id); + data.extend_from_slice(&success_output); + keccak_256(&data) + }; + + let valid_signature = create_valid_signature(&message_hash); + + assert_eq!(BtcVault::user_balances(requester), 0); + + assert_ok!(BtcVault::claim_btc( + RuntimeOrigin::signed(requester), + request_id, + bounded_u8::<65536>(success_output), + valid_signature, + )); + + assert_eq!(BtcVault::user_balances(requester), amount); + assert!(BtcVault::pending_deposits(&request_id).is_none()); + + System::assert_has_event( + Event::DepositClaimed { + request_id, + claimer: requester, + amount, + } + .into(), + ); + }); +} \ No newline at end of file diff --git a/pallets/build-btc-tx/Cargo.toml b/pallets/build-btc-tx/Cargo.toml new file mode 100644 index 0000000000..1c42527f3c --- /dev/null +++ b/pallets/build-btc-tx/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "pallet-build-btc-tx" +version = "1.0.0" +description = "Pallet for building PSBT-encoded Bitcoin transactions" +authors = ["GalacticCouncil"] +edition = "2021" +homepage = "https://github.com/galacticcouncil/hydration-node" +license = "Apache 2.0" +repository = "https://github.com/galacticcouncil/hydration-node" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } +sp-std = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +signet-rs = { git = "https://github.com/esaminu/signet.rs", branch = "borsh-1.5.7", default-features = false, features = ["bitcoin"] } +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-core = { workspace = true } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "sp-std/std", + "scale-info/std", + "signet-rs/std", +] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/build-btc-tx/src/lib.rs b/pallets/build-btc-tx/src/lib.rs new file mode 100644 index 0000000000..64a33823f3 --- /dev/null +++ b/pallets/build-btc-tx/src/lib.rs @@ -0,0 +1,172 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_prelude::*; +use frame_system::{ensure_signed, pallet_prelude::*}; +use sp_std::vec::Vec; + +pub use pallet::*; + +#[cfg(test)] +mod tests; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_system::pallet_prelude::BlockNumberFor; // ← Add this + + #[pallet::config] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MaxInputs: Get; + #[pallet::constant] + type MaxOutputs: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); + + /// Bitcoin script max size (standard Bitcoin limit) + pub type MaxScriptLength = ConstU32<520>; + + /// Bitcoin UTXO input with metadata required for PSBT construction + #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)] + pub struct UtxoInput { + pub txid: [u8; 32], + pub vout: u32, + pub value: u64, + pub script_pubkey: BoundedVec, + pub sequence: u32, + } + + /// Bitcoin transaction output + #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)] + pub struct BitcoinOutput { + pub value: u64, + pub script_pubkey: BoundedVec, + } + + #[pallet::error] + pub enum Error { + NoInputs, + NoOutputs, + InvalidLockTime, + PsbtCreationFailed, + PsbtSerializationFailed, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + impl Pallet { + /// Build a Bitcoin PSBT and return the serialized bytes + /// + /// # Parameters + /// - `origin`: The signed origin + /// - `inputs`: UTXOs to spend + /// - `outputs`: Transaction outputs + /// - `lock_time`: Transaction locktime + /// + /// # Returns + /// Serialized PSBT bytes + pub fn build_bitcoin_tx( + origin: OriginFor, + inputs: BoundedVec, + outputs: BoundedVec, + lock_time: u32, + ) -> Result, DispatchError> { + ensure_signed(origin)?; + + ensure!(!inputs.is_empty(), Error::::NoInputs); + ensure!(!outputs.is_empty(), Error::::NoOutputs); + + let (psbt_bytes, _) = Self::build_psbt(&inputs, &outputs, lock_time)?; + Ok(psbt_bytes) + } + + /// Get the transaction ID (txid) for a Bitcoin transaction + /// + /// # Parameters + /// - `origin`: The signed origin + /// - `inputs`: UTXOs to spend + /// - `outputs`: Transaction outputs + /// - `lock_time`: Transaction locktime + /// + /// # Returns + /// 32-byte transaction ID (canonical, excludes witness) + pub fn get_txid( + origin: OriginFor, + inputs: BoundedVec, + outputs: BoundedVec, + lock_time: u32, + ) -> Result<[u8; 32], DispatchError> { + ensure_signed(origin)?; + + ensure!(!inputs.is_empty(), Error::::NoInputs); + ensure!(!outputs.is_empty(), Error::::NoOutputs); + + let (_, txid) = Self::build_psbt(&inputs, &outputs, lock_time)?; + Ok(txid) + } + + fn build_psbt( + inputs: &[UtxoInput], + outputs: &[BitcoinOutput], + lock_time: u32, + ) -> Result<(Vec, [u8; 32]), DispatchError> { + use signet_rs::bitcoin::types::{ + Amount, Hash, LockTime, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid, Version, Witness, + }; + use signet_rs::{TransactionBuilder, TxBuilder, BITCOIN}; + + let tx_inputs: Vec = inputs + .iter() + .map(|input| TxIn { + previous_output: OutPoint::new(Txid(Hash(input.txid)), input.vout), + script_sig: ScriptBuf::default(), + sequence: Sequence(input.sequence), + witness: Witness::default(), + }) + .collect(); + + let tx_outputs: Vec = outputs + .iter() + .map(|output| TxOut { + value: Amount::from_sat(output.value), + script_pubkey: ScriptBuf(output.script_pubkey.to_vec()), + }) + .collect(); + + let lock_time_parsed = if lock_time < 500_000_000 { + LockTime::from_height(lock_time).map_err(|_| Error::::InvalidLockTime)? + } else { + LockTime::from_time(lock_time).map_err(|_| Error::::InvalidLockTime)? + }; + + let tx = TransactionBuilder::new::() + .version(Version::Two) + .inputs(tx_inputs) + .outputs(tx_outputs) + .lock_time(lock_time_parsed) + .build(); + + // Extract txid before creating PSBT + let txid = tx.compute_txid(); + let mut txid_bytes = txid.as_byte_array(); + txid_bytes.reverse(); + + let mut psbt = tx.to_psbt(); + + for (i, input) in inputs.iter().enumerate() { + psbt.update_input_with_witness_utxo(i, input.script_pubkey.to_vec(), input.value) + .map_err(|_| Error::::PsbtCreationFailed)?; + + psbt.update_input_with_sighash_type(i, 1) + .map_err(|_| Error::::PsbtCreationFailed)?; + } + + let psbt_bytes = psbt.serialize().map_err(|_| Error::::PsbtSerializationFailed)?; + + Ok((psbt_bytes, txid_bytes)) + } + } +} diff --git a/pallets/build-btc-tx/src/tests.rs b/pallets/build-btc-tx/src/tests.rs new file mode 100644 index 0000000000..2a52c29544 --- /dev/null +++ b/pallets/build-btc-tx/src/tests.rs @@ -0,0 +1,202 @@ +use crate::{self as pallet_build_btc_tx, *}; +use frame_support::{assert_noop, assert_ok, parameter_types, traits::{ConstU16, ConstU32, ConstU64}, BoundedVec}; +use sp_core::H256; +use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, BuildStorage}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + BuildBitcoinTx: pallet_build_btc_tx, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +parameter_types! { + pub const MaxInputs: u32 = 10; + pub const MaxOutputs: u32 = 10; +} + +impl pallet_build_btc_tx::Config for Test { + type MaxInputs = MaxInputs; + type MaxOutputs = MaxOutputs; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +#[test] +fn test_build_simple_transaction() { + new_test_ext().execute_with(|| { + let inputs = BoundedVec::try_from(vec![UtxoInput { + txid: [0x42; 32], + vout: 0, + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + sequence: 0xFFFFFFFF, + }]) + .unwrap(); + + let outputs = BoundedVec::try_from(vec![BitcoinOutput { + value: 99_900_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + }]) + .unwrap(); + + let result = BuildBitcoinTx::build_bitcoin_tx( + RuntimeOrigin::signed(1), + inputs, + outputs, + 0 + ); + + assert_ok!(&result); + let psbt = result.unwrap(); + assert!(!psbt.is_empty(), "PSBT should not be empty"); + }); +} + +#[test] +fn test_get_txid() { + new_test_ext().execute_with(|| { + let inputs = BoundedVec::try_from(vec![UtxoInput { + txid: [0x42; 32], + vout: 0, + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + sequence: 0xFFFFFFFF, + }]) + .unwrap(); + + let outputs = BoundedVec::try_from(vec![BitcoinOutput { + value: 99_900_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + }]) + .unwrap(); + + let result = BuildBitcoinTx::get_txid( + RuntimeOrigin::signed(1), + inputs.clone(), + outputs.clone(), + 0 + ); + + assert_ok!(&result); + let txid = result.unwrap(); + assert_eq!(txid.len(), 32, "Txid should be 32 bytes"); + + // Verify txid is deterministic + let result2 = BuildBitcoinTx::get_txid( + RuntimeOrigin::signed(1), + inputs, + outputs, + 0 + ); + assert_eq!(txid, result2.unwrap(), "Same inputs should produce same txid"); + }); +} + +#[test] +fn test_no_inputs_fails() { + new_test_ext().execute_with(|| { + let outputs = BoundedVec::try_from(vec![BitcoinOutput { + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + }]) + .unwrap(); + + assert_noop!( + BuildBitcoinTx::build_bitcoin_tx( + RuntimeOrigin::signed(1), + BoundedVec::default(), + outputs.clone(), + 0 + ), + Error::::NoInputs + ); + + assert_noop!( + BuildBitcoinTx::get_txid( + RuntimeOrigin::signed(1), + BoundedVec::default(), + outputs, + 0 + ), + Error::::NoInputs + ); + }); +} + +#[test] +fn test_no_outputs_fails() { + new_test_ext().execute_with(|| { + let inputs = BoundedVec::try_from(vec![UtxoInput { + txid: [0x42; 32], + vout: 0, + value: 100_000_000, + script_pubkey: BoundedVec::try_from(vec![0x00, 0x14]).unwrap(), + sequence: 0xFFFFFFFF, + }]) + .unwrap(); + + assert_noop!( + BuildBitcoinTx::build_bitcoin_tx( + RuntimeOrigin::signed(1), + inputs.clone(), + BoundedVec::default(), + 0 + ), + Error::::NoOutputs + ); + + assert_noop!( + BuildBitcoinTx::get_txid( + RuntimeOrigin::signed(1), + inputs, + BoundedVec::default(), + 0 + ), + Error::::NoOutputs + ); + }); +} \ No newline at end of file diff --git a/pallets/build-evm-tx/Cargo.toml b/pallets/build-evm-tx/Cargo.toml new file mode 100644 index 0000000000..fa8054b3c3 --- /dev/null +++ b/pallets/build-evm-tx/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "pallet-build-evm-tx" +version = "1.0.0" +description = "Pallet for building RLP serialized EVM transactions" +authors = ["GalacticCouncil"] +edition = "2021" +homepage = "https://github.com/galacticcouncil/hydration-node" +license = "Apache 2.0" +repository = "https://github.com/galacticcouncil/hydration-node" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } + +# Substrate dependencies +sp-std = { workspace = true } +sp-core = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +ethereum = { workspace = true } +signet-rs = { git = "https://github.com/esaminu/signet.rs", branch = "borsh-1.5.7", default-features = false, features = ["evm"] } + +# Optionals +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-core = { workspace = true } +hex = { workspace = true } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "sp-std/std", + "sp-core/std", + "scale-info/std", + "ethereum/std", +] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/build-evm-tx/README.md b/pallets/build-evm-tx/README.md new file mode 100644 index 0000000000..f178e99196 --- /dev/null +++ b/pallets/build-evm-tx/README.md @@ -0,0 +1,33 @@ +# pallet-build-evm-tx + +## Overview + +The build-evm-tx pallet provides functionality to construct EIP-1559 compliant EVM transactions and encode them using RLP serialization. + +### Usage + +Other pallets can use the helper function to build EVM transactions: + +```rust +let rlp_encoded = pallet_build_evm_tx::Pallet::::build_evm_tx( + Some(who), // Optional account for event emission + Some(to_address), // Optional recipient (None for contract creation) + value, // ETH value in wei + data, // Transaction data/calldata + nonce, // Transaction nonce + gas_limit, // Gas limit + max_fee_per_gas, // Maximum total fee per gas + max_priority_fee, // Maximum priority fee (tip) per gas + chain_id, // Target chain ID +)?; +``` + +### Configuration + +The pallet requires configuring the maximum data length + +```rust +parameter_types! { + pub const MaxEvmDataLength: u32 = 100_000; +} +``` diff --git a/pallets/build-evm-tx/src/lib.rs b/pallets/build-evm-tx/src/lib.rs new file mode 100644 index 0000000000..dee3a93c0b --- /dev/null +++ b/pallets/build-evm-tx/src/lib.rs @@ -0,0 +1,114 @@ +// This file is part of HydraDX. + +// Copyright (C) 2020-2021 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_prelude::*; +use frame_system::{ensure_signed, pallet_prelude::OriginFor}; +use sp_std::vec::Vec; + +use ethereum::AccessListItem; +use signet_rs::{TransactionBuilder, TxBuilder, EVM}; +use sp_core::H160; + +pub use pallet::*; + +#[cfg(test)] +mod tests; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Maximum length of transaction data + #[pallet::constant] + type MaxDataLength: Get; + } + + #[pallet::error] + pub enum Error { + /// Transaction data exceeds maximum allowed length + DataTooLong, + /// Invalid address format - must be exactly 20 bytes + InvalidAddress, + /// Priority fee cannot exceed max fee per gas (EIP-1559 requirement) + InvalidGasPrice, + } + + impl Pallet { + /// Build an EIP-1559 EVM transaction and return the RLP-encoded data + /// + /// # Parameters + /// - `origin`: The signed origin + /// - `to_address`: Optional recipient address (None for contract creation) + /// - `value`: ETH value in wei + /// - `data`: Transaction data/calldata + /// - `nonce`: Transaction nonce + /// - `gas_limit`: Maximum gas units for transaction + /// - `max_fee_per_gas`: Maximum total fee per gas (base + priority) + /// - `max_priority_fee_per_gas`: Maximum priority fee (tip) per gas + /// - `chain_id`: Target EVM chain ID + /// + /// # Returns + /// RLP-encoded transaction data with EIP-2718 type prefix (0x02 for EIP-1559) + pub fn build_evm_tx( + origin: OriginFor, + to_address: Option, + value: u128, + data: Vec, + nonce: u64, + gas_limit: u64, + max_fee_per_gas: u128, + max_priority_fee_per_gas: u128, + access_list: Vec, + chain_id: u64, + ) -> Result, DispatchError> { + ensure_signed(origin)?; + ensure!(data.len() <= T::MaxDataLength::get() as usize, Error::::DataTooLong); + ensure!(max_priority_fee_per_gas <= max_fee_per_gas, Error::::InvalidGasPrice); + + let to_address_bytes = to_address.map(|h| h.0); + let access_list_converted = convert_access_list(access_list); + + let tx = TransactionBuilder::new::() + .chain_id(chain_id) + .nonce(nonce) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .max_fee_per_gas(max_fee_per_gas) + .gas_limit(gas_limit as u128) + .value(value) + .input(data) + .access_list(access_list_converted); + + let tx = if let Some(to) = to_address_bytes { tx.to(to) } else { tx }; + + Ok(tx.build().build_for_signing()) + } + } + + fn convert_access_list(items: Vec) -> Vec<([u8; 20], Vec<[u8; 32]>)> { + items + .into_iter() + .map(|item| (item.address.0, item.storage_keys.into_iter().map(|k| k.0).collect())) + .collect() + } +} diff --git a/pallets/build-evm-tx/src/tests/mock.rs b/pallets/build-evm-tx/src/tests/mock.rs new file mode 100644 index 0000000000..22ca11ed2e --- /dev/null +++ b/pallets/build-evm-tx/src/tests/mock.rs @@ -0,0 +1,77 @@ +use crate as pallet_build_evm_tx; +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU16, ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test + { + System: frame_system, + BuildEvmTx: pallet_build_evm_tx, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +parameter_types! { + pub const MaxDataLength: u32 = 100_000; +} + +impl pallet_build_evm_tx::Config for Test { + type MaxDataLength = MaxDataLength; +} + +#[derive(Default)] +pub struct ExtBuilder {} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let mut r: sp_io::TestExternalities = t.into(); + + r.execute_with(|| { + System::set_block_number(1); + }); + + r + } +} diff --git a/pallets/build-evm-tx/src/tests/mod.rs b/pallets/build-evm-tx/src/tests/mod.rs new file mode 100644 index 0000000000..9dc9f19563 --- /dev/null +++ b/pallets/build-evm-tx/src/tests/mod.rs @@ -0,0 +1,4 @@ +pub mod mock; +#[allow(clippy::module_inception)] +mod tests; +mod validation; diff --git a/pallets/build-evm-tx/src/tests/tests.rs b/pallets/build-evm-tx/src/tests/tests.rs new file mode 100644 index 0000000000..0966a569cd --- /dev/null +++ b/pallets/build-evm-tx/src/tests/tests.rs @@ -0,0 +1,44 @@ +use super::mock::*; +use crate::Error; + +#[test] +fn data_too_long_fails() { + ExtBuilder::default().build().execute_with(|| { + let large_data = vec![0xff; 100_001]; // Exceeds MaxDataLength + + let result = BuildEvmTx::build_evm_tx( + RuntimeOrigin::signed(1u64), + None, + 0, + large_data, + 0, + 21000, + 20000000000, + 1000000000, + Vec::new(), + 1, + ); + + assert_eq!(result, Err(Error::::DataTooLong.into())); + }); +} + +#[test] +fn invalid_gas_price_relationship_fails() { + ExtBuilder::default().build().execute_with(|| { + let result = BuildEvmTx::build_evm_tx( + RuntimeOrigin::signed(1u64), + None, + 0, + vec![], + 0, + 21000, + 20000000000, // max_fee_per_gas + 30000000000, // max_priority_fee_per_gas (higher than max_fee) + Vec::new(), + 1, + ); + + assert_eq!(result, Err(Error::::InvalidGasPrice.into())); + }); +} diff --git a/pallets/build-evm-tx/src/tests/validation.rs b/pallets/build-evm-tx/src/tests/validation.rs new file mode 100644 index 0000000000..e6ce505f0c --- /dev/null +++ b/pallets/build-evm-tx/src/tests/validation.rs @@ -0,0 +1,192 @@ +#[cfg(test)] +mod validation { + use super::super::mock::*; + use ethereum::AccessListItem; + use sp_core::{H160, H256}; + + #[test] + fn eip1559_call_basic_no_access_list_matches_reference_rlp() { + ExtBuilder::default().build().execute_with(|| { + let to_address = H160::from([0x11u8; 20]); + let value = 1000000000000000000u128; // 1 ETH + let data = vec![0x12, 0x34, 0x56, 0x78]; + let nonce = 5u64; + let gas_limit = 21000u64; + let max_fee_per_gas = 20000000000u128; // 20 gwei + let max_priority_fee_per_gas = 1000000000u128; // 1 gwei + let chain_id = 1u64; // Ethereum mainnet + + // Pre-computed RLP for EIP-1559 transaction with the above parameters + // This was generated using: + // let tx = EIP1559TransactionMessage { + // chain_id: 1, + // nonce: U256::from(5), + // gas_limit: U256::from(21000), + // max_fee_per_gas: U256::from(20000000000), + // max_priority_fee_per_gas: U256::from(1000000000), + // action: TransactionAction::Call(H160::from([0x11; 20])), + // value: U256::from(1000000000000000000u128), + // input: vec![0x12, 0x34, 0x56, 0x78], + // access_list: vec![], + // }; + // let encoded = rlp::encode(&tx); + // prepend with 0x02 for EIP-1559 + let expected_rlp = vec![ + 0x02, 0xf4, 0x01, 0x05, 0x84, 0x3b, 0x9a, 0xca, 0x00, 0x85, 0x04, 0xa8, 0x17, 0xc8, 0x00, 0x82, 0x52, + 0x08, 0x94, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x88, 0x0d, 0xe0, 0xb6, 0xb3, 0xa7, 0x64, 0x00, 0x00, 0x84, 0x12, 0x34, + 0x56, 0x78, 0xc0, + ]; + + let returned_rlp = BuildEvmTx::build_evm_tx( + RuntimeOrigin::signed(1u64), + Some(to_address), + value, + data, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + Vec::new(), + chain_id, + ) + .expect("Failed to build transaction"); + + assert_eq!( + returned_rlp, + expected_rlp, + "RLP mismatch!\nGot: {:?}\nExpected: {:?}", + hex::encode(&returned_rlp), + hex::encode(&expected_rlp) + ); + }); + } + + #[test] + fn eip1559_contract_creation_with_data_matches_reference_rlp() { + ExtBuilder::default().build().execute_with(|| { + let value = 0u128; + let data = vec![0xab, 0xcd]; + let nonce = 10u64; + let gas_limit = 53000u64; + let max_fee_per_gas = 30_000_000_000u128; // 30 gwei + let max_priority_fee_per_gas = 2_000_000_000u128; // 2 gwei + let chain_id = 1u64; + + // Reference generation (using `ethereum` crate): + // let tx = EIP1559TransactionMessage { + // chain_id: 1, + // nonce: U256::from(10), + // gas_limit: U256::from(53000), + // max_fee_per_gas: U256::from(30_000_000_000u128), + // max_priority_fee_per_gas: U256::from(2_000_000_000u128), + // action: TransactionAction::Create, + // value: U256::from(0u128), + // input: vec![0xab, 0xcd], + // access_list: vec![], + // }; + // let mut expected_rlp = vec![0x02]; + // expected_rlp.extend_from_slice(&rlp::encode(&tx)); + + let expected_rlp = vec![ + 0x02, 0xd6, 0x01, 0x0a, 0x84, 0x77, 0x35, 0x94, 0x00, 0x85, 0x06, 0xfc, 0x23, 0xac, 0x00, 0x82, 0xcf, + 0x08, 0x80, 0x80, 0x82, 0xab, 0xcd, 0xc0, + ]; + + let returned_rlp = BuildEvmTx::build_evm_tx( + RuntimeOrigin::signed(1u64), + None, + value, + data, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + Vec::new(), + chain_id, + ) + .expect("Failed to build transaction"); + + assert_eq!( + returned_rlp, + expected_rlp, + "RLP mismatch!\nGot: {:?}\nExpected: {:?}", + hex::encode(&returned_rlp), + hex::encode(&expected_rlp) + ); + }); + } + + #[test] + fn eip1559_call_with_non_empty_access_list_matches_reference_rlp() { + ExtBuilder::default().build().execute_with(|| { + let to_address = H160::from([0x22u8; 20]); + let value = 1u128; + let data = Vec::new(); + let nonce = 3u64; + let gas_limit = 500_000u64; + let max_fee_per_gas = 40_000_000_000u128; // 40 gwei + let max_priority_fee_per_gas = 1_500_000_000u128; // 1.5 gwei + let chain_id = 1u64; + + let access_list = vec![AccessListItem { + address: H160::from([0x33u8; 20]), + storage_keys: vec![H256::from([0x44u8; 32]), H256::from([0x55u8; 32])], + }]; + + // Reference generation (using `ethereum` crate): + // let tx = EIP1559TransactionMessage { + // chain_id: 1, + // nonce: U256::from(3), + // gas_limit: U256::from(500_000), + // max_fee_per_gas: U256::from(40_000_000_000u128), + // max_priority_fee_per_gas: U256::from(1_500_000_000u128), + // action: TransactionAction::Call(H160::from([0x22; 20])), + // value: U256::from(1u128), + // input: vec![], + // access_list: vec![AccessListItem { + // address: H160::from([0x33; 20]), + // storage_keys: vec![ + // H256::from([0x44; 32]), + // H256::from([0x55; 32]), + // ], + // }], + // }; + // let mut expected_rlp = vec![0x02]; + // expected_rlp.extend_from_slice(&rlp::encode(&tx)); + + let expected_rlp = vec![ + 0x02, 0xF8, 0x85, 0x01, 0x03, 0x84, 0x59, 0x68, 0x2F, 0x00, 0x85, 0x09, 0x50, 0x2F, 0x90, 0x00, 0x83, + 0x07, 0xA1, 0x20, 0x94, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x01, 0x80, 0xF8, 0x5B, 0xF8, 0x59, 0x94, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0xF8, 0x42, 0xA0, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0xA0, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + ]; + + let returned_rlp = BuildEvmTx::build_evm_tx( + RuntimeOrigin::signed(1u64), + Some(to_address), + value, + data, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + access_list, + chain_id, + ) + .expect("Failed to build transaction"); + + assert_eq!( + returned_rlp, + expected_rlp, + "RLP mismatch!\nGot: {:?}\nExpected: {:?}", + hex::encode(&returned_rlp), + hex::encode(&expected_rlp) + ); + }); + } +} diff --git a/pallets/erc20-vault/Cargo.toml b/pallets/erc20-vault/Cargo.toml new file mode 100644 index 0000000000..36a5ff116c --- /dev/null +++ b/pallets/erc20-vault/Cargo.toml @@ -0,0 +1,73 @@ +[package] +name = "pallet-erc20-vault" +version = "1.0.0" +authors = ["Signet"] +edition = "2021" +license = "Apache-2.0" +description = "ERC20 vault pallet for cross-chain token deposits via MPC" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +hex = { version = "0.4", default-features = false, features = ["alloc"] } + +# Ethereum ABI encoding +alloy-sol-types = { version = "=0.7.6", default-features = false, features = ["json"] } +alloy-primitives = { version = "=0.7.7", default-features = false } + +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +pallet-build-evm-tx = { path = "../build-evm-tx", default-features = false } +pallet-signet = { path = "../signet", default-features = false } + +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +pallet-balances = { workspace = true, features = ["std"] } +sp-io = { workspace = true, features = ["std"] } +sp-core = { workspace = true, features = ["std"] } +sp-runtime = { workspace = true, features = ["std"] } +secp256k1 = { version = "0.27", features = ["recovery", "global-context"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "sp-core/std", + "sp-io/std", + "frame-benchmarking?/std", + "alloy-sol-types/std", + "alloy-primitives/std", + "serde/std", + "serde_json/std", + "pallet-build-evm-tx/std", + "pallet-signet/std", + "hex/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] + +[target.'cfg(not(feature = "std"))'.dependencies] +borsh = { version = "1.5", default-features = false, features = ["derive", "hashbrown"] } + +[target.'cfg(feature = "std")'.dependencies] +borsh = { version = "1.5", default-features = false, features = ["derive", "std"] } \ No newline at end of file diff --git a/pallets/erc20-vault/src/lib.rs b/pallets/erc20-vault/src/lib.rs new file mode 100644 index 0000000000..b802857565 --- /dev/null +++ b/pallets/erc20-vault/src/lib.rs @@ -0,0 +1,444 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +use alloc::{format, string::String, vec}; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::pallet_prelude::*; +use frame_support::traits::Currency; +use frame_support::PalletId; +use frame_support::{dispatch::DispatchResult, pallet_prelude::Weight, BoundedVec}; +use frame_system::pallet_prelude::*; +use sp_core::H160; +use sp_runtime::traits::{AccountIdConversion, Saturating, Zero}; +use sp_std::vec::Vec; + +const MAX_SERIALIZED_OUTPUT_LENGTH: u32 = 65536; + +#[cfg(test)] +mod tests; + +pub use pallet::*; + +pub const SEPOLIA_VAULT_ADDRESS: [u8; 20] = [ + 0x00, 0xA4, 0x0C, 0x26, 0x61, 0x29, 0x3d, 0x51, 0x34, 0xE5, 0x3D, 0xa5, 0x29, 0x51, 0xA3, 0xF7, 0x76, 0x78, 0x36, + 0xEf, +]; + +// ERC20 ABI definition +use alloy_primitives::{Address, U256}; +use alloy_sol_types::{sol, SolCall}; + +sol! { + #[sol(abi)] + interface IERC20 { + function transfer(address to, uint256 amount) external returns (bool); + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use sp_io::hashing; + + // ========================= Configuration ========================= + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_build_evm_tx::Config + pallet_signet::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type VaultPalletId: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); + + // ========================= Storage ========================= + + /// Global vault configuration + #[pallet::storage] + #[pallet::getter(fn vault_config)] + pub type VaultConfig = StorageValue<_, VaultConfigData, OptionQuery>; + + /// Pending deposits awaiting signature + #[pallet::storage] + #[pallet::getter(fn pending_deposits)] + pub type PendingDeposits = StorageMap< + _, + Blake2_128Concat, + [u8; 32], // request_id + PendingDepositData, + OptionQuery, + >; + + /// User ERC20 balances + #[pallet::storage] + #[pallet::getter(fn user_balances)] + pub type UserBalances = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + [u8; 20], // ERC20 address + u128, + ValueQuery, + >; + + // ========================= Types ========================= + + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, MaxEncodedLen)] + pub struct VaultConfigData { + pub mpc_root_signer_address: [u8; 20], + } + + #[derive(Encode, Decode, TypeInfo, Clone, Debug, MaxEncodedLen)] + pub struct PendingDepositData { + pub requester: AccountId, + pub amount: u128, + pub erc20_address: [u8; 20], + pub path: BoundedVec>, + } + + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq)] + pub struct EvmTransactionParams { + pub value: u128, + pub gas_limit: u64, + pub max_fee_per_gas: u128, + pub max_priority_fee_per_gas: u128, + pub nonce: u64, + pub chain_id: u64, + } + + // ========================= Events ========================= + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + VaultInitialized { + mpc_address: [u8; 20], + initialized_by: T::AccountId, + }, + DepositRequested { + request_id: [u8; 32], + requester: T::AccountId, + erc20_address: [u8; 20], + amount: u128, + }, + DepositClaimed { + request_id: [u8; 32], + claimer: T::AccountId, + erc20_address: [u8; 20], + amount: u128, + }, + } + + // ========================= Errors ========================= + + #[pallet::error] + pub enum Error { + NotInitialized, + AlreadyInitialized, + InvalidRequestId, + DepositNotFound, + UnauthorizedClaimer, + InvalidSignature, + InvalidSigner, + InvalidOutput, + TransferFailed, + Overflow, + InvalidAbi, + SerializationError, + PathTooLong, + PalletAccountNotFunded, + } + + // ========================= Hooks ========================= + + #[pallet::hooks] + impl Hooks> for Pallet {} + + // ========================= Extrinsics ========================= + + #[pallet::call] + impl Pallet { + /// Initialize the vault with MPC signer address + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(10_000, 0))] + pub fn initialize(origin: OriginFor, mpc_root_signer_address: [u8; 20]) -> DispatchResult { + let initializer = ensure_signed(origin)?; + ensure!(VaultConfig::::get().is_none(), Error::::AlreadyInitialized); + + VaultConfig::::put(VaultConfigData { + mpc_root_signer_address, + }); + + Self::deposit_event(Event::VaultInitialized { + mpc_address: mpc_root_signer_address, + initialized_by: initializer, + }); + + Ok(()) + } + + /// Request to deposit ERC20 tokens + /// Note: The pallet account must be funded before calling this + #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(100_000, 0))] + pub fn deposit_erc20( + origin: OriginFor, + request_id: [u8; 32], + erc20_address: [u8; 20], + amount: u128, + tx_params: EvmTransactionParams, + ) -> DispatchResult { + let requester = ensure_signed(origin)?; + + // Ensure vault is initialized + ensure!(VaultConfig::::get().is_some(), Error::::NotInitialized); + + // Ensure no duplicate request + ensure!( + PendingDeposits::::get(&request_id).is_none(), + Error::::InvalidRequestId + ); + + // Get signet deposit amount and pallet account + let signet_deposit = pallet_signet::Pallet::::signature_deposit(); + let pallet_account = Self::account_id(); + let existential_deposit = ::Currency::minimum_balance(); + + // Ensure pallet account has sufficient balance + // It needs at least ED + signet_deposit to transfer signet_deposit while staying alive + let pallet_balance = ::Currency::free_balance(&pallet_account); + let required_balance = existential_deposit.saturating_add(signet_deposit); + ensure!(pallet_balance >= required_balance, Error::::PalletAccountNotFunded); + + // Transfer signet deposit from requester to pallet account + ::Currency::transfer( + &requester, + &pallet_account, + signet_deposit, + frame_support::traits::ExistenceRequirement::AllowDeath, + )?; + + // Use requester account as path + let path = { + let encoded = requester.encode(); + format!("0x{}", hex::encode(encoded)).into_bytes() + }; + + let recipient = Address::from_slice(&SEPOLIA_VAULT_ADDRESS); + let call = IERC20::transferCall { + to: recipient, + amount: U256::from(amount), + }; + + // Build EVM transaction + let rlp_encoded = pallet_build_evm_tx::Pallet::::build_evm_tx( + frame_system::RawOrigin::Signed(requester.clone()).into(), + Some(H160::from(erc20_address)), + tx_params.value, + call.abi_encode(), + tx_params.nonce, + tx_params.gas_limit, + tx_params.max_fee_per_gas, + tx_params.max_priority_fee_per_gas, + vec![], + 11155111, + )?; + + // Generate and verify request ID + let computed_request_id = Self::generate_request_id( + &Self::account_id(), + &rlp_encoded, + "eip155:11155111", + 0, + &path, + b"ecdsa", + b"ethereum", + b"", + ); + + ensure!(computed_request_id == request_id, Error::::InvalidRequestId); + + // Store pending deposit + PendingDeposits::::insert( + &request_id, + PendingDepositData { + requester: requester.clone(), + amount, + erc20_address, + path: path.clone().try_into().map_err(|_| Error::::PathTooLong)?, + }, + ); + + // Create schemas for the response + let functions = IERC20::abi::functions(); + let transfer_func = functions + .get("transfer") + .and_then(|funcs| funcs.first()) + .ok_or(Error::::InvalidAbi)?; + + let explorer_schema = + serde_json::to_vec(&transfer_func.outputs).map_err(|_| Error::::SerializationError)?; + + let callback_schema = + serde_json::to_vec(&serde_json::json!("bool")).map_err(|_| Error::::SerializationError)?; + + // Call sign_bidirectional from the pallet account + pallet_signet::Pallet::::sign_bidirectional( + frame_system::RawOrigin::Signed(Self::account_id()).into(), + BoundedVec::try_from(rlp_encoded).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(b"eip155:11155111".to_vec()).map_err(|_| Error::::SerializationError)?, // CAIP-2 format for Sepolia + 0, // key_version + BoundedVec::try_from(path).map_err(|_| Error::::PathTooLong)?, + BoundedVec::try_from(b"ecdsa".to_vec()).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(b"ethereum".to_vec()).map_err(|_| Error::::SerializationError)?, + BoundedVec::try_from(vec![]).map_err(|_| Error::::SerializationError)?, + Self::account_id(), // program_id (use pallet account) + BoundedVec::try_from(explorer_schema).map_err(|_| Error::::SerializationError)?, // output_deserialization_schema + BoundedVec::try_from(callback_schema).map_err(|_| Error::::SerializationError)?, // respond_serialization_schema + )?; + + Self::deposit_event(Event::DepositRequested { + request_id, + requester, + erc20_address, + amount, + }); + + Ok(()) + } + + /// Claim deposited ERC20 tokens after signature verification + #[pallet::call_index(2)] + #[pallet::weight(Weight::from_parts(50_000, 0))] + pub fn claim_erc20( + origin: OriginFor, + request_id: [u8; 32], + serialized_output: BoundedVec>, + signature: pallet_signet::Signature, + ) -> DispatchResult { + let claimer = ensure_signed(origin)?; + + // Get pending deposit + let pending = PendingDeposits::::get(&request_id).ok_or(Error::::DepositNotFound)?; + + // Verify claimer is the original requester + ensure!(pending.requester == claimer, Error::::UnauthorizedClaimer); + + // Get vault config + let config = VaultConfig::::get().ok_or(Error::::NotInitialized)?; + + // Verify signature + let message_hash = Self::hash_message(&request_id, &serialized_output); + Self::verify_signature_from_address(&message_hash, &signature, &config.mpc_root_signer_address)?; + + // Check for error magic prefix + const ERROR_PREFIX: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF]; + + let success = if serialized_output.len() >= 4 && &serialized_output[..4] == ERROR_PREFIX { + false + } else { + // Decode boolean (Borsh serialized) + use borsh::BorshDeserialize; + bool::try_from_slice(&serialized_output).map_err(|_| Error::::InvalidOutput)? + }; + + ensure!(success, Error::::TransferFailed); + + // Update user balance + UserBalances::::mutate(&claimer, &pending.erc20_address, |balance| -> DispatchResult { + *balance = balance.checked_add(pending.amount).ok_or(Error::::Overflow)?; + Ok(()) + })?; + + // Clean up storage + PendingDeposits::::remove(&request_id); + + Self::deposit_event(Event::DepositClaimed { + request_id, + claimer, + erc20_address: pending.erc20_address, + amount: pending.amount, + }); + + Ok(()) + } + } + + // ========================= Helper Functions ========================= + + impl Pallet { + fn generate_request_id( + sender: &T::AccountId, + transaction_data: &[u8], + caip2_id: &str, + key_version: u32, + path: &[u8], + algo: &[u8], + dest: &[u8], + params: &[u8], + ) -> [u8; 32] { + use alloy_sol_types::SolValue; + use sp_core::crypto::Ss58Codec; + + let encoded = sender.encode(); + let mut account_bytes = [0u8; 32]; + let len = encoded.len().min(32); + account_bytes[..len].copy_from_slice(&encoded[..len]); + + let account_id32 = sp_runtime::AccountId32::from(account_bytes); + let sender_ss58 = account_id32.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(0)); + + let encoded = ( + sender_ss58.as_str(), + transaction_data, + caip2_id, + key_version, + core::str::from_utf8(path).unwrap_or(""), + core::str::from_utf8(algo).unwrap_or(""), + core::str::from_utf8(dest).unwrap_or(""), + core::str::from_utf8(params).unwrap_or(""), + ) + .abi_encode_packed(); + + sp_io::hashing::keccak_256(&encoded) + } + + fn hash_message(request_id: &[u8; 32], output: &[u8]) -> [u8; 32] { + let mut data = Vec::with_capacity(32 + output.len()); + data.extend_from_slice(request_id); + data.extend_from_slice(output); + hashing::keccak_256(&data) + } + + fn verify_signature_from_address( + message_hash: &[u8; 32], + signature: &pallet_signet::Signature, + expected_address: &[u8; 20], + ) -> DispatchResult { + ensure!(signature.recovery_id < 4, Error::::InvalidSignature); + + let mut sig_bytes = [0u8; 65]; + sig_bytes[..32].copy_from_slice(&signature.big_r.x); + sig_bytes[32..64].copy_from_slice(&signature.s); + sig_bytes[64] = signature.recovery_id; + + let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig_bytes, message_hash) + .map_err(|_| Error::::InvalidSignature)?; + + let pubkey_hash = hashing::keccak_256(&pubkey); + let recovered_address = &pubkey_hash[12..]; + + ensure!(recovered_address == expected_address, Error::::InvalidSigner); + + Ok(()) + } + } + + impl Pallet { + pub fn account_id() -> T::AccountId { + T::VaultPalletId::get().into_account_truncating() + } + } +} diff --git a/pallets/erc20-vault/src/tests.rs b/pallets/erc20-vault/src/tests.rs new file mode 100644 index 0000000000..9f79c40643 --- /dev/null +++ b/pallets/erc20-vault/src/tests.rs @@ -0,0 +1,752 @@ +use crate::{self as pallet_erc20_vault, *}; +use alloy_primitives::{Address, U256}; +use alloy_sol_types::SolCall; +use codec::Encode; +use frame_support::{assert_noop, assert_ok, parameter_types, traits::Currency as CurrencyTrait, PalletId}; +use frame_system as system; +use secp256k1::{Message, PublicKey, Secp256k1, SecretKey}; +use sp_core::H256; +use sp_io::hashing::keccak_256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +extern crate alloc; + +// Test secret key for signing +fn get_test_secret_key() -> SecretKey { + SecretKey::from_slice(&[42u8; 32]).expect("Valid secret key") +} + +fn bounded_u8(v: Vec) -> BoundedVec> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_chain_id(v: Vec) -> BoundedVec { + BoundedVec::try_from(v).unwrap() +} + +// Get public key from secret key +fn get_test_public_key() -> PublicKey { + let secp = Secp256k1::new(); + let secret_key = get_test_secret_key(); + PublicKey::from_secret_key(&secp, &secret_key) +} + +fn public_key_to_eth_address(public_key: &PublicKey) -> [u8; 20] { + // Get uncompressed public key (65 bytes: 0x04 + x + y) + let uncompressed = public_key.serialize_uncompressed(); + // Skip the 0x04 prefix byte and hash the remaining 64 bytes + let hash = keccak_256(&uncompressed[1..]); + // Take the last 20 bytes as Ethereum address + let mut address = [0u8; 20]; + address.copy_from_slice(&hash[12..]); + address +} + +// Create a valid signature for testing using secp256k1 directly +fn create_valid_signature(message_hash: &[u8; 32]) -> pallet_signet::Signature { + let secp = Secp256k1::new(); + let secret_key = get_test_secret_key(); + let message = Message::from_slice(message_hash).expect("Valid message hash"); + + // Sign without hashing (message is already hashed) + let sig = secp.sign_ecdsa_recoverable(&message, &secret_key); + let (recovery_id, sig_bytes) = sig.serialize_compact(); + + // Extract r and s + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&sig_bytes[0..32]); + s.copy_from_slice(&sig_bytes[32..64]); + + pallet_signet::Signature { + big_r: pallet_signet::AffinePoint { + x: r, + y: [0u8; 32], // y-coordinate not used in recovery + }, + s, + recovery_id: recovery_id.to_i32() as u8, + } +} + +// Mock runtime construction +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + Balances: pallet_balances, + Signet: pallet_signet, + BuildEvmTx: pallet_build_evm_tx, + Erc20Vault: pallet_erc20_vault, + } +); + +// System config +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = frame_system::mocking::MockBlock; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +// Balances config +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +impl pallet_balances::Config for Test { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); +} + +// Signet config +parameter_types! { + pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); + pub const MaxChainIdLength: u32 = 128; +} + +impl pallet_signet::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type PalletId = SignetPalletId; + type MaxChainIdLength = MaxChainIdLength; + type WeightInfo = (); +} + +// Build EVM TX mock config +parameter_types! { + pub const MaxDataLength: u32 = 1024; +} + +impl pallet_build_evm_tx::Config for Test { + type MaxDataLength = MaxDataLength; +} + +parameter_types! { + pub const Erc20VaultPalletId: PalletId = PalletId(*b"py/erc20"); +} + +// ERC20 Vault config +impl pallet_erc20_vault::Config for Test { + type RuntimeEvent = RuntimeEvent; + type VaultPalletId = Erc20VaultPalletId; +} + +// Helper to build test externalities +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + // Fund test accounts using Currency trait + let _ = >::deposit_creating(&1, 1_000_000); + let _ = >::deposit_creating(&2, 1_000_000); + let _ = >::deposit_creating(&3, 100); + // Initialize signet pallet for tests that need it + let _ = pallet_signet::Pallet::::initialize( + RuntimeOrigin::signed(1), + 1, // admin + 100, // deposit + bounded_chain_id(b"test-chain".to_vec()), + ); + let pallet_account = Erc20Vault::account_id(); + let _ = >::deposit_creating(&pallet_account, 10_000); + }); + ext +} + +// Helper to create test signature (dummy, for invalid signature tests) +fn create_test_signature() -> pallet_signet::Signature { + pallet_signet::Signature { + big_r: pallet_signet::AffinePoint { + x: [1u8; 32], + y: [2u8; 32], + }, + s: [3u8; 32], + recovery_id: 0, + } +} + +// Helper to create valid EVM transaction params +fn create_test_tx_params() -> EvmTransactionParams { + EvmTransactionParams { + value: 0, + gas_limit: 100_000, + max_fee_per_gas: 30_000_000_000, + max_priority_fee_per_gas: 1_000_000_000, + nonce: 0, + chain_id: 1, + } +} + +// Helper to create test addresses +fn create_test_erc20_address() -> [u8; 20] { + [1u8; 20] // Mock USDC address +} + +fn create_test_mpc_address() -> [u8; 20] { + // Use the address derived from our test public key + let public_key = get_test_public_key(); + public_key_to_eth_address(&public_key) +} + +fn compute_request_id( + requester: u64, + erc20_address: [u8; 20], + amount: u128, + tx_params: &EvmTransactionParams, +) -> [u8; 32] { + use alloy_sol_types::SolValue; + use sp_core::crypto::Ss58Codec; + + let recipient = Address::from_slice(&crate::SEPOLIA_VAULT_ADDRESS); + let call = crate::IERC20::transferCall { + to: recipient, + amount: U256::from(amount), + }; + + let rlp_encoded = pallet_build_evm_tx::Pallet::::build_evm_tx( + frame_system::RawOrigin::Signed(requester).into(), + Some(sp_core::H160::from(erc20_address)), + tx_params.value, + call.abi_encode(), + tx_params.nonce, + tx_params.gas_limit, + tx_params.max_fee_per_gas, + tx_params.max_priority_fee_per_gas, + vec![], + 11155111, + ) + .expect("build_evm_tx should succeed"); + + // Use PALLET account as sender (not requester) + let pallet_account = Erc20Vault::account_id(); + + let encoded = pallet_account.encode(); + let mut account_bytes = [0u8; 32]; + let len = encoded.len().min(32); + account_bytes[..len].copy_from_slice(&encoded[..len]); + + let account_id32 = sp_runtime::AccountId32::from(account_bytes); + let sender_ss58 = account_id32.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(0)); + + // Path uses requester (this is correct) + let path = format!("0x{}", hex::encode(requester.encode())); + + let encoded = ( + sender_ss58.as_str(), + rlp_encoded.as_slice(), + "eip155:11155111", + 0u32, + path.as_str(), + "ecdsa", + "ethereum", + "", + ) + .abi_encode_packed(); + + keccak_256(&encoded) +} + +// ======================================== +// INITIALIZATION TESTS +// ======================================== + +#[test] +fn test_initialize_works() { + new_test_ext().execute_with(|| { + let initializer = 2u64; + let mpc_address = create_test_mpc_address(); + + // Vault should not be initialized yet + assert_eq!(Erc20Vault::vault_config(), None); + + // Anyone can initialize + assert_ok!(Erc20Vault::initialize(RuntimeOrigin::signed(initializer), mpc_address)); + + // Check storage + assert_eq!( + Erc20Vault::vault_config(), + Some(VaultConfigData { + mpc_root_signer_address: mpc_address + }) + ); + + // Check event + System::assert_last_event( + Event::VaultInitialized { + mpc_address, + initialized_by: initializer, + } + .into(), + ); + }); +} + +#[test] +fn test_cannot_initialize_twice() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + + // First initialization succeeds + assert_ok!(Erc20Vault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + // Second initialization fails + assert_noop!( + Erc20Vault::initialize( + RuntimeOrigin::signed(2), + [4u8; 20] // Different address + ), + Error::::AlreadyInitialized + ); + + // Config remains unchanged + assert_eq!( + Erc20Vault::vault_config(), + Some(VaultConfigData { + mpc_root_signer_address: mpc_address + }) + ); + }); +} + +#[test] +fn test_any_account_can_initialize() { + new_test_ext().execute_with(|| { + let random_account = 3u64; + let mpc_address = create_test_mpc_address(); + + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(random_account), + mpc_address + )); + + System::assert_last_event( + Event::VaultInitialized { + mpc_address, + initialized_by: random_account, + } + .into(), + ); + }); +} + +// ======================================== +// DEPOSIT TESTS +// ======================================== + +#[test] +fn test_deposit_erc20_fails_without_initialization() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + System::reset_events(); + + let requester = 1u64; + let request_id = [1u8; 32]; + + assert_noop!( + Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + create_test_erc20_address(), + 1_000_000u128, + create_test_tx_params(), + ), + Error::::NotInitialized + ); + }); +} + +#[test] +fn test_deposit_erc20_success() { + new_test_ext().execute_with(|| { + // Initialize vault + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let requester = 2u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute the correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + let balance_before = >::free_balance(&requester); + + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params, + )); + + // Check pending deposit was stored + let pending = Erc20Vault::pending_deposits(&request_id); + assert!(pending.is_some()); + let pending = pending.unwrap(); + assert_eq!(pending.requester, requester); + assert_eq!(pending.amount, amount); + assert_eq!(pending.erc20_address, erc20_address); + + // Check deposit event was emitted + let events = System::events(); + assert!(events.iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::Erc20Vault(Event::DepositRequested { + request_id: rid, + requester: req, + erc20_address: erc20, + amount: amt, + }) if rid == &request_id + && req == &requester + && erc20 == &erc20_address + && amt == &amount + ) + })); + + // Check signet event was emitted + assert!(events.iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::Signet(pallet_signet::Event::SignBidirectionalRequested { .. }) + ) + })); + + // Check deposit was taken (signet deposit) + assert_eq!( + >::free_balance(&requester), + balance_before - 100 // 100 for signet deposit + ); + }); +} + +#[test] +fn test_deposit_with_invalid_request_id_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let requester = 2u64; + let wrong_request_id = [99u8; 32]; + + assert_noop!( + Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + wrong_request_id, + create_test_erc20_address(), + 1_000_000u128, + create_test_tx_params(), + ), + Error::::InvalidRequestId + ); + + assert!(Erc20Vault::pending_deposits(&wrong_request_id).is_none()); + }); +} + +#[test] +fn test_deposit_duplicate_request_id_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let requester = 2u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + + // First deposit succeeds + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params.clone(), + )); + + // Second deposit with same request ID fails + assert_noop!( + Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + 2_000_000u128, // Different amount + tx_params, + ), + Error::::InvalidRequestId + ); + }); +} + +// ======================================== +// CLAIM TESTS +// ======================================== + +#[test] +fn test_claim_nonexistent_deposit_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let claimer = 2u64; + let request_id = [99u8; 32]; + + assert_noop!( + Erc20Vault::claim_erc20( + RuntimeOrigin::signed(claimer), + request_id, + bounded_u8::<65536>(vec![1u8]), + create_test_signature(), + ), + Error::::DepositNotFound + ); + }); +} + +#[test] +fn test_claim_by_non_requester_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let requester = 2u64; + let wrong_claimer = 1u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + + // Create deposit + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params, + )); + + // Try to claim with different account + assert_noop!( + Erc20Vault::claim_erc20( + RuntimeOrigin::signed(wrong_claimer), + request_id, + bounded_u8::<65536>(vec![1u8]), + create_test_signature(), + ), + Error::::UnauthorizedClaimer + ); + }); +} + +#[test] +fn test_claim_with_invalid_signature_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Erc20Vault::initialize( + RuntimeOrigin::signed(1), + create_test_mpc_address() + )); + + let requester = 2u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params, + )); + + // Create invalid signature + let mut bad_signature = create_test_signature(); + bad_signature.recovery_id = 5; + + assert_noop!( + Erc20Vault::claim_erc20( + RuntimeOrigin::signed(requester), + request_id, + bounded_u8::<65536>(vec![1u8]), + bad_signature, + ), + Error::::InvalidSignature + ); + }); +} + +#[test] +fn test_claim_with_error_response_fails() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + assert_ok!(Erc20Vault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + let requester = 2u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params, + )); + + // Error response with magic prefix + let error_output = vec![0xDE, 0xAD, 0xBE, 0xEF, 1, 2, 3]; + + // Create valid signature for the error response + let message_hash = { + let mut data = Vec::with_capacity(32 + error_output.len()); + data.extend_from_slice(&request_id); + data.extend_from_slice(&error_output); + keccak_256(&data) + }; + + let valid_signature = create_valid_signature(&message_hash); + + // Should fail with TransferFailed because error prefix is detected + assert_noop!( + Erc20Vault::claim_erc20( + RuntimeOrigin::signed(requester), + request_id, + bounded_u8::<65536>(error_output), + valid_signature, + ), + Error::::TransferFailed + ); + + // Balance should not change + assert_eq!(Erc20Vault::user_balances(requester, erc20_address), 0); + }); +} + +#[test] +fn test_claim_successful_with_valid_signature() { + new_test_ext().execute_with(|| { + let mpc_address = create_test_mpc_address(); + assert_ok!(Erc20Vault::initialize(RuntimeOrigin::signed(1), mpc_address)); + + let requester = 2u64; + let erc20_address = create_test_erc20_address(); + let amount = 1_000_000u128; + let tx_params = create_test_tx_params(); + + // Compute correct request ID + let request_id = compute_request_id(requester, erc20_address, amount, &tx_params); + + assert_ok!(Erc20Vault::deposit_erc20( + RuntimeOrigin::signed(requester), + request_id, + erc20_address, + amount, + tx_params, + )); + + // Success response: Borsh-encoded true (1u8) + let success_output = vec![1u8]; + + // Create valid signature + let message_hash = { + let mut data = Vec::with_capacity(32 + success_output.len()); + data.extend_from_slice(&request_id); + data.extend_from_slice(&success_output); + keccak_256(&data) + }; + + let valid_signature = create_valid_signature(&message_hash); + + // Initial balance should be 0 + assert_eq!(Erc20Vault::user_balances(requester, erc20_address), 0); + + // Claim should succeed + assert_ok!(Erc20Vault::claim_erc20( + RuntimeOrigin::signed(requester), + request_id, + bounded_u8::<65536>(success_output), + valid_signature, + ),); + + // Balance should be updated + assert_eq!(Erc20Vault::user_balances(requester, erc20_address), amount); + + // Pending deposit should be removed + assert!(Erc20Vault::pending_deposits(&request_id).is_none()); + + // Check event was emitted + System::assert_has_event( + Event::DepositClaimed { + request_id, + claimer: requester, + erc20_address, + amount, + } + .into(), + ); + }); +} diff --git a/pallets/signet/Cargo.toml b/pallets/signet/Cargo.toml new file mode 100644 index 0000000000..3fe90bfe27 --- /dev/null +++ b/pallets/signet/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "pallet-signet" +version = "1.0.0" +authors = ["Signet"] +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/galacticcouncil/hydradx-node" +description = "Signet event emitter pallet" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +# Optional imports for benchmarking +frame-benchmarking = { workspace = true, optional = true } +sp-io = { workspace = true, optional = true } + +[dev-dependencies] +sp-io = { workspace = true } +sp-core = { workspace = true } +pallet-balances = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-benchmarking?/std", + "pallet-balances/std" +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-io", +] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/pallets/signet/src/benchmarks.rs b/pallets/signet/src/benchmarks.rs new file mode 100644 index 0000000000..a0f840de2f --- /dev/null +++ b/pallets/signet/src/benchmarks.rs @@ -0,0 +1,49 @@ +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::traits::Currency; +use frame_system::RawOrigin; + +benchmarks! { + initialize { + let admin: T::AccountId = whitelisted_caller(); + let deposit = T::Currency::minimum_balance(); + let chain_id = b"test-chain".to_vec(); + }: _(RawOrigin::Root, admin.clone(), deposit, chain_id) + verify { + assert_eq!(Admin::::get(), Some(admin)); + assert_eq!(SignatureDeposit::::get(), deposit); + } + + update_deposit { + let admin: T::AccountId = whitelisted_caller(); + let initial_deposit = T::Currency::minimum_balance(); + let chain_id = b"test-chain".to_vec(); + let _ = Pallet::::initialize(RawOrigin::Root.into(), admin.clone(), initial_deposit, chain_id); + let new_deposit = T::Currency::minimum_balance() * 2u32.into(); + }: _(RawOrigin::Signed(admin), new_deposit) + verify { + assert_eq!(SignatureDeposit::::get(), new_deposit); + } + + withdraw_funds { + let admin: T::AccountId = whitelisted_caller(); + let chain_id = b"test-chain".to_vec(); + let _ = Pallet::::initialize(RawOrigin::Root.into(), admin.clone(), T::Currency::minimum_balance(), chain_id); + + // Fund the pallet account + let pallet_account = Pallet::::account_id(); + let amount = T::Currency::minimum_balance() * 100u32.into(); + let _ = T::Currency::deposit_creating(&pallet_account, amount); + + let recipient: T::AccountId = whitelisted_caller(); + let withdraw_amount = T::Currency::minimum_balance() * 50u32.into(); + }: _(RawOrigin::Signed(admin), recipient.clone(), withdraw_amount) + verify { + // Verify funds were transferred + assert!(T::Currency::free_balance(&recipient) >= withdraw_amount); + } + + impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/pallets/signet/src/lib.rs b/pallets/signet/src/lib.rs new file mode 100644 index 0000000000..43e689dc55 --- /dev/null +++ b/pallets/signet/src/lib.rs @@ -0,0 +1,461 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + pallet_prelude::*, + traits::{Currency, ExistenceRequirement}, + PalletId, +}; +use frame_system::pallet_prelude::*; +use sp_runtime::traits::AccountIdConversion; +use sp_std::vec::Vec; + +pub use pallet::*; + +// Type alias for cleaner code +type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; + +/// Maximum length for paths, algorithms, destinations, and parameters +const MAX_PATH_LENGTH: u32 = 256; +const MAX_ALGO_LENGTH: u32 = 32; +const MAX_DEST_LENGTH: u32 = 64; +const MAX_PARAMS_LENGTH: u32 = 1024; + +/// Maximum length for transaction data and serialized outputs +const MAX_TRANSACTION_LENGTH: u32 = 65536; // 64 KB +const MAX_SERIALIZED_OUTPUT_LENGTH: u32 = 65536; // 64 KB + +/// Maximum length for schemas and error messages +const MAX_SCHEMA_LENGTH: u32 = 4096; // 4 KB +const MAX_ERROR_MESSAGE_LENGTH: u32 = 1024; + +/// Maximum batch sizes +const MAX_BATCH_SIZE: u32 = 100; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarks; + +pub mod weights; +pub use weights::WeightInfo; + +#[cfg(test)] +mod tests; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Currency for handling deposits and fees + type Currency: Currency; + + /// The pallet's unique ID for deriving its account + #[pallet::constant] + type PalletId: Get; + + /// Maximum length for chain ID + #[pallet::constant] + type MaxChainIdLength: Get; + + type WeightInfo: WeightInfo; + } + + // ======================================== + // Types + // ======================================== + + /// Serialization format enum + #[derive(Encode, Decode, TypeInfo, Clone, Copy, Debug, PartialEq, Eq)] + pub enum SerializationFormat { + Borsh = 0, + AbiJson = 1, + } + + /// Affine point for signatures + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq)] + pub struct AffinePoint { + pub x: [u8; 32], + pub y: [u8; 32], + } + + /// Signature structure + #[derive(Encode, Decode, TypeInfo, Clone, Debug, PartialEq, Eq)] + pub struct Signature { + pub big_r: AffinePoint, + pub s: [u8; 32], + pub recovery_id: u8, + } + + /// Error response structure + #[derive(Encode, Decode, TypeInfo, Debug, Clone, PartialEq, Eq)] + pub struct ErrorResponse { + pub request_id: [u8; 32], + pub error_message: BoundedVec>, + } + + // ======================================== + // Storage + // ======================================== + + /// The admin account that controls this pallet + #[pallet::storage] + #[pallet::getter(fn admin)] + pub type Admin = StorageValue<_, T::AccountId>; + + /// The amount required as deposit for signature requests + #[pallet::storage] + #[pallet::getter(fn signature_deposit)] + pub type SignatureDeposit = StorageValue<_, BalanceOf, ValueQuery>; + + /// The CAIP-2 chain identifier + #[pallet::storage] + #[pallet::getter(fn chain_id)] + pub type ChainId = StorageValue<_, BoundedVec, ValueQuery>; + + // ======================================== + // Events + // ======================================== + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Pallet has been initialized with an admin + Initialized { + admin: T::AccountId, + signature_deposit: BalanceOf, + chain_id: Vec, + }, + + /// Signature deposit amount has been updated + DepositUpdated { + old_deposit: BalanceOf, + new_deposit: BalanceOf, + }, + + /// Funds have been withdrawn from the pallet + FundsWithdrawn { + amount: BalanceOf, + recipient: T::AccountId, + }, + + /// A signature has been requested + SignatureRequested { + sender: T::AccountId, + payload: [u8; 32], + key_version: u32, + deposit: BalanceOf, + chain_id: Vec, + path: Vec, + algo: Vec, + dest: Vec, + params: Vec, + }, + + /// Sign bidirectional request event + SignBidirectionalRequested { + sender: T::AccountId, + serialized_transaction: Vec, + caip2_id: Vec, + key_version: u32, + deposit: BalanceOf, + path: Vec, + algo: Vec, + dest: Vec, + params: Vec, + program_id: T::AccountId, + output_deserialization_schema: Vec, + respond_serialization_schema: Vec, + }, + + /// Signature response event + SignatureResponded { + request_id: [u8; 32], + responder: T::AccountId, + signature: Signature, + }, + + /// Signature error event + SignatureError { + request_id: [u8; 32], + responder: T::AccountId, + error: Vec, + }, + + /// Read response event + RespondBidirectionalEvent { + request_id: [u8; 32], + responder: T::AccountId, + serialized_output: Vec, + signature: Signature, + }, + } + + // ======================================== + // Errors + // ======================================== + + #[pallet::error] + pub enum Error { + /// The pallet has already been initialized + AlreadyInitialized, + /// The pallet has not been initialized yet + NotInitialized, + /// Unauthorized - caller is not admin + Unauthorized, + /// Insufficient funds for withdrawal + InsufficientFunds, + /// Invalid transaction data (empty) + InvalidTransaction, + /// Arrays must have the same length + InvalidInputLength, + /// The chain ID is too long + ChainIdTooLong, + } + + // ======================================== + // Extrinsics + // ======================================== + + #[pallet::call] + impl Pallet { + /// Initialize the pallet with admin, deposit, and chain ID + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::initialize())] + pub fn initialize( + origin: OriginFor, + admin: T::AccountId, + signature_deposit: BalanceOf, + chain_id: BoundedVec, + ) -> DispatchResult { + let _initializer = ensure_signed(origin)?; + ensure!(Admin::::get().is_none(), Error::::AlreadyInitialized); + + Admin::::put(&admin); + SignatureDeposit::::put(signature_deposit); + + let bounded_chain_id = BoundedVec::::try_from(chain_id.clone()) + .map_err(|_| Error::::ChainIdTooLong)?; + ChainId::::put(bounded_chain_id); + + Self::deposit_event(Event::Initialized { + admin, + signature_deposit, + chain_id: chain_id.to_vec(), + }); + + Ok(()) + } + + /// Update the signature deposit amount (admin only) + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::update_deposit())] + pub fn update_deposit(origin: OriginFor, new_deposit: BalanceOf) -> DispatchResult { + let who = ensure_signed(origin)?; + let admin = Admin::::get().ok_or(Error::::NotInitialized)?; + ensure!(who == admin, Error::::Unauthorized); + + let old_deposit = SignatureDeposit::::get(); + SignatureDeposit::::put(new_deposit); + + Self::deposit_event(Event::DepositUpdated { + old_deposit, + new_deposit, + }); + + Ok(()) + } + + /// Withdraw funds from the pallet account (admin only) + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::withdraw_funds())] + pub fn withdraw_funds(origin: OriginFor, recipient: T::AccountId, amount: BalanceOf) -> DispatchResult { + let who = ensure_signed(origin)?; + let admin = Admin::::get().ok_or(Error::::NotInitialized)?; + ensure!(who == admin, Error::::Unauthorized); + + let pallet_account = Self::account_id(); + let pallet_balance = T::Currency::free_balance(&pallet_account); + ensure!(pallet_balance >= amount, Error::::InsufficientFunds); + + T::Currency::transfer(&pallet_account, &recipient, amount, ExistenceRequirement::AllowDeath)?; + + Self::deposit_event(Event::FundsWithdrawn { amount, recipient }); + + Ok(()) + } + + /// Request a signature for a payload + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::sign())] + pub fn sign( + origin: OriginFor, + payload: [u8; 32], + key_version: u32, + path: BoundedVec>, + algo: BoundedVec>, + dest: BoundedVec>, + params: BoundedVec>, + ) -> DispatchResult { + let requester = ensure_signed(origin)?; + + // Ensure initialized + ensure!(Admin::::get().is_some(), Error::::NotInitialized); + + // Get deposit amount + let deposit = SignatureDeposit::::get(); + + // Transfer deposit from requester to pallet account + let pallet_account = Self::account_id(); + T::Currency::transfer(&requester, &pallet_account, deposit, ExistenceRequirement::KeepAlive)?; + + // Get chain ID for event (convert BoundedVec to Vec) + let chain_id = ChainId::::get().to_vec(); + + // Emit event + Self::deposit_event(Event::SignatureRequested { + sender: requester, + payload, + key_version, + deposit, + chain_id, + path: path.to_vec(), + algo: algo.to_vec(), + dest: dest.to_vec(), + params: params.to_vec(), + }); + + Ok(()) + } + + /// Request a signature for a serialized transaction + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::sign_respond())] + pub fn sign_bidirectional( + origin: OriginFor, + serialized_transaction: BoundedVec>, + caip2_id: BoundedVec>, + key_version: u32, + path: BoundedVec>, + algo: BoundedVec>, + dest: BoundedVec>, + params: BoundedVec>, + program_id: T::AccountId, + output_deserialization_schema: BoundedVec>, + respond_serialization_schema: BoundedVec>, + ) -> DispatchResult { + let requester = ensure_signed(origin)?; + + // Ensure initialized + ensure!(Admin::::get().is_some(), Error::::NotInitialized); + + // Validate transaction data + ensure!(!serialized_transaction.is_empty(), Error::::InvalidTransaction); + + // Get deposit amount + let deposit = SignatureDeposit::::get(); + + // Transfer deposit from requester to pallet account + let pallet_account = Self::account_id(); + T::Currency::transfer(&requester, &pallet_account, deposit, ExistenceRequirement::KeepAlive)?; + + // Emit event + Self::deposit_event(Event::SignBidirectionalRequested { + sender: requester, + serialized_transaction: serialized_transaction.to_vec(), + caip2_id: caip2_id.to_vec(), + key_version, + deposit, + path: path.to_vec(), + algo: algo.to_vec(), + dest: dest.to_vec(), + params: params.to_vec(), + program_id, + output_deserialization_schema: output_deserialization_schema.to_vec(), + respond_serialization_schema: respond_serialization_schema.to_vec(), + }); + + Ok(()) + } + + /// Respond to signature requests (batch support) + #[pallet::call_index(5)] + #[pallet::weight(::WeightInfo::respond(request_ids.len() as u32))] + pub fn respond( + origin: OriginFor, + request_ids: BoundedVec<[u8; 32], ConstU32>, + signatures: BoundedVec>, + ) -> DispatchResult { + let responder = ensure_signed(origin)?; + + // Validate input lengths + ensure!(request_ids.len() == signatures.len(), Error::::InvalidInputLength); + + // Emit events for each response + for i in 0..request_ids.len() { + Self::deposit_event(Event::SignatureResponded { + request_id: request_ids[i], + responder: responder.clone(), + signature: signatures[i].clone(), + }); + } + + Ok(()) + } + + /// Report signature generation errors (batch support) + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::respond_error(errors.len() as u32))] + pub fn respond_error( + origin: OriginFor, + errors: BoundedVec>, + ) -> DispatchResult { + let responder = ensure_signed(origin)?; + + // Emit error events + for error in errors { + Self::deposit_event(Event::SignatureError { + request_id: error.request_id, + responder: responder.clone(), + error: error.error_message.to_vec(), + }); + } + + Ok(()) + } + + /// Provide a read response with signature + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::read_respond())] + pub fn respond_bidirectional( + origin: OriginFor, + request_id: [u8; 32], + serialized_output: BoundedVec>, + signature: Signature, + ) -> DispatchResult { + let responder = ensure_signed(origin)?; + + // Just emit event + Self::deposit_event(Event::RespondBidirectionalEvent { + request_id, + responder, + serialized_output: serialized_output.to_vec(), + signature, + }); + + Ok(()) + } + } + + // Helper functions + impl Pallet { + /// Get the pallet's account ID (where funds are stored) + pub fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + } +} diff --git a/pallets/signet/src/tests.rs b/pallets/signet/src/tests.rs new file mode 100644 index 0000000000..df6b7475a8 --- /dev/null +++ b/pallets/signet/src/tests.rs @@ -0,0 +1,932 @@ +use crate::{self as pallet_signet, *}; +use crate::{AffinePoint, ErrorResponse, SerializationFormat, Signature}; +use frame_support::{ + assert_noop, assert_ok, parameter_types, + traits::{ConstU16, ConstU64, Currency as CurrencyTrait}, + PalletId, +}; +use frame_system as system; +use sp_core::H256; +use sp_runtime::traits::AccountIdConversion; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +use sp_std::vec::Vec; + +fn bounded_u8(v: Vec) -> BoundedVec> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_array(v: Vec<[u8; 32]>) -> BoundedVec<[u8; 32], ConstU32> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_sig(v: Vec) -> BoundedVec> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_err(v: Vec) -> BoundedVec> { + BoundedVec::try_from(v).unwrap() +} + +fn bounded_chain_id(v: Vec) -> BoundedVec { + BoundedVec::try_from(v).unwrap() +} +#[frame_support::pallet] +pub mod pallet_mock_caller { + use crate::{self as pallet_signet, tests::bounded_u8}; + use frame_support::{pallet_prelude::*, PalletId}; + use frame_system::pallet_prelude::*; + use sp_runtime::traits::AccountIdConversion; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_signet::Config { + #[pallet::constant] + type PalletId: Get; + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(10_000, 0))] + pub fn call_signet(origin: OriginFor) -> DispatchResult { + // This pallet will call signet with ITS OWN account as the sender + let _who = ensure_signed(origin)?; + + // Get this pallet's derived account (use fully-qualified syntax) + let pallet_account: T::AccountId = ::PalletId::get().into_account_truncating(); + + // Call signet from this pallet's account + pallet_signet::Pallet::::sign( + frame_system::RawOrigin::Signed(pallet_account).into(), + [99u8; 32], + 1, + bounded_u8::<256>(b"from_pallet".to_vec()), + bounded_u8::<32>(b"ecdsa".to_vec()), + bounded_u8::<64>(b"".to_vec()), + bounded_u8::<1024>(b"{}".to_vec()), + )?; + + Ok(()) + } + } +} + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Signet: pallet_signet, + MockCaller: pallet_mock_caller, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +impl pallet_balances::Config for Test { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); +} + +parameter_types! { + pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); + pub const MaxChainIdLength: u32 = 128; +} + +impl pallet_signet::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type PalletId = SignetPalletId; + type MaxChainIdLength = MaxChainIdLength; + type WeightInfo = (); +} + +parameter_types! { + pub const MockCallerPalletId: PalletId = PalletId(*b"py/mockc"); +} + +impl pallet_mock_caller::Config for Test { + type PalletId = MockCallerPalletId; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::::default().build_storage().unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + let _ = Balances::deposit_creating(&1, 1_000_000); + let _ = Balances::deposit_creating(&2, 1_000_000); + let _ = Balances::deposit_creating(&3, 100); + }); + ext +} + +// ======================================== +// 🧪 TESTS START HERE +// ======================================== + +#[test] +fn test_initialize_works() { + new_test_ext().execute_with(|| { + let admin_account = 1u64; + let deposit = 1000u128; + let chain_id = bounded_chain_id(b"test-chain".to_vec()); + + assert_eq!(Signet::admin(), None); + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin_account, + deposit, + chain_id.clone() + )); + + assert_eq!(Signet::admin(), Some(admin_account)); + assert_eq!(Signet::signature_deposit(), deposit); + assert_eq!(Signet::chain_id().to_vec(), chain_id.to_vec()); + + System::assert_last_event( + Event::Initialized { + admin: admin_account, + signature_deposit: deposit, + chain_id: chain_id.to_vec(), + } + .into(), + ); + }); +} + +#[test] +fn test_cannot_initialize_twice() { + new_test_ext().execute_with(|| { + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + 1, + 1000, + bounded_chain_id(b"test-chain".to_vec()) + )); + + assert_noop!( + Signet::initialize( + RuntimeOrigin::signed(2), + 2, + 2000, + bounded_chain_id(b"test-chain".to_vec()) + ), + Error::::AlreadyInitialized + ); + }); +} + +#[test] +fn test_cannot_use_before_initialization() { + new_test_ext().execute_with(|| { + assert_noop!( + Signet::sign( + RuntimeOrigin::signed(1), + [0u8; 32], + 1, + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) + ), + Error::::NotInitialized + ); + }); +} + +#[test] +fn test_any_signed_can_initialize_once() { + new_test_ext().execute_with(|| { + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(2), + 1, + 1000, + bounded_chain_id(b"test-chain".to_vec()) + )); + + assert_eq!(Signet::admin(), Some(1)); + assert_eq!(Signet::signature_deposit(), 1000); + + assert_noop!( + Signet::initialize( + RuntimeOrigin::signed(1), + 3, + 2000, + bounded_chain_id(b"test-chain".to_vec()) + ), + Error::::AlreadyInitialized + ); + + assert_noop!( + Signet::initialize( + RuntimeOrigin::signed(3), + 3, + 2000, + bounded_chain_id(b"test-chain".to_vec()) + ), + Error::::AlreadyInitialized + ); + + assert_eq!(Signet::admin(), Some(1)); + assert_eq!(Signet::signature_deposit(), 1000); + }); +} + +#[test] +fn test_initialize_sets_deposit() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let initial_deposit = 1000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + initial_deposit, + bounded_chain_id(b"test-chain".to_vec()) + )); + + assert_eq!(Signet::signature_deposit(), initial_deposit); + + System::assert_last_event( + Event::Initialized { + admin, + signature_deposit: initial_deposit, + chain_id: bounded_chain_id(b"test-chain".to_vec()).to_vec(), + } + .into(), + ); + }); +} + +#[test] +fn test_update_deposit_as_admin() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let initial_deposit = 1000u128; + let new_deposit = 2000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + initial_deposit, + bounded_chain_id(b"test-chain".to_vec()) + )); + + assert_ok!(Signet::update_deposit(RuntimeOrigin::signed(admin), new_deposit)); + assert_eq!(Signet::signature_deposit(), new_deposit); + + System::assert_last_event( + Event::DepositUpdated { + old_deposit: initial_deposit, + new_deposit, + } + .into(), + ); + }); +} + +#[test] +fn test_non_admin_cannot_update_deposit() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let non_admin = 2u64; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + bounded_chain_id(b"test-chain".to_vec()) + )); + + assert_noop!( + Signet::update_deposit(RuntimeOrigin::signed(non_admin), 2000), + Error::::Unauthorized + ); + + assert_eq!(Signet::signature_deposit(), 1000); + }); +} + +#[test] +fn test_cannot_update_deposit_before_initialization() { + new_test_ext().execute_with(|| { + assert_noop!( + Signet::update_deposit(RuntimeOrigin::signed(1), 1000), + Error::::NotInitialized + ); + }); +} + +#[test] +fn test_withdraw_funds_as_admin() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let recipient = 2u64; + let amount = 5000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + bounded_chain_id(b"test-chain".to_vec()) + )); + + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + let recipient_balance_before = Balances::free_balance(&recipient); + assert_eq!(Balances::free_balance(&pallet_account), 10_000); + + assert_ok!(Signet::withdraw_funds(RuntimeOrigin::signed(admin), recipient, amount)); + + assert_eq!(Balances::free_balance(&pallet_account), 5_000); + assert_eq!(Balances::free_balance(&recipient), recipient_balance_before + amount); + + System::assert_last_event(Event::FundsWithdrawn { amount, recipient }.into()); + }); +} + +#[test] +fn test_non_admin_cannot_withdraw() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let non_admin = 2u64; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + bounded_chain_id(b"test-chain".to_vec()) + )); + + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + assert_noop!( + Signet::withdraw_funds(RuntimeOrigin::signed(non_admin), non_admin, 5000), + Error::::Unauthorized + ); + }); +} + +#[test] +fn test_cannot_withdraw_more_than_balance() { + new_test_ext().execute_with(|| { + let admin = 1u64; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 1000, + bounded_chain_id(b"test-chain".to_vec()) + )); + + let pallet_account = Signet::account_id(); + let _ = Balances::deposit_creating(&pallet_account, 10_000); + + assert_noop!( + Signet::withdraw_funds(RuntimeOrigin::signed(admin), admin, 20_000), + Error::::InsufficientFunds + ); + }); +} + +#[test] +fn test_pallet_account_id_is_deterministic() { + new_test_ext().execute_with(|| { + let account1 = Signet::account_id(); + let account2 = Signet::account_id(); + assert_eq!(account1, account2); + + assert_ne!(account1, 1u64); + assert_ne!(account1, 2u64); + }); +} + +#[test] +fn test_sign_request_works() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let deposit = 1000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + bounded_chain_id(b"test-chain".to_vec()) + )); + + let balance_before = Balances::free_balance(&requester); + let payload = [42u8; 32]; + let key_version = 1u32; + let path = bounded_u8::<256>(b"path".to_vec()); + let algo = bounded_u8::<32>(b"ecdsa".to_vec()); + let dest = bounded_u8::<64>(b"callback_contract".to_vec()); + let params = bounded_u8::<1024>(b"{}".to_vec()); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester), + payload, + key_version, + path.clone(), + algo.clone(), + dest.clone(), + params.clone() + )); + + assert_eq!(Balances::free_balance(&requester), balance_before - deposit); + let pallet_account = Signet::account_id(); + assert_eq!(Balances::free_balance(&pallet_account), deposit); + + System::assert_last_event( + Event::SignatureRequested { + sender: requester, + payload, + key_version, + deposit, + chain_id: bounded_chain_id(b"test-chain".to_vec()).to_vec(), + path: path.to_vec(), + algo: algo.to_vec(), + dest: dest.to_vec(), + params: params.to_vec(), + } + .into(), + ); + }); +} + +#[test] +fn test_sign_request_insufficient_balance() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let poor_user = 3u64; + let deposit = 1000u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + bounded_chain_id(b"test-chain".to_vec()) + )); + + assert_noop!( + Signet::sign( + RuntimeOrigin::signed(poor_user), + [0u8; 32], + 1, + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) + ), + sp_runtime::TokenError::FundsUnavailable + ); + }); +} + +#[test] +fn test_sign_request_before_initialization() { + new_test_ext().execute_with(|| { + assert_noop!( + Signet::sign( + RuntimeOrigin::signed(1), + [0u8; 32], + 1, + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) + ), + Error::::NotInitialized + ); + }); +} + +#[test] +fn test_multiple_sign_requests() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester1 = 1u64; + let requester2 = 2u64; + let deposit = 100u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + bounded_chain_id(b"test-chain".to_vec()) + )); + + let pallet_account = Signet::account_id(); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester1), + [1u8; 32], + 1, + bounded_u8::<256>(b"path1".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) + )); + + assert_eq!(Balances::free_balance(&pallet_account), deposit); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester2), + [2u8; 32], + 2, + bounded_u8::<256>(b"path2".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) + )); + + assert_eq!(Balances::free_balance(&pallet_account), deposit * 2); + }); +} + +fn create_test_signature() -> Signature { + Signature { + big_r: AffinePoint { + x: [1u8; 32], + y: [2u8; 32], + }, + s: [3u8; 32], + recovery_id: 0, + } +} + +#[test] +fn test_sign_bidirectional_works() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let deposit = 100u128; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + deposit, + bounded_chain_id(b"test-chain".to_vec()) + )); + + let tx_data = b"mock_transaction_data".to_vec(); + let slip44_chain_id = 60u32; + let balance_before = Balances::free_balance(&requester); + + assert_ok!(Signet::sign_bidirectional( + RuntimeOrigin::signed(requester), + bounded_u8::<65536>(tx_data.clone()), + bounded_u8::<64>(b"eip155:60".to_vec()), + 1, + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"ecdsa".to_vec()), + bounded_u8::<64>(b"callback".to_vec()), + bounded_u8::<1024>(b"{}".to_vec()), + requester, + bounded_u8::<4096>(b"schema1".to_vec()), + bounded_u8::<4096>(b"schema2".to_vec()) + )); + + assert_eq!(Balances::free_balance(&requester), balance_before - deposit); + + let events = System::events(); + let event_found = events + .iter() + .any(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignBidirectionalRequested { .. }))); + assert!(event_found); + }); +} + +#[test] +fn test_sign_bidirectional_empty_transaction_fails() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 100, + bounded_chain_id(b"test-chain".to_vec()) + )); + + assert_noop!( + Signet::sign_bidirectional( + RuntimeOrigin::signed(requester), + bounded_u8::<65536>(vec![]), + bounded_u8::<64>(b"eip155:60".to_vec()), + 1, + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()), + requester, + bounded_u8::<4096>(vec![]), + bounded_u8::<4096>(vec![]) + ), + Error::::InvalidTransaction + ); + }); +} + +#[test] +fn test_respond_single() { + new_test_ext().execute_with(|| { + let responder = 1u64; + let request_id = [99u8; 32]; + let signature = create_test_signature(); + + assert_ok!(Signet::respond( + RuntimeOrigin::signed(responder), + bounded_array::<100>(vec![request_id]), + bounded_sig::<100>(vec![signature.clone()]) + )); + + System::assert_last_event( + Event::SignatureResponded { + request_id, + responder, + signature, + } + .into(), + ); + }); +} + +#[test] +fn test_respond_batch() { + new_test_ext().execute_with(|| { + let responder = 1u64; + let request_ids = vec![[1u8; 32], [2u8; 32], [3u8; 32]]; + let signatures = vec![ + create_test_signature(), + create_test_signature(), + create_test_signature(), + ]; + + assert_ok!(Signet::respond( + RuntimeOrigin::signed(responder), + bounded_array::<100>(request_ids.clone()), + bounded_sig::<100>(signatures.clone()) + )); + + let events = System::events(); + let response_events = events + .iter() + .filter(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignatureResponded { .. }))) + .count(); + assert_eq!(response_events, 3); + }); +} + +#[test] +fn test_respond_mismatched_arrays_fails() { + new_test_ext().execute_with(|| { + let responder = 1u64; + + assert_noop!( + Signet::respond( + RuntimeOrigin::signed(responder), + bounded_array::<100>(vec![[1u8; 32], [2u8; 32]]), + bounded_sig::<100>(vec![ + create_test_signature(), + create_test_signature(), + create_test_signature(), + ]) + ), + Error::::InvalidInputLength + ); + }); +} + +#[test] +fn test_respond_error_single() { + new_test_ext().execute_with(|| { + let responder = 1u64; + let error_response = ErrorResponse { + request_id: [99u8; 32], + error_message: bounded_u8::<1024>(b"Signature generation failed".to_vec()), + }; + + assert_ok!(Signet::respond_error( + RuntimeOrigin::signed(responder), + bounded_err::<100>(vec![error_response]) + )); + + System::assert_last_event( + Event::SignatureError { + request_id: [99u8; 32], + responder, + error: b"Signature generation failed".to_vec(), + } + .into(), + ); + }); +} + +#[test] +fn test_respond_error_batch() { + new_test_ext().execute_with(|| { + let responder = 1u64; + let errors = vec![ + ErrorResponse { + request_id: [1u8; 32], + error_message: bounded_u8::<1024>(b"Error 1".to_vec()), + }, + ErrorResponse { + request_id: [2u8; 32], + error_message: bounded_u8::<1024>(b"Error 2".to_vec()), + }, + ]; + + assert_ok!(Signet::respond_error( + RuntimeOrigin::signed(responder), + bounded_err::<100>(errors) + )); + + let events = System::events(); + let error_events = events + .iter() + .filter(|e| matches!(&e.event, RuntimeEvent::Signet(Event::SignatureError { .. }))) + .count(); + assert_eq!(error_events, 2); + }); +} + +#[test] +fn test_respond_bidirectional() { + new_test_ext().execute_with(|| { + let responder = 1u64; + let request_id = [99u8; 32]; + let output = b"read_output_data".to_vec(); + let signature = create_test_signature(); + + assert_ok!(Signet::respond_bidirectional( + RuntimeOrigin::signed(responder), + request_id, + bounded_u8::<65536>(output.clone()), + signature.clone() + )); + + System::assert_last_event( + Event::RespondBidirectionalEvent { + request_id, + responder, + serialized_output: output, + signature, + } + .into(), + ); + }); +} + +#[test] +fn test_sign_includes_chain_id() { + new_test_ext().execute_with(|| { + let admin = 1u64; + let requester = 2u64; + let chain_id = bounded_chain_id(b"hydradx:polkadot:0".to_vec()); + + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + admin, + 100, + chain_id.clone() + )); + + assert_ok!(Signet::sign( + RuntimeOrigin::signed(requester), + [42u8; 32], + 1, + bounded_u8::<256>(b"path".to_vec()), + bounded_u8::<32>(b"algo".to_vec()), + bounded_u8::<64>(b"dest".to_vec()), + bounded_u8::<1024>(b"params".to_vec()) + )); + + let events = System::events(); + let sign_event = events.iter().find_map(|e| { + if let RuntimeEvent::Signet(Event::SignatureRequested { + chain_id: event_chain_id, + .. + }) = &e.event + { + Some(event_chain_id.clone()) + } else { + None + } + }); + + assert_eq!(sign_event, Some(chain_id.to_vec())); + }); +} + +#[test] +fn test_cross_pallet_execution() { + new_test_ext().execute_with(|| { + // Initialize signet first + assert_ok!(Signet::initialize( + RuntimeOrigin::signed(1), + 1, + 100, + bounded_chain_id(b"test-chain".to_vec()) + )); + + // Fund the MockCaller pallet's account + let mock_pallet_account: u64 = MockCallerPalletId::get().into_account_truncating(); + let _ = Balances::deposit_creating(&mock_pallet_account, 10_000); + + // User calls MockCaller, which then calls Signet + assert_ok!(MockCaller::call_signet(RuntimeOrigin::signed(2))); + + // Check the event - the sender should be the PALLET's account + System::assert_last_event( + Event::SignatureRequested { + sender: mock_pallet_account, + payload: [99u8; 32], + key_version: 1, + deposit: 100, + chain_id: bounded_chain_id(b"test-chain".to_vec()).to_vec(), + path: b"from_pallet".to_vec(), + algo: b"ecdsa".to_vec(), + dest: b"".to_vec(), + params: b"{}".to_vec(), + } + .into(), + ); + + // Verify the deposit was taken from the pallet's account + assert_eq!(Balances::free_balance(&mock_pallet_account), 10_000 - 100); + + println!("✅ Cross-pallet test passed!"); + println!(" User 2 called MockCaller"); + println!(" MockCaller called Signet"); + println!( + " Signet saw sender as: {:?} (the pallet account)", + mock_pallet_account + ); + println!(" NOT as: 2 (the original user)"); + }); +} diff --git a/pallets/signet/src/weights.rs b/pallets/signet/src/weights.rs new file mode 100644 index 0000000000..979df9ab01 --- /dev/null +++ b/pallets/signet/src/weights.rs @@ -0,0 +1,56 @@ +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions for this pallet +pub trait WeightInfo { + fn initialize() -> Weight; + fn update_deposit() -> Weight; + fn withdraw_funds() -> Weight; + fn sign() -> Weight; + fn sign_respond() -> Weight; + fn respond(r: u32) -> Weight; // r = number of responses + fn respond_error(e: u32) -> Weight; // e = number of errors + fn read_respond() -> Weight; +} + +/// For tests - just returns simple weights +impl WeightInfo for () { + fn initialize() -> Weight { + Weight::from_parts(10_000_000, 0) + } + + fn update_deposit() -> Weight { + Weight::from_parts(8_000_000, 0) + } + + fn withdraw_funds() -> Weight { + Weight::from_parts(35_000_000, 0) + } + + fn sign() -> Weight { + Weight::from_parts(45_000_000, 0) + } + + fn sign_respond() -> Weight { + Weight::from_parts(50_000_000, 0) // Slightly more than sign + } + + fn respond(r: u32) -> Weight { + // Base weight + per-response weight + Weight::from_parts(10_000_000, 0) + .saturating_add(Weight::from_parts(1_000_000, 0).saturating_mul(r.into())) + } + + fn respond_error(e: u32) -> Weight { + // Base weight + per-error weight + Weight::from_parts(10_000_000, 0) + .saturating_add(Weight::from_parts(500_000, 0).saturating_mul(e.into())) + } + + fn read_respond() -> Weight { + Weight::from_parts(10_000_000, 0) + } +} \ No newline at end of file diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 94d79a7f81..d8002a2ed3 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -63,6 +63,8 @@ pallet-route-executor = { workspace = true } pallet-staking = { workspace = true } pallet-liquidation = { workspace = true } pallet-hsm = { workspace = true } +pallet-build-evm-tx = { workspace = true } +pallet-build-btc-tx = { workspace = true } pallet-parameters = { workspace = true } # pallets @@ -91,6 +93,9 @@ pallet-uniques = { workspace = true } pallet-whitelist = { workspace = true } pallet-message-queue = { workspace = true } pallet-state-trie-migration = { workspace = true } +pallet-signet = { workspace = true } +pallet-erc20-vault = { workspace = true } +pallet-btc-vault = { workspace = true } # ORML dependencies orml-tokens = { workspace = true } @@ -241,6 +246,8 @@ runtime-benchmarks = [ "pallet-lbp/runtime-benchmarks", "pallet-xyk/runtime-benchmarks", "pallet-hsm/runtime-benchmarks", + "pallet-build-evm-tx/runtime-benchmarks", + "pallet-build-btc-tx/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-referrals/runtime-benchmarks", "pallet-evm-accounts/runtime-benchmarks", @@ -256,6 +263,9 @@ runtime-benchmarks = [ "pallet-xcm-benchmarks/runtime-benchmarks", "ismp-parachain/runtime-benchmarks", "pallet-token-gateway/runtime-benchmarks", + "pallet-signet/runtime-benchmarks", + "pallet-erc20-vault/runtime-benchmarks", + "pallet-btc-vault/runtime-benchmarks", ] std = [ "codec/std", @@ -336,6 +346,8 @@ std = [ "pallet-duster/std", "pallet-duster-rpc-runtime-api/std", "pallet-hsm/std", + "pallet-build-evm-tx/std", + "pallet-build-btc-tx/std", "pallet-parameters/std", "warehouse-liquidity-mining/std", "sp-api/std", @@ -390,6 +402,9 @@ std = [ "ismp/std", "ismp-parachain/std", "ismp-parachain-runtime-api/std", + "pallet-signet/std", + "pallet-erc20-vault/std", + "pallet-btc-vault/std", ] try-runtime = [ "frame-try-runtime", @@ -457,6 +472,8 @@ try-runtime = [ "pallet-evm-chain-id/try-runtime", "pallet-xyk/try-runtime", "pallet-hsm/try-runtime", + "pallet-build-evm-tx/try-runtime", + "pallet-build-btc-tx/try-runtime", "pallet-referrals/try-runtime", "pallet-evm-accounts/try-runtime", "pallet-xyk-liquidity-mining/try-runtime", @@ -468,6 +485,9 @@ try-runtime = [ "pallet-whitelist/try-runtime", "pallet-broadcast/try-runtime", "pallet-dispatcher/try-runtime", + "pallet-signet/try-runtime", + "pallet-erc20-vault/try-runtime", + "pallet-btc-vault/try-runtime", ] metadata-hash = [ diff --git a/runtime/hydradx/src/assets.rs b/runtime/hydradx/src/assets.rs index a809766a64..dc9be06eaf 100644 --- a/runtime/hydradx/src/assets.rs +++ b/runtime/hydradx/src/assets.rs @@ -1832,6 +1832,55 @@ impl pallet_hsm::Config for Runtime { type BenchmarkHelper = helpers::benchmark_helpers::HsmBenchmarkHelper; } +parameter_types! { + pub const MaxEvmDataLength: u32 = 100_000; +} + +impl pallet_build_evm_tx::Config for Runtime { + type MaxDataLength = MaxEvmDataLength; +} + +parameter_types! { + pub const MaxBTCInputs: u32 = 100; + pub const MaxBTCOutputs: u32 = 100; +} + +impl pallet_build_btc_tx::Config for Runtime { + type MaxInputs = MaxBTCInputs; + type MaxOutputs = MaxBTCOutputs; +} + +parameter_types! { + pub const SignetPalletId: PalletId = PalletId(*b"py/signt"); + pub const MaxChainIdLength: u32 = 128; +} + +impl pallet_signet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type PalletId = SignetPalletId; + type MaxChainIdLength = MaxChainIdLength; + type WeightInfo = weights::pallet_signet::HydraWeight; +} + +parameter_types! { + pub const Erc20VaultPalletId: PalletId = PalletId(*b"py/erc20"); +} + +impl pallet_erc20_vault::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type VaultPalletId = Erc20VaultPalletId; +} + +parameter_types! { + pub const BTCVaultPalletId: PalletId = PalletId(*b"py/btcvt"); +} + +impl pallet_btc_vault::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type VaultPalletId = BTCVaultPalletId; +} + pub struct ConvertViaOmnipool(PhantomData); impl Convert for ConvertViaOmnipool where diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 0fe87abb1f..b133c06d53 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -192,6 +192,11 @@ construct_runtime!( Liquidation: pallet_liquidation = 76, HSM: pallet_hsm = 82, Parameters: pallet_parameters = 83, + BuildEvmTx: pallet_build_evm_tx = 86, + BuildBTCTx: pallet_build_btc_tx = 87, + Signet: pallet_signet = 84, + Erc20Vault: pallet_erc20_vault = 85, + BTCVault: pallet_btc_vault = 88, // ORML related modules Tokens: orml_tokens = 77, @@ -341,9 +346,14 @@ mod benches { [pallet_whitelist, Whitelist] [pallet_dispatcher, Dispatcher] [pallet_hsm, HSM] + [pallet_build_evm_tx, BuildEvmTx] + [pallet_build_btc_tx, BuildBTCTx] [pallet_dynamic_fees, DynamicFees] [ismp_parachain, IsmpParachain] [pallet_token_gateway, TokenGateway] + [pallet_signet, Signet] + [pallet_erc20_vault, Erc20Vault] + [pallet_btc_vault, BTCVault] ); } diff --git a/runtime/hydradx/src/weights/mod.rs b/runtime/hydradx/src/weights/mod.rs index 70670db723..2f7e1af181 100644 --- a/runtime/hydradx/src/weights/mod.rs +++ b/runtime/hydradx/src/weights/mod.rs @@ -53,3 +53,4 @@ pub mod pallet_xcm; pub mod pallet_xyk; pub mod pallet_xyk_liquidity_mining; pub mod xcm; +pub mod pallet_signet; diff --git a/runtime/hydradx/src/weights/pallet_signet.rs b/runtime/hydradx/src/weights/pallet_signet.rs new file mode 100644 index 0000000000..ab8f98c00e --- /dev/null +++ b/runtime/hydradx/src/weights/pallet_signet.rs @@ -0,0 +1,43 @@ +use frame_support::weights::Weight; +use sp_runtime::traits::Get; +use sp_std::marker::PhantomData; + +pub use pallet_signet::weights::*; + +pub struct HydraWeight(PhantomData); + +impl pallet_signet::weights::WeightInfo for HydraWeight { + fn initialize() -> Weight { + Weight::from_parts(10_000_000, 0) + } + + fn update_deposit() -> Weight { + Weight::from_parts(8_000_000, 0) + } + + fn withdraw_funds() -> Weight { + Weight::from_parts(35_000_000, 0) + } + + fn sign() -> Weight { + Weight::from_parts(45_000_000, 0) + } + + fn sign_respond() -> Weight { + Weight::from_parts(50_000_000, 0) + } + + fn respond(r: u32) -> Weight { + Weight::from_parts(10_000_000, 0) + .saturating_add(Weight::from_parts(1_000_000, 0).saturating_mul(r.into())) + } + + fn respond_error(e: u32) -> Weight { + Weight::from_parts(10_000_000, 0) + .saturating_add(Weight::from_parts(500_000, 0).saturating_mul(e.into())) + } + + fn read_respond() -> Weight { + Weight::from_parts(10_000_000, 0) + } +} \ No newline at end of file diff --git a/signet-ts-client-scripts/README.md b/signet-ts-client-scripts/README.md new file mode 100644 index 0000000000..faee2707c9 --- /dev/null +++ b/signet-ts-client-scripts/README.md @@ -0,0 +1,248 @@ +# Signet Substrate Client + +Test client for the Signet pallet on Substrate/Polkadot. Validates signature generation and verification for both simple payloads, EIP-1559 transactions, and ERC20 vault deposits. + +## Prerequisites + +- Node.js v16+ and npm/yarn +- Running Substrate node with Signet pallet deployed (port 8000) +- Access to the Signet signature server +- For ERC20 vault tests: Funded Ethereum Sepolia account with ETH and USDC +- For Bitcoin vault tests: Docker for running Bitcoin regtest + +## Setup + +### 1. Start Bitcoin Regtest (for Bitcoin vault tests) +```bash +# Clone the Bitcoin regtest repository +git clone https://github.com/Pessina/bitcoin-regtest.git +cd bitcoin-regtest + +# Start Bitcoin Core in regtest mode +yarn docker:dev +``` + +The Bitcoin regtest node will be available at `http://localhost:18443`. + +### 2. Start the Signature Server + +Clone and run the signature server that responds to Substrate signature requests. Add .env to the root of the repository: + +```bash +# Clone the server repository (substrate-new branch) +git clone -b substrate-new https://github.com/sig-net/solana-signet-program.git +cd solana-signet-program/clients/response-server + +# Install dependencies +yarn install + +# Configure environment variables +cat > .env << EOF +# Substrate Configuration +SUBSTRATE_WS_URL=ws://localhost:8000 +SUBSTRATE_SIGNER_SEED=//Bob + +# Signing Keys (must match ROOT_PUBLIC_KEY in tests) +PRIVATE_KEY_TESTNET=0x... # Your private key for signing + +# Ethereum Configuration (for vault monitoring) +SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/your_infura_key_here + +# Bitcoin Configuration (for Bitcoin vault monitoring) +BITCOIN_NETWORK=regtest + +# Dummy solana key +SOLANA_PRIVATE_KEY='[16,151,155,240,122,151,187,95,145,26,179,205,196,113,3,62,17,105,18,240,197,176,45,90,176,108,30,106,182,43,7,104,80,202,59,51,239,219,236,17,39,204,155,35,175,195,17,172,201,196,134,125,25,214,148,76,102,47,123,37,203,86,159,147]' +EOF + +# Start the server +yarn start +``` + +The server will: +- Connect to your Substrate node +- Automatically respond to signature requests +- Monitor Ethereum transactions and report results back to Substrate +- Monitor Bitcoin transactions on regtest and report results back to Substrate + +### 3. Install Test Client Dependencies + +```bash +yarn install +``` + +### 4. Ensure Substrate Node is Running + +The tests expect a Substrate node with the Signet pallet at `ws://localhost:8000`. If using Chopsticks: + +```bash +npx @acala-network/chopsticks@latest --config=hydradx \ + --wasm-override ./target/release/wbuild/hydradx-runtime/hydradx_runtime.compact.compressed.wasm \ + --db=:memory: +``` + +### 5. Fund Ethereum Account for Vault Tests + +The ERC20 vault test requires a funded account on Sepolia. The test derives an Ethereum address from your Substrate account and expects it to have: + +- At least 0.001 ETH for gas +- At least 0.01 USDC (testnet) at address `0xbe72E441BF55620febc26715db68d3494213D8Cb` + +The derived address is deterministic based on your Substrate account. Run the test once to see the address, then fund it on Sepolia + +## Running Tests + +```bash +# Run all tests +yarn test + +# Run specific test suite +yarn test signet.test.ts +yarn test erc20vault.test.ts +yarn test btc-vault.test.ts + +# Run with watch mode +yarn test:watch +``` + +## Test Coverage + +### Basic Signature Tests (`signet.test.ts`) +- **Simple Signatures**: Request and verify ECDSA signatures for 32-byte payloads +- **Transaction Signatures**: Sign and verify EIP-1559 Ethereum transactions +- **Key Derivation**: Verify derived keys match between client and server +- **Address Recovery**: Ensure signature recovery produces expected addresses + +### ERC20 Vault Integration (`erc20vault.test.ts`) +- **Vault Initialization**: Initialize vault with MPC signer address +- **Deposit Flow**: + - Request MPC signature for ERC20 transfer transaction + - Verify signature and broadcast to Sepolia + - Wait for transaction confirmation +- **Result Monitoring**: MPC server observes transaction result on Ethereum +- **Claim Flow**: + - Receive transaction output from MPC + - Verify MPC signature on result + - Claim deposited tokens in Substrate vault +- **Multi-token Support**: Vault supports any ERC20 token (decimal-agnostic) + +### Bitcoin Vault Integration (`btc-vault.test.ts`) +- **Vault Initialization**: Initialize vault with MPC signer address hash +- **UTXO Management**: Automatically discover and select UTXOs for spending +- **Deposit Flow**: + - Fund Bitcoin address derived from Substrate account + - Build PSBT (Partially Signed Bitcoin Transaction) with vault output + - Request per-input MPC signatures for each PSBT input + - Finalize and broadcast signed transaction to regtest + - Wait for confirmation +- **Result Monitoring**: MPC server observes transaction confirmation on Bitcoin +- **Claim Flow**: + - Receive transaction output from MPC + - Verify MPC signature on result + - Claim deposited sats in Substrate vault +- **Per-Input Signing**: Each UTXO input receives an individual signature with unique request ID + +## Expected Output + +### Basic Signature Test +``` +PASS ./signet.test.ts + Signet Pallet Integration + Sign + ✓ should request and verify a signature + ✅ Signature received from: 14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3 + Recovered: 0xF4a62e4f48e8e71170BA758b5bAf90646db61301 + Expected: 0xf4a62e4f48e8e71170ba758b5baf90646db61301 + ✅ Signature verification PASSED + + SignRespond + ✓ should request and verify a transaction signature + ✅ Transaction signature received from: 14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3 + ✅ Transaction signature verification PASSED +``` + +### ERC20 Vault Test +``` +PASS ./erc20vault.test.ts (47.902 s) + ERC20 Vault Integration + ✓ should complete full deposit and claim flow (43924 ms) + + 🔑 Derived Ethereum Address: 0xde36cd568b21c9e5b19ab6ecf01f9e5024398913 + 💰 Balances for 0xde36cd568b21c9e5b19ab6ecf01f9e5024398913: + ETH: 0.00999946 + USDC: 0.06 + + Initializing vault with MPC address: 0x00a40c2661293d5134e53da52951a3f7767836ef + ✅ Vault initialized + + 📊 Current nonce for 0xde36cd568b21c9e5b19ab6ecf01f9e5024398913: 18 + 📋 Request ID: 0x15dc855a7fb93a3e694d5a93b9a40a2a141c7af0bbfc6afdc20d8c80ce4124f7 + 🚀 Submitting deposit_erc20 transaction... + ⏳ Waiting for MPC signature... + + ✅ Received signature from: 14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3 + 🔍 Signature verification: + Expected address: 0xde36cd568b21c9e5b19ab6ecf01f9e5024398913 + Recovered address: 0xDE36CD568B21C9E5B19AB6ECF01F9e5024398913 + Match: true + + 📊 Fresh nonce check: 18 + 📡 Broadcasting transaction to Sepolia... + Tx Hash: 0xead2b6a3de9fdd90d04da5f329c5ada25060b4f0e48ebc65aa1c1c601696f974 + ✅ Transaction confirmed in block 9357487 + + ⏳ Waiting for MPC to read transaction result... + ✅ Received read response + ✅ Claim transaction confirmed + ✅ Balance increased by: 0.01 USDC + Total balance: 0.04 USDC + +Test Suites: 1 passed, 1 total +Tests: 1 passed, 1 total +Time: 48.056 s +``` + +### Bitcoin Vault Test +``` +PASS ./btc-vault-perinput.test.ts (131.227 s) +BTC Vault Integration +✓ should complete full deposit and claim flow (125678 ms) +🔑 Derived Bitcoin Address: bcrt1qmr8q7udefh6e3mld7xpahh7g652pq3zurswrxz +💰 Funding bcrt1qmr8q7udefh6e3mld7xpahh7g652pq3zurswrxz with 1 BTC... +📦 Found 32 UTXO(s) +📊 Transaction breakdown: +Inputs: 1 +To vault: 36879590 sats +Fee: 138 sats +✅ Deposit BTC included in block +⏳ Waiting for MPC signature(s)... +✅ Received all 1 signature(s) from MPC +📡 Broadcasting transaction to regtest... +✅ Transaction confirmed (1 confirmations) +✅ Balance increased by: 36879590 sats +Test Suites: 1 passed, 1 total +Tests: 1 passed, 1 total +``` + +## Configuration + +The root public key used for derivation is hardcoded in the tests. Ensure the server's `PRIVATE_KEY_TESTNET` corresponds to: + +```typescript +const ROOT_PUBLIC_KEY = "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadfec68fb46fdee257e26b8ba383ddba9914b33c60e869265f859566fff4baef283c54d821ca3b64"; +``` + +## Troubleshooting + +- **Timeout errors**: Ensure the signature server is running and connected to the same Substrate node +- **Address mismatch**: Verify the server's private key matches the client's expected public key +- **Transaction errors**: Check that the Signet pallet is initialized (tests handle this automatically) +- **ERC20 vault test failures**: + - Ensure your derived Ethereum address is funded with ETH and USDC on Sepolia + - Verify the MPC server has Ethereum monitoring enabled with `SEPOLIA_RPC_URL` configured + - Check that the vault's MPC address matches the server's signing key +- **Bitcoin vault test failures**: + - Ensure Bitcoin regtest is running at `http://localhost:18443` + - Verify the MPC server has `BITCOIN_NETWORK=regtest` configured + - The test automatically funds addresses and mines blocks, but ensure Docker has sufficient resources +- **InvalidSigner errors**: The output bytes from Substrate events include SCALE encoding that must be stripped before verification \ No newline at end of file diff --git a/signet-ts-client-scripts/btc-vault.test.ts b/signet-ts-client-scripts/btc-vault.test.ts new file mode 100644 index 0000000000..3ff7bf0a25 --- /dev/null +++ b/signet-ts-client-scripts/btc-vault.test.ts @@ -0,0 +1,917 @@ +import { ApiPromise, WsProvider, Keyring } from "@polkadot/api"; +import { ISubmittableResult } from "@polkadot/types/types"; +import { waitReady } from "@polkadot/wasm-crypto"; +import { u8aToHex } from "@polkadot/util"; +import { encodeAddress } from "@polkadot/keyring"; +import { ethers } from "ethers"; +import * as bitcoin from "bitcoinjs-lib"; +import Client from "bitcoin-core"; +import { SignetClient } from "./signet-client"; +import { KeyDerivation } from "./key-derivation"; +import * as ecc from "tiny-secp256k1"; +import coinSelect from "coinselect"; + +const ROOT_PUBLIC_KEY = + "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadfec68fb46fdee257e26b8ba383ddba9914b33c60e869265f859566fff4baef283c54d821ca3b64"; +const CHAIN_ID = "bip122:000000000933ea01ad0ee984209779ba"; + +function normalizeSignature(r: Buffer, s: Buffer): { r: Buffer; s: Buffer } { + const N = BigInt( + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ); + const halfN = N / 2n; + const sBigInt = BigInt("0x" + s.toString("hex")); + + if (sBigInt > halfN) { + const normalizedS = N - sBigInt; + const sBuffer = Buffer.from( + normalizedS.toString(16).padStart(64, "0"), + "hex" + ); + return { r, s: sBuffer }; + } + + return { r, s }; +} + +const TESTNET_VAULT_ADDRESS_HASH = new Uint8Array([ + 0x89, 0xf0, 0xa8, 0x23, 0x93, 0x8c, 0x58, 0xcf, 0x5b, 0x17, 0xc8, 0xeb, 0x93, + 0xc6, 0x82, 0x80, 0x63, 0x5b, 0x73, 0x4e, +]); + +function getPalletAccountId(): Uint8Array { + const palletId = new TextEncoder().encode("py/btcvt"); + const modl = new TextEncoder().encode("modl"); + const data = new Uint8Array(32); + data.set(modl, 0); + data.set(palletId, 4); + return data; +} + +async function submitWithRetry( + tx: any, + signer: any, + api: ApiPromise, + label: string, + maxRetries: number = 1, + timeoutMs: number = 60000 +): Promise<{ events: any[] }> { + let attempt = 0; + + while (attempt <= maxRetries) { + try { + console.log(`${label} - Attempt ${attempt + 1}/${maxRetries + 1}`); + + const result = await new Promise<{ events: any[] }>((resolve, reject) => { + let unsubscribe: any; + + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + console.log(`⏱️ ${label} timed out after ${timeoutMs}ms`); + reject(new Error("TIMEOUT")); + }, timeoutMs); + + tx.signAndSend(signer, (result: ISubmittableResult) => { + const { status, events, dispatchError } = result; + + if (status.isInBlock) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + + console.log( + `✅ ${label} included in block ${status.asInBlock.toHex()}` + ); + + console.log(`📋 All events (${events.length}):`); + for (const record of events) { + const { event } = record; + console.log(` ${event.section}.${event.method}`); + + if ( + event.section === "btcVault" && + event.method === "DebugTxid" + ) { + const palletTxid = event.data[0].toU8a(); + console.log( + `🔍 PALLET TXID: ${Buffer.from(palletTxid).toString("hex")}` + ); + } + } + + if (dispatchError) { + if (dispatchError.isModule) { + const decoded = api.registry.findMetaError( + dispatchError.asModule + ); + reject( + new Error( + `${decoded.section}.${decoded.name}: ${decoded.docs.join( + " " + )}` + ) + ); + } else { + reject(new Error(dispatchError.toString())); + } + return; + } + + resolve({ events: Array.from(events) }); + } else if (status.isInvalid) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + reject(new Error("INVALID_TX")); + } else if (status.isDropped) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + reject(new Error(`${label} dropped`)); + } + }) + .then((unsub: any) => { + unsubscribe = unsub; + }) + .catch((error: any) => { + clearTimeout(timer); + reject(error); + }); + }); + + return result; + } catch (error: any) { + if ( + (error.message === "INVALID_TX" || error.message === "TIMEOUT") && + attempt < maxRetries + ) { + console.log(`🔄 Retrying ${label}...`); + attempt++; + await new Promise((resolve) => setTimeout(resolve, 2000)); + continue; + } + throw error; + } + } + + throw new Error(`${label} failed after ${maxRetries + 1} attempts`); +} + +describe("BTC Vault Integration", () => { + let api: ApiPromise; + let alice: any; + let signetClient: SignetClient; + let btcClient: any; + let derivedBtcAddress: string; + let derivedPubKey: string; + let network: bitcoin.Network; + + beforeAll(async () => { + await waitReady(); + + console.log("🔗 Connecting to Bitcoin regtest..."); + btcClient = new Client({ + host: "http://localhost:18443", + username: "test", + password: "test123", + }); + + try { + const blockCount = await btcClient.command("getblockcount"); + console.log( + `✅ Connected to Bitcoin regtest (block height: ${blockCount})\n` + ); + } catch (error) { + throw new Error( + "❌ Cannot connect to Bitcoin regtest. Make sure it's running:\n" + + " cd bitcoin-regtest && yarn docker:dev" + ); + } + + api = await ApiPromise.create({ + provider: new WsProvider("ws://127.0.0.1:8000"), + types: { + AffinePoint: { x: "[u8; 32]", y: "[u8; 32]" }, + Signature: { big_r: "AffinePoint", s: "[u8; 32]", recovery_id: "u8" }, + }, + }); + + const keyring = new Keyring({ type: "sr25519" }); + alice = keyring.addFromUri("//Alice"); + const bob = keyring.addFromUri("//Bob"); + + const { data: bobBalance } = (await api.query.system.account( + bob.address + )) as any; + + if (bobBalance.free.toBigInt() < 1000000000000n) { + console.log("Funding Bob's account for server responses..."); + const bobFundTx = api.tx.balances.transferKeepAlive( + bob.address, + 100000000000000n + ); + await submitWithRetry(bobFundTx, alice, api, "Fund Bob account"); + } + + const palletAccountId = getPalletAccountId(); + const palletSS58 = encodeAddress(palletAccountId, 0); + + const { data: palletBalance } = (await api.query.system.account( + palletSS58 + )) as any; + + const fundingAmount = 10000000000000n; + + if (palletBalance.free.toBigInt() < fundingAmount) { + console.log(`Funding BTC vault pallet account ${palletSS58}...`); + const fundTx = api.tx.balances.transferKeepAlive( + palletSS58, + fundingAmount + ); + await submitWithRetry(fundTx, alice, api, "Fund pallet account"); + } + + signetClient = new SignetClient(api, alice); + await signetClient.ensureInitialized(CHAIN_ID); + + const aliceAccountId = keyring.decodeAddress(alice.address); + const aliceHexPath = "0x" + u8aToHex(aliceAccountId).slice(2); + + derivedPubKey = KeyDerivation.derivePublicKey( + ROOT_PUBLIC_KEY, + palletSS58, + aliceHexPath, + "polkadot:2034" + ); + + const compressedForComparison = compressPubkey(derivedPubKey); + console.log( + `🔍 Test expects compressed pubkey: ${compressedForComparison.toString( + "hex" + )}` + ); + + console.log(`🔍 Test using path: ${aliceHexPath}`); + console.log(`🔍 Test using sender: ${palletSS58}`); + console.log(`🔍 Derived public key: ${derivedPubKey}`); + + network = bitcoin.networks.regtest; + derivedBtcAddress = btcAddressFromPubKey(derivedPubKey, network); + + console.log(`\n🔑 Derived Bitcoin Address: ${derivedBtcAddress}`); + + await fundBitcoinAddress(derivedBtcAddress); + }, 180000); + + afterAll(async () => { + if (api) { + await api.disconnect(); + } + }); + + async function fundBitcoinAddress(address: string) { + console.log(`💰 Funding ${address} with 1 BTC...`); + + try { + let walletAddress; + try { + walletAddress = await btcClient.command("getnewaddress"); + } catch (e) { + try { + await btcClient.command("createwallet", "testwallet"); + walletAddress = await btcClient.command("getnewaddress"); + } catch (createErr: any) { + await btcClient.command("loadwallet", "testwallet"); + walletAddress = await btcClient.command("getnewaddress"); + } + } + + await btcClient.command("generatetoaddress", 101, walletAddress); + console.log(` Mined 101 blocks to wallet`); + + const txid = await btcClient.command("sendtoaddress", address, 1.0); + console.log(` Funding txid: ${txid}`); + + await btcClient.command("generatetoaddress", 1, walletAddress); + + const scanResult = await btcClient.command("scantxoutset", "start", [ + `addr(${derivedBtcAddress})`, + ]); + + console.log(`📦 Found ${scanResult.unspents.length} UTXO(s)`); + + for (const utxo of scanResult.unspents) { + console.log(`\n🔍 UTXO ${utxo.txid}:${utxo.vout}`); + console.log(` scriptPubKey: ${utxo.scriptPubKey}`); + console.log(` amount: ${utxo.amount} BTC`); + } + + if (scanResult.unspents.length === 0) { + throw new Error("No UTXOs found after funding"); + } + + console.log("✅ Funding confirmed\n"); + } catch (error: any) { + console.error("Funding error:", error.message); + throw error; + } + } + + it("should complete full deposit and claim flow", async () => { + const mpcEthAddress = ethAddressFromPubKey(ROOT_PUBLIC_KEY); + console.log("Checking vault initialization..."); + + const existingConfig = await api.query.btcVault.vaultConfig(); + const configJson = existingConfig.toJSON(); + + if (configJson !== null) { + console.log("⚠️ Vault already initialized, skipping initialization"); + console.log(" Existing config:", existingConfig.toHuman()); + } else { + console.log("Initializing vault with MPC address hash"); + const initTx = api.tx.btcVault.initialize(Array.from(mpcEthAddress)); + await submitWithRetry(initTx, alice, api, "Initialize vault"); + } + + const scanResult = await btcClient.command("scantxoutset", "start", [ + `addr(${derivedBtcAddress})`, + ]); + + if (scanResult.unspents.length === 0) { + throw new Error("No UTXOs available for derived address"); + } + + console.log(`📦 Found ${scanResult.unspents.length} UTXO(s)`); + + const depositAmount = 36879690; + const feeRate = 1; // satoshis per byte + + // Convert UTXOs to coinselect format + const utxos = scanResult.unspents.map((u: any) => ({ + txid: u.txid, + vout: u.vout, + value: Math.floor(u.amount * 100000000), + script: Buffer.from( + bitcoin.address.toOutputScript(derivedBtcAddress, network) + ), + })); + + // Vault output + const vaultScript = Buffer.concat([ + Buffer.from([0x00, 0x14]), + Buffer.from(TESTNET_VAULT_ADDRESS_HASH), + ]); + + const targets = [ + { + script: vaultScript, + value: depositAmount, + }, + ]; + + // Let coinselect pick optimal UTXOs and calculate fee + const { inputs, outputs, fee } = coinSelect(utxos, targets, feeRate); + + if (!inputs || !outputs) { + throw new Error("Insufficient funds for transaction"); + } + + console.log(`📊 Transaction breakdown:`); + console.log(` Inputs: ${inputs.length}`); + inputs.forEach((inp: any, i: number) => { + console.log( + ` Input ${i}: ${inp.value} sats (${inp.txid.slice(0, 8)}...)` + ); + }); + console.log(` To vault: ${outputs[0].value} sats`); + if (outputs.length > 1) { + console.log(` Change: ${outputs[1].value} sats`); + } + console.log(` Fee: ${fee} sats\n`); + + // Build PSBT with selected inputs/outputs + const psbt = new bitcoin.Psbt({ network }); + + for (const input of inputs) { + const txidBytes = Buffer.from(input.txid, "hex").reverse(); + psbt.addInput({ + hash: txidBytes, + index: input.vout, + sequence: 0xffffffff, + witnessUtxo: { + script: input.script!, + value: input.value, + }, + }); + } + + for (let i = 0; i < inputs.length; i++) { + psbt.updateInput(i, { + sighashType: bitcoin.Transaction.SIGHASH_ALL, + }); + } + + for (const output of outputs) { + if (output.script) { + psbt.addOutput({ + script: output.script, + value: output.value, + }); + } else { + // Change output + psbt.addOutput({ + address: derivedBtcAddress, + value: output.value, + }); + } + } + + const psbtBytes = psbt.toBuffer(); + console.log(`📝 PSBT bytes length: ${psbtBytes.length}`); + + console.log(`🔍 Test prevout txid (as sent to pallet): ${inputs[0].txid}`); + console.log( + `🔍 Test prevout txid (reversed for PSBT): ${Buffer.from( + inputs[0].txid, + "hex" + ) + .reverse() + .toString("hex")}` + ); + + // Extract txid from PSBT + const unsignedTxBuffer = psbt.data.globalMap.unsignedTx.toBuffer(); + const unsignedTx = bitcoin.Transaction.fromBuffer(unsignedTxBuffer); + const txid = Buffer.from(unsignedTx.getId(), "hex"); + + const testUnsignedTx = bitcoin.Transaction.fromBuffer(unsignedTxBuffer); + for (let i = 0; i < inputs.length; i++) { + const testSighash = testUnsignedTx.hashForWitnessV0( + i, + inputs[i].script!, + inputs[i].value, + bitcoin.Transaction.SIGHASH_ALL + ); + console.log(`🔍 Test Input ${i} sighash: ${testSighash.toString("hex")}`); + } + + console.log(`🔑 Transaction ID: ${txid.toString("hex")}`); + + console.log(`🔍 Test tx version: ${unsignedTx.version}`); + console.log(`🔍 Test tx locktime: ${unsignedTx.locktime}`); + + const keyring = new Keyring({ type: "sr25519" }); + const palletAccountId = getPalletAccountId(); + const palletSS58 = encodeAddress(palletAccountId, 0); + const aliceAccountId = keyring.decodeAddress(alice.address); + const aliceHexPath = "0x" + u8aToHex(aliceAccountId).slice(2); + + console.log(`🔍 Pallet SS58 (sender): ${palletSS58}`); + console.log(`🔍 Alice hex path: ${aliceHexPath}`); + + // Calculate aggregate request ID (for monitoring) + const aggregateRequestId = signetClient.calculateSignRespondRequestId( + palletSS58, + Array.from(txid), + { + caip2Id: CHAIN_ID, + keyVersion: 0, + path: aliceHexPath, + algo: "ecdsa", + dest: "bitcoin", + params: "", + } + ); + + console.log( + `📋 Aggregate Request ID: ${ethers.hexlify(aggregateRequestId)}` + ); + + // Generate per-input request IDs + const txidForPerInputRequestId = Buffer.from(unsignedTx.getId(), "hex"); + + // Generate per-input request IDs + const perInputRequestIds: string[] = []; + for (let i = 0; i < inputs.length; i++) { + const inputIndexBytes = Buffer.alloc(4); + inputIndexBytes.writeUInt32LE(i, 0); + // Use the display order txid reversed (internal order) to match server + const txDataForInput = Buffer.concat([ + txidForPerInputRequestId, + inputIndexBytes, + ]); + + const perInputRequestId = signetClient.calculateSignRespondRequestId( + palletSS58, + Array.from(txDataForInput), + { + caip2Id: CHAIN_ID, + keyVersion: 0, + path: aliceHexPath, + algo: "ecdsa", + dest: "bitcoin", + params: "", + } + ); + + perInputRequestIds.push(ethers.hexlify(perInputRequestId)); + console.log(`📋 Input ${i} Request ID: ${perInputRequestIds[i]}`); + } + console.log(""); // Empty line + + // Convert to pallet format + const palletInputs = inputs.map((input: any) => ({ + txid: Array.from(Buffer.from(input.txid, "hex")), + vout: input.vout, + value: input.value, + scriptPubkey: Array.from(input.script), + sequence: 0xffffffff, + })); + + const palletOutputs = outputs.map((output: any) => { + if (output.script) { + return { + value: output.value, + scriptPubkey: Array.from(output.script), + }; + } else { + return { + value: output.value, + scriptPubkey: Array.from( + bitcoin.address.toOutputScript(derivedBtcAddress, network) + ), + }; + } + }); + + const depositTx = api.tx.btcVault.depositBtc( + Array.from(ethers.getBytes(aggregateRequestId)), + palletInputs, + palletOutputs, + 0 + ); + + console.log("🚀 Submitting deposit_btc transaction..."); + const depositResult = await submitWithRetry( + depositTx, + alice, + api, + "Deposit BTC" + ); + + const debugEvent = depositResult.events.find( + (record: any) => + record.event.section === "btcVault" && + record.event.method === "DebugTxid" + ); + + if (debugEvent) { + const palletTxid = debugEvent.event.data[0].toU8a(); + console.log( + `🔍 Pallet computed txid: ${Buffer.from(palletTxid).toString("hex")}` + ); + console.log( + `🔍 Test computed txid: ${Buffer.from(txid).toString("hex")}` + ); + console.log( + `🔍 Match: ${ + Buffer.from(palletTxid).toString("hex") === + Buffer.from(txid).toString("hex") + }` + ); + } else { + console.log("⚠️ DebugTxid event not found"); + } + + const debugTxEvent = depositResult.events.find( + (record: any) => + record.event.section === "btcVault" && + record.event.method === "DebugTransaction" + ); + + if (debugTxEvent) { + const palletTxHex = debugTxEvent.event.data[0].toHex(); + const palletVersion = debugTxEvent.event.data[1].toNumber(); + const palletLocktime = debugTxEvent.event.data[2].toNumber(); + + console.log(`🔍 Pallet PSBT hex: ${palletTxHex}`); + console.log( + `🔍 Pallet version: ${palletVersion}, locktime: ${palletLocktime}` + ); + } + + const signetEvents = depositResult.events.filter( + (record: any) => + record.event.section === "signet" && + record.event.method === "SignBidirectionalRequested" + ); + + console.log( + `📊 Found ${signetEvents.length} SignBidirectionalRequested event(s)` + ); + + if (signetEvents.length > 0) { + console.log( + "✅ SignBidirectionalRequested event emitted - MPC should pick it up!" + ); + } + + console.log("⏳ Waiting for MPC signature(s)..."); + + // Wait for each input's signature separately + const signatures: any[] = []; + for (let i = 0; i < perInputRequestIds.length; i++) { + console.log( + ` Waiting for signature ${i + 1}/${perInputRequestIds.length}...` + ); + const sig = await waitForSingleSignature( + api, + perInputRequestIds[i], + 120000 + ); + signatures.push(sig); + console.log(` ✅ Received signature ${i + 1}`); + } + + console.log( + `\n✅ Received all ${signatures.length} signature(s) from MPC\n` + ); + + const compressedPubkey = compressPubkey(derivedPubKey); + + for (let i = 0; i < signatures.length; i++) { + const sig = signatures[i]; + + const rBuf = + typeof sig.bigR.x === "string" + ? Buffer.from(sig.bigR.x.slice(2), "hex") + : Buffer.from(sig.bigR.x); + + const sBuf = + typeof sig.s === "string" + ? Buffer.from(sig.s.slice(2), "hex") + : Buffer.from(sig.s); + + const { r: normalizedR, s: normalizedS } = normalizeSignature(rBuf, sBuf); + + console.log(`🔍 Signature ${i} verification:`); + console.log(` R: ${normalizedR.toString("hex")}`); + console.log(` S: ${normalizedS.toString("hex")}`); + console.log(` Recovery ID: ${sig.recoveryId}`); + + // VERIFY THE SIGNATURE + const testSighash = testUnsignedTx.hashForWitnessV0( + i, + inputs[i].script!, + inputs[i].value, + bitcoin.Transaction.SIGHASH_ALL + ); + + // Verify signature is valid + const rawSig = Buffer.concat([normalizedR, normalizedS]); + const isValid = ecc.verify(testSighash, compressedPubkey, rawSig); + console.log(` Signature valid (ecc.verify): ${isValid}`); + + const derSig = encodeDER(normalizedR, normalizedS); + const fullSig = Buffer.concat([derSig, Buffer.from([0x01])]); + + psbt.updateInput(i, { + partialSig: [ + { + pubkey: compressedPubkey, + signature: fullSig, + }, + ], + }); + } + + console.log(`🔍 Input 0 witnessUtxo:`, psbt.data.inputs[0].witnessUtxo); + psbt.finalizeAllInputs(); + const signedTx = psbt.extractTransaction(); + console.log(`🔍 Witness for input 0:`); + const witness = signedTx.ins[0].witness; + witness.forEach((w, idx) => { + console.log(` Witness ${idx}: ${w.toString("hex")}`); + }); + const signedTxHex = signedTx.toHex(); + + console.log(`\n🔍 Full signed transaction hex (first 200 chars):`); + console.log(` ${signedTxHex.substring(0, 200)}...`); + console.log( + `🔍 Transaction input 0 sequence: 0x${signedTx.ins[0].sequence.toString( + 16 + )}` + ); + + console.log(`✅ Transaction finalized: ${signedTx.getId()}\n`); + + console.log("📡 Broadcasting transaction to regtest..."); + const broadcastTxid = await btcClient.command( + "sendrawtransaction", + signedTxHex + ); + console.log(` Tx Hash: ${broadcastTxid}`); + + console.log("⛏️ Mining block to confirm transaction..."); + await btcClient.command("generatetoaddress", 1, derivedBtcAddress); + + const txDetails = await btcClient.command( + "getrawtransaction", + broadcastTxid, + true + ); + + console.log( + `✅ Transaction confirmed (${txDetails.confirmations} confirmations)\n` + ); + + console.log("⏳ Waiting for MPC to read transaction result..."); + const readResponse = await waitForReadResponse( + api, + ethers.hexlify(aggregateRequestId), + 120000 + ); + + if (!readResponse) { + throw new Error("❌ Timeout waiting for read response"); + } + + console.log("✅ Received read response\n"); + + console.log("\n🔍 Claim Debug:"); + console.log(" Request ID:", ethers.hexlify(aggregateRequestId)); + console.log( + " Output (hex):", + Buffer.from(readResponse.output).toString("hex") + ); + + let outputBytes = new Uint8Array(readResponse.output); + if (outputBytes.length > 0) { + const mode = outputBytes[0] & 0b11; + if (mode === 0) { + outputBytes = outputBytes.slice(1); + } else if (mode === 1) { + outputBytes = outputBytes.slice(2); + } else if (mode === 2) { + outputBytes = outputBytes.slice(4); + } + } + + console.log( + " Stripped output (hex):", + Buffer.from(outputBytes).toString("hex") + ); + + const balanceBefore = await api.query.btcVault.userBalances(alice.address); + + const claimTx = api.tx.btcVault.claimBtc( + Array.from(ethers.getBytes(aggregateRequestId)), + Array.from(outputBytes), + readResponse.signature + ); + + console.log("🚀 Submitting claim transaction..."); + await submitWithRetry(claimTx, alice, api, "Claim BTC"); + + const balanceAfter = await api.query.btcVault.userBalances(alice.address); + + const balanceIncrease = + BigInt(balanceAfter.toString()) - BigInt(balanceBefore.toString()); + + expect(balanceIncrease.toString()).toBe(depositAmount.toString()); + console.log(`✅ Balance increased by: ${balanceIncrease} sats`); + console.log(` Total balance: ${balanceAfter} sats\n`); + }, 300000); + + function compressPubkey(pubKey: string): Buffer { + const uncompressed = Buffer.from(pubKey.slice(4), "hex"); + const fullUncompressed = Buffer.concat([Buffer.from([0x04]), uncompressed]); + const compressed = ecc.pointCompress(fullUncompressed, true); + return Buffer.from(compressed); + } + + function btcAddressFromPubKey( + pubKey: string, + network: bitcoin.Network + ): string { + const compressedPubkey = compressPubkey(pubKey); + const payment = bitcoin.payments.p2wpkh({ + pubkey: compressedPubkey, + network, + }); + return payment.address!; + } + + function ethAddressFromPubKey(pubKey: string): Uint8Array { + const uncompressedPubkey = Buffer.from(pubKey.slice(4), "hex"); + const hash = ethers.keccak256(uncompressedPubkey); + return new Uint8Array(Buffer.from(hash.slice(2), "hex").slice(-20)); + } + + function encodeDER(r: Buffer, s: Buffer): Buffer { + function toDER(x: Buffer): Buffer { + let i = 0; + while (i < x.length - 1 && x[i] === 0 && x[i + 1] < 0x80) i++; + + const xDER = x.slice(i); + + if (xDER[0] >= 0x80) { + return Buffer.concat([Buffer.from([0x00]), xDER]); + } + + return xDER; + } + + const rDER = toDER(r); + const sDER = toDER(s); + + const len = 2 + rDER.length + 2 + sDER.length; + const buf = Buffer.allocUnsafe(2 + len); + + buf[0] = 0x30; + buf[1] = len; + buf[2] = 0x02; + buf[3] = rDER.length; + rDER.copy(buf, 4); + buf[4 + rDER.length] = 0x02; + buf[5 + rDER.length] = sDER.length; + sDER.copy(buf, 6 + rDER.length); + + return buf; + } + + async function waitForSingleSignature( + api: ApiPromise, + requestId: string, + timeout: number + ): Promise { + return new Promise((resolve, reject) => { + let unsubscribe: any; + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + reject( + new Error( + `Timeout waiting for signature with request ID ${requestId}` + ) + ); + }, timeout); + + api.query.system + .events((events: any) => { + events.forEach((record: any) => { + const { event } = record; + if ( + event.section === "signet" && + event.method === "SignatureResponded" + ) { + const [reqId, responder, signature] = event.data; + if (ethers.hexlify(reqId.toU8a()) === requestId) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + resolve(signature.toJSON()); + } + } + }); + }) + .then((unsub: any) => { + unsubscribe = unsub; + }); + }); + } + + async function waitForReadResponse( + api: ApiPromise, + requestId: string, + timeout: number + ): Promise { + return new Promise((resolve) => { + let unsubscribe: any; + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + resolve(null); + }, timeout); + + api.query.system + .events((events: any) => { + events.forEach((record: any) => { + const { event } = record; + if ( + event.section === "signet" && + event.method === "RespondBidirectionalEvent" + ) { + const [reqId, responder, output, signature] = event.data; + if (ethers.hexlify(reqId.toU8a()) === requestId) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + resolve({ + responder: responder.toString(), + output: Array.from(output.toU8a()), + signature: signature.toJSON(), + }); + } + } + }); + }) + .then((unsub: any) => { + unsubscribe = unsub; + }); + }); + } +}); diff --git a/signet-ts-client-scripts/coinselect.d.ts b/signet-ts-client-scripts/coinselect.d.ts new file mode 100644 index 0000000000..8c421164b7 --- /dev/null +++ b/signet-ts-client-scripts/coinselect.d.ts @@ -0,0 +1,28 @@ +declare module 'coinselect' { + export interface UTXO { + txid: string; + vout: number; + value: number; + script?: Buffer; + } + + export interface Target { + address?: string; + script?: Buffer; + value: number; + } + + export interface CoinSelectResult { + inputs: UTXO[] | undefined; + outputs: Array<{ address?: string; script?: Buffer; value: number }> | undefined; + fee: number; + } + + function coinSelect( + utxos: UTXO[], + targets: Target[], + feeRate: number + ): CoinSelectResult; + + export default coinSelect; +} \ No newline at end of file diff --git a/signet-ts-client-scripts/erc20vault.test.ts b/signet-ts-client-scripts/erc20vault.test.ts new file mode 100644 index 0000000000..dbaa27fe55 --- /dev/null +++ b/signet-ts-client-scripts/erc20vault.test.ts @@ -0,0 +1,553 @@ +import { ApiPromise, WsProvider, Keyring } from "@polkadot/api"; +import { ISubmittableResult } from "@polkadot/types/types"; +import { waitReady } from "@polkadot/wasm-crypto"; +import { u8aToHex } from "@polkadot/util"; +import { encodeAddress } from "@polkadot/keyring"; +import { ethers } from "ethers"; +import { SignetClient } from "./signet-client"; +import { KeyDerivation } from "./key-derivation"; + +const ROOT_PUBLIC_KEY = + "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadfec68fb46fdee257e26b8ba383ddba9914b33c60e869265f859566fff4baef283c54d821ca3b64"; +const CHAIN_ID = "polkadot:2034"; +const USDC_SEPOLIA = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; +const SEPOLIA_RPC = + process.env.SEPOLIA_RPC || + "https://sepolia.infura.io/v3/6df51ccaa17f4e078325b5050da5a2dd"; + +function getPalletAccountId(): Uint8Array { + const palletId = new TextEncoder().encode("py/erc20"); + const modl = new TextEncoder().encode("modl"); + const data = new Uint8Array(32); + data.set(modl, 0); + data.set(palletId, 4); + return data; +} + +async function submitWithRetry( + tx: any, + signer: any, + api: ApiPromise, + label: string, + maxRetries: number = 1, + timeoutMs: number = 60000 // 60 seconds +): Promise<{ events: any[] }> { + let attempt = 0; + + while (attempt <= maxRetries) { + try { + console.log(`${label} - Attempt ${attempt + 1}/${maxRetries + 1}`); + + const result = await new Promise<{ events: any[] }>((resolve, reject) => { + let unsubscribe: any; + + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + console.log(`⏱️ ${label} timed out after ${timeoutMs}ms`); + reject(new Error("TIMEOUT")); + }, timeoutMs); + + tx.signAndSend(signer, (result: ISubmittableResult) => { + const { status, events, dispatchError } = result; + + if (status.isInBlock) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + + console.log( + `✅ ${label} included in block ${status.asInBlock.toHex()}` + ); + + // Check for dispatch errors + if (dispatchError) { + if (dispatchError.isModule) { + const decoded = api.registry.findMetaError( + dispatchError.asModule + ); + reject( + new Error( + `${decoded.section}.${decoded.name}: ${decoded.docs.join( + " " + )}` + ) + ); + } else { + reject(new Error(dispatchError.toString())); + } + return; + } + + resolve({ events: Array.from(events) }); + } else if (status.isInvalid) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + console.log(`⚠️ ${label} marked as Invalid`); + reject(new Error("INVALID_TX")); + } else if (status.isDropped) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + reject(new Error(`${label} dropped`)); + } + }) + .then((unsub: any) => { + unsubscribe = unsub; + }) + .catch((error: any) => { + clearTimeout(timer); + reject(error); + }); + }); + + return result; + } catch (error: any) { + if ( + (error.message === "INVALID_TX" || error.message === "TIMEOUT") && + attempt < maxRetries + ) { + console.log(`🔄 Retrying ${label}...`); + attempt++; + await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2s before retry + continue; + } + throw error; + } + } + + throw new Error(`${label} failed after ${maxRetries + 1} attempts`); +} + +describe("ERC20 Vault Integration", () => { + let api: ApiPromise; + let alice: any; + let signetClient: SignetClient; + let sepoliaProvider: ethers.JsonRpcProvider; + let derivedEthAddress: string; + let derivedPubKey: string; + + beforeAll(async () => { + await waitReady(); + + api = await ApiPromise.create({ + provider: new WsProvider("ws://127.0.0.1:8000"), + types: { + AffinePoint: { x: "[u8; 32]", y: "[u8; 32]" }, + Signature: { big_r: "AffinePoint", s: "[u8; 32]", recovery_id: "u8" }, + }, + }); + + const keyring = new Keyring({ type: "sr25519" }); + alice = keyring.addFromUri("//Alice"); + const bob = keyring.addFromUri("//Bob"); + + const { data: bobBalance } = (await api.query.system.account( + bob.address + )) as any; + + if (bobBalance.free.toBigInt() < 1000000000000n) { + console.log("Funding Bob's account for server responses..."); + + const bobFundTx = api.tx.balances.transferKeepAlive( + bob.address, + 100000000000000n + ); + await submitWithRetry(bobFundTx, alice, api, "Fund Bob account"); + } + + const palletAccountId = getPalletAccountId(); + const palletSS58 = encodeAddress(palletAccountId, 0); + + const { data: palletBalance } = (await api.query.system.account( + palletSS58 + )) as any; + + const fundingAmount = 10000000000000n; + + if (palletBalance.free.toBigInt() < fundingAmount) { + console.log(`Funding ERC20 vault pallet account ${palletSS58}...`); + + const fundTx = api.tx.balances.transferKeepAlive( + palletSS58, + fundingAmount + ); + await submitWithRetry(fundTx, alice, api, "Fund pallet account"); + } + + signetClient = new SignetClient(api, alice); + sepoliaProvider = new ethers.JsonRpcProvider(SEPOLIA_RPC); + + await signetClient.ensureInitialized(CHAIN_ID); + + const aliceAccountId = keyring.decodeAddress(alice.address); + const aliceHexPath = "0x" + u8aToHex(aliceAccountId).slice(2); + + derivedPubKey = KeyDerivation.derivePublicKey( + ROOT_PUBLIC_KEY, + palletSS58, + aliceHexPath, + CHAIN_ID + ); + + derivedEthAddress = ethAddressFromPubKey(derivedPubKey); + + console.log(`\n🔑 Derived Ethereum Address: ${derivedEthAddress}`); + await checkFunding(); + }, 120000); + + afterAll(async () => { + if (api) { + await api.disconnect(); + } + }); + + async function checkFunding() { + const ethBalance = await sepoliaProvider.getBalance(derivedEthAddress); + + const usdcContract = new ethers.Contract( + USDC_SEPOLIA, + ["function balanceOf(address) view returns (uint256)"], + sepoliaProvider + ); + const usdcBalance = await usdcContract.balanceOf(derivedEthAddress); + + const feeData = await sepoliaProvider.getFeeData(); + const gasLimit = 100000n; + const estimatedGas = (feeData.maxFeePerGas || 30000000000n) * gasLimit; + + console.log(`💰 Balances for ${derivedEthAddress}:`); + console.log(` ETH: ${ethers.formatEther(ethBalance)}`); + console.log(` USDC: ${ethers.formatUnits(usdcBalance, 6)}`); + console.log( + ` Estimated gas needed: ${ethers.formatEther(estimatedGas)} ETH\n` + ); + + const minUSDC = ethers.parseUnits("0.01", 6); + + if (ethBalance < estimatedGas) { + throw new Error( + `❌ Insufficient ETH at ${derivedEthAddress}\n` + + ` Need: ${ethers.formatEther(estimatedGas)} ETH\n` + + ` Have: ${ethers.formatEther(ethBalance)} ETH\n` + + ` Please fund this address with ETH for gas` + ); + } + + if (usdcBalance < minUSDC) { + throw new Error( + `❌ Insufficient USDC at ${derivedEthAddress}\n` + + ` Need: 0.01 USDC\n` + + ` Have: ${ethers.formatUnits(usdcBalance, 6)} USDC\n` + + ` Please fund this address with USDC` + ); + } + } + + it("should complete full deposit and claim flow", async () => { + const mpcEthAddress = ethAddressFromPubKey(ROOT_PUBLIC_KEY); + console.log("Checking vault initialization..."); + const mpcAddressBytes = Array.from(ethers.getBytes(mpcEthAddress)); + + const existingConfig = await api.query.erc20Vault.vaultConfig(); + const configJson = existingConfig.toJSON(); + + if (configJson !== null) { + console.log("⚠️ Vault already initialized, skipping initialization"); + console.log(" Existing config:", existingConfig.toHuman()); + } else { + console.log("Initializing vault with MPC address:", mpcEthAddress); + const initTx = api.tx.erc20Vault.initialize(mpcAddressBytes); + await submitWithRetry(initTx, alice, api, "Initialize vault"); + } + + const amount = ethers.parseUnits("0.01", 6); + const feeData = await sepoliaProvider.getFeeData(); + const currentNonce = await sepoliaProvider.getTransactionCount( + derivedEthAddress, + "pending" + ); + + console.log(`📊 Current nonce for ${derivedEthAddress}: ${currentNonce}`); + + const txParams = { + value: 0, + gasLimit: 100000, + maxFeePerGas: Number(feeData.maxFeePerGas || 30000000000n), + maxPriorityFeePerGas: Number(feeData.maxPriorityFeePerGas || 2000000000n), + nonce: currentNonce, + chainId: 11155111, + }; + + const keyring = new Keyring({ type: "sr25519" }); + const palletAccountId = getPalletAccountId(); + const palletSS58 = encodeAddress(palletAccountId, 0); + const aliceAccountId = keyring.decodeAddress(alice.address); + const aliceHexPath = "0x" + u8aToHex(aliceAccountId).slice(2); + + // Build transaction to get request ID + const iface = new ethers.Interface([ + "function transfer(address to, uint256 amount) returns (bool)", + ]); + const data = iface.encodeFunctionData("transfer", [ + "0x00A40C2661293d5134E53Da52951A3F7767836Ef", + amount, + ]); + + const tx = ethers.Transaction.from({ + type: 2, + chainId: txParams.chainId, + nonce: txParams.nonce, + maxPriorityFeePerGas: txParams.maxPriorityFeePerGas, + maxFeePerGas: txParams.maxFeePerGas, + gasLimit: txParams.gasLimit, + to: USDC_SEPOLIA, + value: 0, + data: data, + }); + + const requestId = signetClient.calculateSignRespondRequestId( + palletSS58, + Array.from(ethers.getBytes(tx.unsignedSerialized)), + { + caip2Id: "eip155:11155111", + keyVersion: 0, + path: aliceHexPath, + algo: "ecdsa", + dest: "ethereum", + params: "", + } + ); + + console.log(`📋 Request ID: ${ethers.hexlify(requestId)}\n`); + + const requestIdBytes = + typeof requestId === "string" ? ethers.getBytes(requestId) : requestId; + + const depositTx = api.tx.erc20Vault.depositErc20( + Array.from(requestIdBytes), + Array.from(ethers.getBytes(USDC_SEPOLIA)), + amount.toString(), + txParams + ); + + console.log("🚀 Submitting deposit_erc20 transaction..."); + const depositResult = await submitWithRetry( + depositTx, + alice, + api, + "Deposit ERC20" + ); + + const signetEvents = depositResult.events.filter( + (record: any) => + record.event.section === "signet" && + record.event.method === "SignBidirectionalRequested" + ); + + console.log( + `📊 Found ${signetEvents.length} SignBidirectionalRequested event(s)` + ); + + if (signetEvents.length > 0) { + console.log( + "✅ SignBidirectionalRequested event emitted - MPC should pick it up!" + ); + } else { + console.log("⚠️ No SignBidirectionalRequested event found!"); + } + + console.log("⏳ Waiting for MPC signature..."); + + const signature = await signetClient.waitForSignature( + ethers.hexlify(requestId), + 120000 + ); + + if (!signature) { + throw new Error("❌ Timeout waiting for MPC signature"); + } + + console.log(`✅ Received signature from: ${signature.responder}\n`); + + // Verify signature by recovering address + const signedTx = constructSignedTransaction( + tx.unsignedSerialized, + signature.signature + ); + const recoveredTx = ethers.Transaction.from(signedTx); + const recoveredAddress = recoveredTx.from; + + console.log(`🔍 Signature verification:`); + console.log(` Expected address: ${derivedEthAddress}`); + console.log(` Recovered address: ${recoveredAddress}`); + console.log( + ` Match: ${ + recoveredAddress?.toLowerCase() === derivedEthAddress.toLowerCase() + }` + ); + + if (recoveredAddress?.toLowerCase() !== derivedEthAddress.toLowerCase()) { + throw new Error( + `❌ Signature verification failed!\n` + + ` Expected: ${derivedEthAddress}\n` + + ` Recovered: ${recoveredAddress}\n` + + ` This means the MPC signed with the wrong key or recovery ID is incorrect.` + ); + } + + // Get fresh nonce before broadcasting + const freshNonce = await sepoliaProvider.getTransactionCount( + derivedEthAddress, + "pending" + ); + console.log(`📊 Fresh nonce check: ${freshNonce}`); + + if (freshNonce !== txParams.nonce) { + throw new Error( + `❌ Nonce mismatch! Expected ${txParams.nonce}, but network shows ${freshNonce}.\n` + + ` A transaction may have already been sent from this address.` + ); + } + + console.log("📡 Broadcasting transaction to Sepolia..."); + const txResponse = await sepoliaProvider.broadcastTransaction(signedTx); + console.log(` Tx Hash: ${txResponse.hash}`); + + const receipt = await txResponse.wait(); + console.log(`✅ Transaction confirmed in block ${receipt?.blockNumber}\n`); + + console.log("⏳ Waiting for MPC to read transaction result..."); + const readResponse = await waitForReadResponse( + api, + ethers.hexlify(requestId), + 120000 + ); + + if (!readResponse) { + throw new Error("❌ Timeout waiting for read response"); + } + + console.log("✅ Received read response\n"); + + console.log("\n🔍 Claim Debug:"); + console.log(" Request ID:", ethers.hexlify(requestIdBytes)); + console.log( + " Output (hex):", + Buffer.from(readResponse.output).toString("hex") + ); + + // Strip SCALE compact prefix from output + let outputBytes = new Uint8Array(readResponse.output); + if (outputBytes.length > 0) { + const mode = outputBytes[0] & 0b11; + if (mode === 0) { + outputBytes = outputBytes.slice(1); + } else if (mode === 1) { + outputBytes = outputBytes.slice(2); + } else if (mode === 2) { + outputBytes = outputBytes.slice(4); + } + } + + console.log( + " Stripped output (hex):", + Buffer.from(outputBytes).toString("hex") + ); + + const balanceBefore = await api.query.erc20Vault.userBalances( + alice.address, + Array.from(ethers.getBytes(USDC_SEPOLIA)) + ); + + const claimTx = api.tx.erc20Vault.claimErc20( + Array.from(requestIdBytes), + Array.from(outputBytes), + readResponse.signature + ); + + console.log("🚀 Submitting claim transaction..."); + await submitWithRetry(claimTx, alice, api, "Claim ERC20"); + + const balanceAfter = await api.query.erc20Vault.userBalances( + alice.address, + Array.from(ethers.getBytes(USDC_SEPOLIA)) + ); + + const balanceIncrease = + BigInt(balanceAfter.toString()) - BigInt(balanceBefore.toString()); + + expect(balanceIncrease.toString()).toBe(amount.toString()); + console.log( + `✅ Balance increased by: ${ethers.formatUnits( + balanceIncrease.toString(), + 6 + )} USDC` + ); + console.log( + ` Total balance: ${ethers.formatUnits( + balanceAfter.toString(), + 6 + )} USDC\n` + ); + }, 180000); + + function constructSignedTransaction( + unsignedSerialized: string, + signature: any + ): string { + const tx = ethers.Transaction.from(unsignedSerialized); + + const rHex = ethers.hexlify(signature.bigR.x); + const sHex = ethers.hexlify(signature.s); + + tx.signature = { + r: rHex, + s: sHex, + v: signature.recoveryId, + }; + + return tx.serialized; + } + + async function waitForReadResponse( + api: ApiPromise, + requestId: string, + timeout: number + ): Promise { + return new Promise((resolve) => { + let unsubscribe: any; + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + resolve(null); + }, timeout); + + api.query.system + .events((events: any) => { + events.forEach((record: any) => { + const { event } = record; + if ( + event.section === "signet" && + event.method === "RespondBidirectionalEvent" + ) { + const [reqId, responder, output, signature] = event.data; + if (ethers.hexlify(reqId.toU8a()) === requestId) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + resolve({ + responder: responder.toString(), + output: Array.from(output.toU8a()), + signature: signature.toJSON(), + }); + } + } + }); + }) + .then((unsub: any) => { + unsubscribe = unsub; + }); + }); + } + + function ethAddressFromPubKey(pubKey: string): string { + const hash = ethers.keccak256("0x" + pubKey.slice(4)); + return "0x" + hash.slice(-40); + } +}); diff --git a/signet-ts-client-scripts/key-derivation.ts b/signet-ts-client-scripts/key-derivation.ts new file mode 100644 index 0000000000..3ef2d1e371 --- /dev/null +++ b/signet-ts-client-scripts/key-derivation.ts @@ -0,0 +1,30 @@ +import { ethers } from "ethers"; +import { ec as EC } from "elliptic"; + +export class KeyDerivation { + private static readonly EPSILON_PREFIX = "sig.network v1.0.0 epsilon derivation"; + + static derivePublicKey( + rootPublicKey: string, + predecessorId: string, + path: string, + chainId: string + ): string { + const ec = new EC("secp256k1"); + + const uncompressedRoot = rootPublicKey.slice(4); + + const derivationPath = `${this.EPSILON_PREFIX},${chainId},${predecessorId},${path}`; + const hash = ethers.keccak256(ethers.toUtf8Bytes(derivationPath)); + const scalarHex = hash.slice(2); + + const x = uncompressedRoot.substring(0, 64); + const y = uncompressedRoot.substring(64); + + const oldPoint = ec.curve.point(x, y); + const scalarTimesG = ec.g.mul(scalarHex); + const newPoint = oldPoint.add(scalarTimesG); + + return `0x04${newPoint.getX().toString(16).padStart(64, "0")}${newPoint.getY().toString(16).padStart(64, "0")}`; + } +} \ No newline at end of file diff --git a/signet-ts-client-scripts/package.json b/signet-ts-client-scripts/package.json new file mode 100644 index 0000000000..d46b625b76 --- /dev/null +++ b/signet-ts-client-scripts/package.json @@ -0,0 +1,39 @@ +{ + "name": "hydration-signet-interaction", + "version": "1.0.0", + "description": "TypeScript client for Signet pallet interaction", + "main": "emit-event.ts", + "scripts": { + "test": "jest", + "test:watch": "jest --watch" + }, + "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "testMatch": [ + "**/*.test.ts" + ] + }, + "dependencies": { + "@polkadot/api": "^11.2.1", + "@polkadot/keyring": "^12.6.2", + "@polkadot/util": "^12.6.2", + "bitcoin-regtest": "^1.0.1", + "bitcoinjs-lib": "^6.1.6", + "coinselect": "^3.1.13", + "elliptic": "^6.6.1", + "ethers": "^6.15.0", + "tiny-secp256k1": "^2.2.4", + "ts-node": "^10.9.2", + "typescript": "^5.3.3", + "viem": "^2.37.6" + }, + "devDependencies": { + "@types/elliptic": "^6.4.18", + "@types/jest": "^30.0.0", + "@types/mocha": "^10.0.10", + "@types/node": "^20.11.5", + "jest": "^30.1.3", + "ts-jest": "^29.4.4" + } +} diff --git a/signet-ts-client-scripts/signet-client.ts b/signet-ts-client-scripts/signet-client.ts new file mode 100644 index 0000000000..38f8c33380 --- /dev/null +++ b/signet-ts-client-scripts/signet-client.ts @@ -0,0 +1,223 @@ +import { ApiPromise } from "@polkadot/api"; +import { EventRecord } from "@polkadot/types/interfaces"; +import { Vec } from "@polkadot/types"; +import { u8aToHex } from "@polkadot/util"; +import { ISubmittableResult } from "@polkadot/types/types"; +import { ethers } from "ethers"; +import { keccak256, recoverAddress } from "viem"; + +export class SignetClient { + constructor(private api: ApiPromise, private signer: any) {} + + async ensureInitialized(chainId: string): Promise { + const admin = await this.api.query.signet.admin(); + + if (admin.isEmpty) { + const chainIdBytes = Array.from(new TextEncoder().encode(chainId)); + const tx = this.api.tx.signet.initialize( + this.signer.address, + 1000000000000, + chainIdBytes + ); + await tx.signAndSend(this.signer); + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + } + + async requestSignature(payload: Uint8Array, params: any): Promise { + const tx = this.api.tx.signet.sign( + Array.from(payload), + params.keyVersion, + params.path, + params.algo, + params.dest, + params.params + ); + + await new Promise((resolve, reject) => { + tx.signAndSend(this.signer, (result: ISubmittableResult) => { + const { status, dispatchError } = result; + if (dispatchError) { + reject(dispatchError); + } else if (status.isInBlock) { + resolve(); + } + }).catch(reject); + }); + } + + async requestTransactionSignature( + serializedTx: number[], + params: any + ): Promise { + const caip2Id = params.caip2Id; + const caip2Bytes = Array.from(new TextEncoder().encode(caip2Id)); + + const tx = this.api.tx.signet.signBidirectional( + serializedTx, + caip2Bytes, + params.keyVersion, + params.path, + params.algo || "", + params.dest || "", + params.params || "", + this.signer.address, // program_id + Array.from(new TextEncoder().encode(params.schemas.explorer.schema)), + Array.from(new TextEncoder().encode(params.schemas.callback.schema)) + ); + + await tx.signAndSend(this.signer); + } + + async waitForSignature(requestId: string, timeout: number): Promise { + return new Promise((resolve) => { + let unsubscribe: any; + const timer = setTimeout(() => { + if (unsubscribe) unsubscribe(); + resolve(null); + }, timeout); + + this.api.query.system + .events((events: Vec) => { + events.forEach((record: EventRecord) => { + const { event } = record; + if ( + event.section === "signet" && + event.method === "SignatureResponded" + ) { + const [reqId, responder, signature] = event.data as any; + if (u8aToHex(reqId.toU8a()) === requestId) { + clearTimeout(timer); + if (unsubscribe) unsubscribe(); + resolve({ + responder: responder.toString(), + signature: signature.toJSON(), + }); + } + } + }); + }) + .then((unsub: any) => { + unsubscribe = unsub; + }); + }); + } + + calculateRequestId( + sender: string, + payload: Uint8Array, + params: any, + chainId: string + ): string { + const payloadHex = "0x" + Buffer.from(payload).toString("hex"); + const encoded = ethers.AbiCoder.defaultAbiCoder().encode( + [ + "string", + "bytes", + "string", + "uint32", + "string", + "string", + "string", + "string", + ], + [ + sender, + payloadHex, + params.path, + params.keyVersion, + chainId, + params.algo, + params.dest, + params.params, + ] + ); + return ethers.keccak256(encoded); + } + + calculateSignRespondRequestId( + sender: string, + txData: number[], + params: any + ): string { + const txHex = "0x" + Buffer.from(txData).toString("hex"); + const caip2Id = params.caip2Id; + + const encoded = ethers.solidityPacked( + [ + "string", + "bytes", + "string", + "uint32", + "string", + "string", + "string", + "string", + ], + [ + sender, + txHex, + caip2Id, + params.keyVersion, + params.path, + params.algo || "", + params.dest || "", + params.params || "", + ] + ); + return ethers.keccak256(encoded); + } + + async verifySignature( + payload: Uint8Array, + signature: any, + derivedPublicKey: string + ): Promise { + const r = signature.bigR.x.startsWith("0x") + ? signature.bigR.x + : `0x${signature.bigR.x}`; + const s = signature.s.startsWith("0x") ? signature.s : `0x${signature.s}`; + const v = BigInt(signature.recoveryId + 27); + + const recoveredAddress = await recoverAddress({ + hash: payload as any, + signature: { r, s, v }, + }); + + const expectedAddress = + "0x" + + keccak256(Buffer.from(derivedPublicKey.slice(4), "hex")).slice(-40); + + console.log(" Recovered:", recoveredAddress); + console.log(" Expected: ", expectedAddress); + + return recoveredAddress.toLowerCase() === expectedAddress.toLowerCase(); + } + + async verifyTransactionSignature( + tx: ethers.Transaction, + signature: any, + derivedPublicKey: string + ): Promise { + const msgHash = ethers.keccak256(tx.unsignedSerialized); + const r = signature.bigR.x.startsWith("0x") + ? signature.bigR.x + : `0x${signature.bigR.x}`; + const s = signature.s.startsWith("0x") ? signature.s : `0x${signature.s}`; + const v = BigInt(signature.recoveryId + 27); + + const recoveredAddress = await recoverAddress({ + hash: msgHash as `0x${string}`, + signature: { r, s, v } as any, + }); + + const expectedAddress = + "0x" + + keccak256(Buffer.from(derivedPublicKey.slice(4), "hex")).slice(-40); + + console.log(" Recovered:", recoveredAddress); + console.log(" Expected: ", expectedAddress); + + return recoveredAddress.toLowerCase() === expectedAddress.toLowerCase(); + } +} diff --git a/signet-ts-client-scripts/signet.test.ts b/signet-ts-client-scripts/signet.test.ts new file mode 100644 index 0000000000..db52d385a8 --- /dev/null +++ b/signet-ts-client-scripts/signet.test.ts @@ -0,0 +1,170 @@ +import { ApiPromise, WsProvider, Keyring } from "@polkadot/api"; +import { ISubmittableResult } from "@polkadot/types/types"; +import { waitReady } from "@polkadot/wasm-crypto"; +import { encodeAddress } from "@polkadot/keyring"; +import * as crypto from "crypto"; +import { SignetClient } from "./signet-client"; +import { KeyDerivation } from "./key-derivation"; +import { TransactionBuilder } from "./transaction-builder"; + +describe("Signet Pallet Integration", () => { + let api: ApiPromise; + let client: SignetClient; + let alice: any; + let alicePolkadotAddress: string; + + const ROOT_PUBLIC_KEY = + "0x044eef776e4f257d68983e45b340c2e9546c5df95447900b6aadfec68fb46fdee257e26b8ba383ddba9914b33c60e869265f859566fff4baef283c54d821ca3b64"; + const CHAIN_ID = "polkadot:2034"; + + beforeAll(async () => { + await waitReady(); + + api = await ApiPromise.create({ + provider: new WsProvider("ws://127.0.0.1:8000"), + types: { + AffinePoint: { x: "[u8; 32]", y: "[u8; 32]" }, + Signature: { big_r: "AffinePoint", s: "[u8; 32]", recovery_id: "u8" }, + }, + }); + + const keyring = new Keyring({ type: "sr25519" }); + alice = keyring.addFromUri("//Alice"); + alicePolkadotAddress = encodeAddress(alice.publicKey, 0); + + const bob = keyring.addFromUri("//Bob"); + const { data: bobBalance } = (await api.query.system.account( + bob.address + )) as any; + + if (bobBalance.free.toBigInt() < 1000000000000n) { + console.log("Funding Bob's account for server responses..."); + + await new Promise((resolve, reject) => { + api.tx.balances + .transferKeepAlive(bob.address, 100000000000000n) + .signAndSend(alice, (result: ISubmittableResult) => { + if (result.dispatchError) { + reject(result.dispatchError); + } else if (result.status.isFinalized) { + console.log("Bob's account funded!"); + resolve(result.status.asFinalized); + } + }); + }); + } + + client = new SignetClient(api, alice); + await client.ensureInitialized(CHAIN_ID); + }, 60000); + + afterAll(async () => { + await api.disconnect(); + }); + + describe("Sign", () => { + it("should request and verify a signature", async () => { + const payload = crypto.randomBytes(32); + const params = { + keyVersion: 1, + path: "testPath", + algo: "ecdsa", + dest: "", + params: "{}", + }; + + const requestId = client.calculateRequestId( + alicePolkadotAddress, + payload, + params, + CHAIN_ID + ); + const derivedKey = KeyDerivation.derivePublicKey( + ROOT_PUBLIC_KEY, + alicePolkadotAddress, + params.path, + CHAIN_ID + ); + + await client.requestSignature(payload, params); + + const signature = await client.waitForSignature(requestId, 30000); + expect(signature).toBeDefined(); + expect(signature.responder).toBeTruthy(); + + console.log("\n ✅ Signature received from:", signature.responder); + + const isValid = await client.verifySignature( + payload, + signature.signature, + derivedKey + ); + expect(isValid).toBe(true); + + console.log(" ✅ Signature verification PASSED"); + }, 80000); + }); + + describe("SignRespond", () => { + it("should request and verify a transaction signature", async () => { + const tx = TransactionBuilder.buildEIP1559({ + chainId: 11155111, + nonce: 0, + maxPriorityFeePerGas: BigInt("2000000000"), + maxFeePerGas: BigInt("30000000000"), + gasLimit: 10000, + to: "0x0000000000000000000000000000000000000000", + value: BigInt(0), + data: "0x", + accessList: [], + }); + + const params = { + caip2Id: "eip155:11155111", + keyVersion: 0, + path: "testPath", + algo: "", + dest: "", + params: "", + schemas: { + explorer: { schema: "{}" }, + callback: { schema: "{}" }, + }, + }; + + const requestId = client.calculateSignRespondRequestId( + alicePolkadotAddress, + tx.serialized, + params + ); + + console.log("Test calculated request ID:", requestId); + + const derivedKey = KeyDerivation.derivePublicKey( + ROOT_PUBLIC_KEY, + alicePolkadotAddress, + params.path, + CHAIN_ID + ); + + await client.requestTransactionSignature(tx.serialized, params); + + const signature = await client.waitForSignature(requestId, 30000); + expect(signature).toBeDefined(); + + console.log( + "\n ✅ Transaction signature received from:", + signature.responder + ); + + const isValid = await client.verifyTransactionSignature( + tx.transaction, + signature.signature, + derivedKey + ); + expect(isValid).toBe(true); + + console.log(" ✅ Transaction signature verification PASSED"); + }, 80000); + }); +}); diff --git a/signet-ts-client-scripts/transaction-builder.ts b/signet-ts-client-scripts/transaction-builder.ts new file mode 100644 index 0000000000..478c9d9f45 --- /dev/null +++ b/signet-ts-client-scripts/transaction-builder.ts @@ -0,0 +1,28 @@ +import { ethers } from "ethers"; + +export class TransactionBuilder { + static buildEIP1559(params: { + chainId: number; + nonce: number; + maxPriorityFeePerGas: bigint; + maxFeePerGas: bigint; + gasLimit: number; + to: string; + value: bigint; + data: string; + accessList: any[]; + }): { transaction: ethers.Transaction; serialized: number[]; unwrapped: number[] } { + const transaction = ethers.Transaction.from({ + type: 2, + ...params + }); + + const fullSerialized = Array.from(ethers.getBytes(transaction.unsignedSerialized)); + + return { + transaction, + serialized: fullSerialized, + unwrapped: fullSerialized.slice(1) + }; + } +} \ No newline at end of file diff --git a/signet-ts-client-scripts/tsconfig.json b/signet-ts-client-scripts/tsconfig.json new file mode 100644 index 0000000000..5e71b205bf --- /dev/null +++ b/signet-ts-client-scripts/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "types": ["jest", "node"] + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/signet-ts-client-scripts/yarn.lock b/signet-ts-client-scripts/yarn.lock new file mode 100644 index 0000000000..aab6414af4 --- /dev/null +++ b/signet-ts-client-scripts/yarn.lock @@ -0,0 +1,3776 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + +"@adraffy/ens-normalize@^1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" + integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== + +"@babel/core@^7.23.9", "@babel/core@^7.27.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" + integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.4" + "@babel/types" "^7.28.4" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.27.5", "@babel/generator@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@emnapi/core@^1.4.3": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.5.0.tgz#85cd84537ec989cebb2343606a1ee663ce4edaf0" + integrity sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg== + dependencies: + "@emnapi/wasi-threads" "1.1.0" + tslib "^2.4.0" + +"@emnapi/runtime@^1.4.3": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.5.0.tgz#9aebfcb9b17195dce3ab53c86787a6b7d058db73" + integrity sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" + integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== + dependencies: + tslib "^2.4.0" + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-30.1.2.tgz#3d32b966454d57874520b27647129228a654c995" + integrity sha512-BGMAxj8VRmoD0MoA/jo9alMXSRoqW8KPeqOfEo1ncxnRLatTBCpRoOwlwlEMdudp68Q6WSGwYrrLtTGOh8fLzw== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + jest-message-util "30.1.0" + jest-util "30.0.5" + slash "^3.0.0" + +"@jest/core@30.1.3": + version "30.1.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-30.1.3.tgz#c097dcead36ac6ccee2825a35078163465f8b79d" + integrity sha512-LIQz7NEDDO1+eyOA2ZmkiAyYvZuo6s1UxD/e2IHldR6D7UYogVq3arTmli07MkENLq6/3JEQjp0mA8rrHHJ8KQ== + dependencies: + "@jest/console" "30.1.2" + "@jest/pattern" "30.0.1" + "@jest/reporters" "30.1.3" + "@jest/test-result" "30.1.3" + "@jest/transform" "30.1.2" + "@jest/types" "30.0.5" + "@types/node" "*" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + ci-info "^4.2.0" + exit-x "^0.2.2" + graceful-fs "^4.2.11" + jest-changed-files "30.0.5" + jest-config "30.1.3" + jest-haste-map "30.1.0" + jest-message-util "30.1.0" + jest-regex-util "30.0.1" + jest-resolve "30.1.3" + jest-resolve-dependencies "30.1.3" + jest-runner "30.1.3" + jest-runtime "30.1.3" + jest-snapshot "30.1.2" + jest-util "30.0.5" + jest-validate "30.1.0" + jest-watcher "30.1.3" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + +"@jest/diff-sequences@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + +"@jest/environment@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.1.2.tgz#f1bd73a7571f96104a3ff2007747c2ce12b5c038" + integrity sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w== + dependencies: + "@jest/fake-timers" "30.1.2" + "@jest/types" "30.0.5" + "@types/node" "*" + jest-mock "30.0.5" + +"@jest/expect-utils@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.1.2.tgz#88ea18040f707c9fadb6fd9e77568cae5266cee8" + integrity sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A== + dependencies: + "@jest/get-type" "30.1.0" + +"@jest/expect@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.1.2.tgz#35283e8bd083aab6cc26d4d30aeeacb5e7190a0f" + integrity sha512-tyaIExOwQRCxPCGNC05lIjWJztDwk2gPDNSDGg1zitXJJ8dC3++G/CRjE5mb2wQsf89+lsgAgqxxNpDLiCViTA== + dependencies: + expect "30.1.2" + jest-snapshot "30.1.2" + +"@jest/fake-timers@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.1.2.tgz#cb0df6995034d50c6973ffd3ffdaa1353a816c41" + integrity sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA== + dependencies: + "@jest/types" "30.0.5" + "@sinonjs/fake-timers" "^13.0.0" + "@types/node" "*" + jest-message-util "30.1.0" + jest-mock "30.0.5" + jest-util "30.0.5" + +"@jest/get-type@30.1.0": + version "30.1.0" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" + integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== + +"@jest/globals@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.1.2.tgz#821cad7d8ef3dc145979088bb0bfbc1f81a5d8ce" + integrity sha512-teNTPZ8yZe3ahbYnvnVRDeOjr+3pu2uiAtNtrEsiMjVPPj+cXd5E/fr8BL7v/T7F31vYdEHrI5cC/2OoO/vM9A== + dependencies: + "@jest/environment" "30.1.2" + "@jest/expect" "30.1.2" + "@jest/types" "30.0.5" + jest-mock "30.0.5" + +"@jest/pattern@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/pattern/-/pattern-30.0.1.tgz#d5304147f49a052900b4b853dedb111d080e199f" + integrity sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA== + dependencies: + "@types/node" "*" + jest-regex-util "30.0.1" + +"@jest/reporters@30.1.3": + version "30.1.3" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-30.1.3.tgz#015b5838b3edf60f6e995186cd805b7fcbac86b3" + integrity sha512-VWEQmJWfXMOrzdFEOyGjUEOuVXllgZsoPtEHZzfdNz18RmzJ5nlR6kp8hDdY8dDS1yGOXAY7DHT+AOHIPSBV0w== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "30.1.2" + "@jest/test-result" "30.1.3" + "@jest/transform" "30.1.2" + "@jest/types" "30.0.5" + "@jridgewell/trace-mapping" "^0.3.25" + "@types/node" "*" + chalk "^4.1.2" + collect-v8-coverage "^1.0.2" + exit-x "^0.2.2" + glob "^10.3.10" + graceful-fs "^4.2.11" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^5.0.0" + istanbul-reports "^3.1.3" + jest-message-util "30.1.0" + jest-util "30.0.5" + jest-worker "30.1.0" + slash "^3.0.0" + string-length "^4.0.2" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473" + integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA== + dependencies: + "@sinclair/typebox" "^0.34.0" + +"@jest/snapshot-utils@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.1.2.tgz#320500eba29a25c33e9ec968154e521873624309" + integrity sha512-vHoMTpimcPSR7OxS2S0V1Cpg8eKDRxucHjoWl5u4RQcnxqQrV3avETiFpl8etn4dqxEGarBeHbIBety/f8mLXw== + dependencies: + "@jest/types" "30.0.5" + chalk "^4.1.2" + graceful-fs "^4.2.11" + natural-compare "^1.4.0" + +"@jest/source-map@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-30.0.1.tgz#305ebec50468f13e658b3d5c26f85107a5620aaa" + integrity sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + callsites "^3.1.0" + graceful-fs "^4.2.11" + +"@jest/test-result@30.1.3": + version "30.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-30.1.3.tgz#69fe7ff93da8c0c47bae245727e0ce23571d058e" + integrity sha512-P9IV8T24D43cNRANPPokn7tZh0FAFnYS2HIfi5vK18CjRkTDR9Y3e1BoEcAJnl4ghZZF4Ecda4M/k41QkvurEQ== + dependencies: + "@jest/console" "30.1.2" + "@jest/types" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + collect-v8-coverage "^1.0.2" + +"@jest/test-sequencer@30.1.3": + version "30.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-30.1.3.tgz#df64038d46150e704ed07c5fee4626609f518089" + integrity sha512-82J+hzC0qeQIiiZDThh+YUadvshdBswi5nuyXlEmXzrhw5ZQSRHeQ5LpVMD/xc8B3wPePvs6VMzHnntxL+4E3w== + dependencies: + "@jest/test-result" "30.1.3" + graceful-fs "^4.2.11" + jest-haste-map "30.1.0" + slash "^3.0.0" + +"@jest/transform@30.1.2": + version "30.1.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.1.2.tgz#42624a9c89f2427cd413b989aaf9f6aeb58cae56" + integrity sha512-UYYFGifSgfjujf1Cbd3iU/IQoSd6uwsj8XHj5DSDf5ERDcWMdJOPTkHWXj4U+Z/uMagyOQZ6Vne8C4nRIrCxqA== + dependencies: + "@babel/core" "^7.27.4" + "@jest/types" "30.0.5" + "@jridgewell/trace-mapping" "^0.3.25" + babel-plugin-istanbul "^7.0.0" + chalk "^4.1.2" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.11" + jest-haste-map "30.1.0" + jest-regex-util "30.0.1" + jest-util "30.0.5" + micromatch "^4.0.8" + pirates "^4.0.7" + slash "^3.0.0" + write-file-atomic "^5.0.1" + +"@jest/types@30.0.5": + version "30.0.5" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-30.0.5.tgz#29a33a4c036e3904f1cfd94f6fe77f89d2e1cc05" + integrity sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ== + dependencies: + "@jest/pattern" "30.0.1" + "@jest/schemas" "30.0.5" + "@types/istanbul-lib-coverage" "^2.0.6" + "@types/istanbul-reports" "^3.0.4" + "@types/node" "*" + "@types/yargs" "^17.0.33" + chalk "^4.1.2" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@napi-rs/wasm-runtime@^0.2.11": + version "0.2.12" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" + integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== + dependencies: + "@emnapi/core" "^1.4.3" + "@emnapi/runtime" "^1.4.3" + "@tybys/wasm-util" "^0.10.0" + +"@noble/ciphers@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.3.0.tgz#f64b8ff886c240e644e5573c097f86e5b43676dc" + integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== + +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + +"@noble/curves@1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.1.tgz#9654a0bc6c13420ae252ddcf975eaf0f58f0a35c" + integrity sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA== + dependencies: + "@noble/hashes" "1.8.0" + +"@noble/curves@^1.3.0", "@noble/curves@~1.9.0": + version "1.9.7" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.7.tgz#79d04b4758a43e4bca2cbdc62e7771352fa6b951" + integrity sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw== + dependencies: + "@noble/hashes" "1.8.0" + +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + +"@noble/hashes@1.8.0", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@^1.8.0", "@noble/hashes@~1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@pkgr/core@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" + integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== + +"@polkadot-api/json-rpc-provider-proxy@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.0.1.tgz#bb5c943642cdf0ec7bc48c0a2647558b9fcd7bdb" + integrity sha512-gmVDUP8LpCH0BXewbzqXF2sdHddq1H1q+XrAW2of+KZj4woQkIGBRGTJHeBEVHe30EB+UejR1N2dT4PO/RvDdg== + +"@polkadot-api/json-rpc-provider@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1.tgz#333645d40ccd9bccfd1f32503f17e4e63e76e297" + integrity sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA== + +"@polkadot-api/metadata-builders@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/metadata-builders/-/metadata-builders-0.0.1.tgz#a76b48febef9ea72be8273d889e2677101045a05" + integrity sha512-GCI78BHDzXAF/L2pZD6Aod/yl82adqQ7ftNmKg51ixRL02JpWUA+SpUKTJE5MY1p8kiJJIo09P2um24SiJHxNA== + dependencies: + "@polkadot-api/substrate-bindings" "0.0.1" + "@polkadot-api/utils" "0.0.1" + +"@polkadot-api/observable-client@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@polkadot-api/observable-client/-/observable-client-0.1.0.tgz#472045ea06a2bc4bccdc2db5c063eadcbf6f5351" + integrity sha512-GBCGDRztKorTLna/unjl/9SWZcRmvV58o9jwU2Y038VuPXZcr01jcw/1O3x+yeAuwyGzbucI/mLTDa1QoEml3A== + dependencies: + "@polkadot-api/metadata-builders" "0.0.1" + "@polkadot-api/substrate-bindings" "0.0.1" + "@polkadot-api/substrate-client" "0.0.1" + "@polkadot-api/utils" "0.0.1" + +"@polkadot-api/substrate-bindings@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/substrate-bindings/-/substrate-bindings-0.0.1.tgz#c4b7f4d6c3672d2c15cbc6c02964f014b73cbb0b" + integrity sha512-bAe7a5bOPnuFVmpv7y4BBMRpNTnMmE0jtTqRUw/+D8ZlEHNVEJQGr4wu3QQCl7k1GnSV1wfv3mzIbYjErEBocg== + dependencies: + "@noble/hashes" "^1.3.1" + "@polkadot-api/utils" "0.0.1" + "@scure/base" "^1.1.1" + scale-ts "^1.6.0" + +"@polkadot-api/substrate-client@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/substrate-client/-/substrate-client-0.0.1.tgz#0e8010a0abe2fb47d6fa7ab94e45e1d0de083314" + integrity sha512-9Bg9SGc3AwE+wXONQoW8GC00N3v6lCZLW74HQzqB6ROdcm5VAHM4CB/xRzWSUF9CXL78ugiwtHx3wBcpx4H4Wg== + +"@polkadot-api/utils@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/utils/-/utils-0.0.1.tgz#908b22becac705149d7cc946532143d0fb003bfc" + integrity sha512-3j+pRmlF9SgiYDabSdZsBSsN5XHbpXOAce1lWj56IEEaFZVjsiCaxDOA7C9nCcgfVXuvnbxqqEGQvnY+QfBAUw== + +"@polkadot/api-augment@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-augment/-/api-augment-11.3.1.tgz#c7deeac438b017613e244c25505216a9d4c3977e" + integrity sha512-Yj+6rb6h0WwY3yJ+UGhjGW+tyMRFUMsKQuGw+eFsXdjiNU9UoXsAqA2dG7Q1F+oeX/g+y2gLGBezNoCwbl6HfA== + dependencies: + "@polkadot/api-base" "11.3.1" + "@polkadot/rpc-augment" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-augment" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/api-base@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-base/-/api-base-11.3.1.tgz#6c74c88d4a4b3d22344bb8715a135493f5a3dd33" + integrity sha512-b8UkNL00NN7+3QaLCwL5cKg+7YchHoKCAhwKusWHNBZkkO6Oo2BWilu0dZkPJOyqV9P389Kbd9+oH+SKs9u2VQ== + dependencies: + "@polkadot/rpc-core" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/util" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/api-derive@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-11.3.1.tgz#3617655b6dab56d5beb8efbf7383ab457370df35" + integrity sha512-9dopzrh4cRuft1nANmBvMY/hEhFDu0VICMTOGxQLOl8NMfcOFPTLAN0JhSBUoicGZhV+c4vpv01NBx/7/IL1HA== + dependencies: + "@polkadot/api" "11.3.1" + "@polkadot/api-augment" "11.3.1" + "@polkadot/api-base" "11.3.1" + "@polkadot/rpc-core" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/api@11.3.1", "@polkadot/api@^11.2.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-11.3.1.tgz#6092aea8147ea03873b3f383cceae0390a67f71d" + integrity sha512-q4kFIIHTLvKxM24b0Eo8hJevsPMme+aITJGrDML9BgdZYTRN14+cu5nXiCsQvaEamdyYj+uCXWe2OV9X7pPxsA== + dependencies: + "@polkadot/api-augment" "11.3.1" + "@polkadot/api-base" "11.3.1" + "@polkadot/api-derive" "11.3.1" + "@polkadot/keyring" "^12.6.2" + "@polkadot/rpc-augment" "11.3.1" + "@polkadot/rpc-core" "11.3.1" + "@polkadot/rpc-provider" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-augment" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/types-create" "11.3.1" + "@polkadot/types-known" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + eventemitter3 "^5.0.1" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/keyring@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-12.6.2.tgz#6067e6294fee23728b008ac116e7e9db05cecb9b" + integrity sha512-O3Q7GVmRYm8q7HuB3S0+Yf/q/EB2egKRRU3fv9b3B7V+A52tKzA+vIwEmNVaD1g5FKW9oB97rmpggs0zaKFqHw== + dependencies: + "@polkadot/util" "12.6.2" + "@polkadot/util-crypto" "12.6.2" + tslib "^2.6.2" + +"@polkadot/networks@12.6.2", "@polkadot/networks@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-12.6.2.tgz#791779fee1d86cc5b6cd371858eea9b7c3f8720d" + integrity sha512-1oWtZm1IvPWqvMrldVH6NI2gBoCndl5GEwx7lAuQWGr7eNL+6Bdc5K3Z9T0MzFvDGoi2/CBqjX9dRKo39pDC/w== + dependencies: + "@polkadot/util" "12.6.2" + "@substrate/ss58-registry" "^1.44.0" + tslib "^2.6.2" + +"@polkadot/rpc-augment@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-augment/-/rpc-augment-11.3.1.tgz#b589ef5b7ab578cf274077604543071ce9889301" + integrity sha512-2PaDcKNju4QYQpxwVkWbRU3M0t340nMX9cMo+8awgvgL1LliV/fUDZueMKLuSS910JJMTPQ7y2pK4eQgMt08gQ== + dependencies: + "@polkadot/rpc-core" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/rpc-core@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-11.3.1.tgz#c23e23ee5c403c4edb207d603ae4dc16e69bc710" + integrity sha512-KKNepsDd/mpmXcA6v/h14eFFPEzLGd7nrvx2UUXUxoZ0Fq2MH1hplP3s93k1oduNY/vOXJR2K9S4dKManA6GVQ== + dependencies: + "@polkadot/rpc-augment" "11.3.1" + "@polkadot/rpc-provider" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/util" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/rpc-provider@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-11.3.1.tgz#1d1289bf42d065b5f04f9baa46ef90d96940819e" + integrity sha512-pqERChoHo45hd3WAgW8UuzarRF+G/o/eXEbl0PXLubiayw4X4qCmIzmtntUcKYgxGNcYGZaG87ZU8OjN97m6UA== + dependencies: + "@polkadot/keyring" "^12.6.2" + "@polkadot/types" "11.3.1" + "@polkadot/types-support" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + "@polkadot/x-fetch" "^12.6.2" + "@polkadot/x-global" "^12.6.2" + "@polkadot/x-ws" "^12.6.2" + eventemitter3 "^5.0.1" + mock-socket "^9.3.1" + nock "^13.5.0" + tslib "^2.6.2" + optionalDependencies: + "@substrate/connect" "0.8.10" + +"@polkadot/types-augment@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-augment/-/types-augment-11.3.1.tgz#1f7f553f0ca6eb8fbc0306901edc045fe18729e1" + integrity sha512-eR3HVpvUmB3v7q2jTWVmVfAVfb1/kuNn7ij94Zqadg/fuUq0pKqIOKwkUj3OxRM3A/5BnW3MbgparjKD3r+fyw== + dependencies: + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-codec@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-codec/-/types-codec-11.3.1.tgz#2767cf482cd49afdd5dce9701615f68ec59cec5e" + integrity sha512-i7IiiuuL+Z/jFoKTA9xeh4wGQnhnNNjMT0+1ohvlOvnFsoKZKFQQOaDPPntGJVL1JDCV+KjkN2uQKZSeW8tguQ== + dependencies: + "@polkadot/util" "^12.6.2" + "@polkadot/x-bigint" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-create@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-create/-/types-create-11.3.1.tgz#3ac2c8283f61555f9e572ca30e3485b95a0a54e2" + integrity sha512-pBXtpz5FehcRJ6j5MzFUIUN8ZWM7z6HbqK1GxBmYbJVRElcGcOg7a/rL2pQVphU0Rx1E8bSO4thzGf4wUxSX7w== + dependencies: + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-known@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-11.3.1.tgz#fc34ed29ac2474db6b66805a15d12008226346bb" + integrity sha512-3BIof7u6tn9bk3ZCIxA07iNoQ3uj4+vn3DTOjCKECozkRlt6V+kWRvqh16Hc0SHMg/QjcMb2fIu/WZhka1McUQ== + dependencies: + "@polkadot/networks" "^12.6.2" + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/types-create" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-support@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-support/-/types-support-11.3.1.tgz#dee02a67784baa13177fe9957f5d8d62e8a7e570" + integrity sha512-jTFz1GKyF7nI29yIOq4v0NiWTOf5yX4HahJNeFD8TcxoLhF+6tH/XXqrUXJEfbaTlSrRWiW1LZYlb+snctqKHA== + dependencies: + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-11.3.1.tgz#bab61b701218158099e3f548d20efc27cbf1287f" + integrity sha512-5c7uRFXQTT11Awi6T0yFIdAfD6xGDAOz06Kp7M5S9OGNZY28wSPk5x6BYfNphWPaIBmHHewYJB5qmnrdYQAWKQ== + dependencies: + "@polkadot/keyring" "^12.6.2" + "@polkadot/types-augment" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/types-create" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/util-crypto@12.6.2", "@polkadot/util-crypto@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-12.6.2.tgz#d2d51010e8e8ca88951b7d864add797dad18bbfc" + integrity sha512-FEWI/dJ7wDMNN1WOzZAjQoIcCP/3vz3wvAp5QQm+lOrzOLj0iDmaIGIcBkz8HVm3ErfSe/uKP0KS4jgV/ib+Mg== + dependencies: + "@noble/curves" "^1.3.0" + "@noble/hashes" "^1.3.3" + "@polkadot/networks" "12.6.2" + "@polkadot/util" "12.6.2" + "@polkadot/wasm-crypto" "^7.3.2" + "@polkadot/wasm-util" "^7.3.2" + "@polkadot/x-bigint" "12.6.2" + "@polkadot/x-randomvalues" "12.6.2" + "@scure/base" "^1.1.5" + tslib "^2.6.2" + +"@polkadot/util@12.6.2", "@polkadot/util@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-12.6.2.tgz#9396eff491221e1f0fd28feac55fc16ecd61a8dc" + integrity sha512-l8TubR7CLEY47240uki0TQzFvtnxFIO7uI/0GoWzpYD/O62EIAMRsuY01N4DuwgKq2ZWD59WhzsLYmA5K6ksdw== + dependencies: + "@polkadot/x-bigint" "12.6.2" + "@polkadot/x-global" "12.6.2" + "@polkadot/x-textdecoder" "12.6.2" + "@polkadot/x-textencoder" "12.6.2" + "@types/bn.js" "^5.1.5" + bn.js "^5.2.1" + tslib "^2.6.2" + +"@polkadot/wasm-bridge@7.5.1": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-bridge/-/wasm-bridge-7.5.1.tgz#f738858213a8a599ae8bf6a6c179b325dcf091f4" + integrity sha512-E+N3CSnX3YaXpAmfIQ+4bTyiAqJQKvVcMaXjkuL8Tp2zYffClWLG5e+RY15Uh+EWfUl9If4y6cLZi3D5NcpAGQ== + dependencies: + "@polkadot/wasm-util" "7.5.1" + tslib "^2.7.0" + +"@polkadot/wasm-crypto-asmjs@7.5.1": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.5.1.tgz#87e07aa340249d5c978cd03eb58b395563066a4c" + integrity sha512-jAg7Uusk+xeHQ+QHEH4c/N3b1kEGBqZb51cWe+yM61kKpQwVGZhNdlWetW6U23t/BMyZArIWMsZqmK/Ij0PHog== + dependencies: + tslib "^2.7.0" + +"@polkadot/wasm-crypto-init@7.5.1": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.5.1.tgz#0434850b7f05619ff312d5cbfd33629a54f9b31a" + integrity sha512-Obu4ZEo5jYO6sN31eqCNOXo88rPVkP9TrUOyynuFCnXnXr8V/HlmY/YkAd9F87chZnkTJRlzak17kIWr+i7w3A== + dependencies: + "@polkadot/wasm-bridge" "7.5.1" + "@polkadot/wasm-crypto-asmjs" "7.5.1" + "@polkadot/wasm-crypto-wasm" "7.5.1" + "@polkadot/wasm-util" "7.5.1" + tslib "^2.7.0" + +"@polkadot/wasm-crypto-wasm@7.5.1": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.5.1.tgz#b3996007875db6945d29f94f4d4719fce2b3bb8f" + integrity sha512-S2yQSGbOGTcaV6UdipFVyEGanJvG6uD6Tg7XubxpiGbNAblsyYKeFcxyH1qCosk/4qf+GIUwlOL4ydhosZflqg== + dependencies: + "@polkadot/wasm-util" "7.5.1" + tslib "^2.7.0" + +"@polkadot/wasm-crypto@^7.3.2": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto/-/wasm-crypto-7.5.1.tgz#324ebf9a86a30fd19bf4b02a6582367bdddb62c9" + integrity sha512-acjt4VJ3w19v7b/SIPsV/5k9s6JsragHKPnwoZ0KTfBvAFXwzz80jUzVGxA06SKHacfCUe7vBRlz7M5oRby1Pw== + dependencies: + "@polkadot/wasm-bridge" "7.5.1" + "@polkadot/wasm-crypto-asmjs" "7.5.1" + "@polkadot/wasm-crypto-init" "7.5.1" + "@polkadot/wasm-crypto-wasm" "7.5.1" + "@polkadot/wasm-util" "7.5.1" + tslib "^2.7.0" + +"@polkadot/wasm-util@7.5.1", "@polkadot/wasm-util@^7.3.2": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-util/-/wasm-util-7.5.1.tgz#4568a9bf8d02d2d68fc139f331719865300e5233" + integrity sha512-sbvu71isFhPXpvMVX+EkRnUg/+54Tx7Sf9BEMqxxoPj7cG1I/MKeDEwbQz6MaU4gm7xJqvEWCAemLFcXfHQ/2A== + dependencies: + tslib "^2.7.0" + +"@polkadot/x-bigint@12.6.2", "@polkadot/x-bigint@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-bigint/-/x-bigint-12.6.2.tgz#59b7a615f205ae65e1ac67194aefde94d3344580" + integrity sha512-HSIk60uFPX4GOFZSnIF7VYJz7WZA7tpFJsne7SzxOooRwMTWEtw3fUpFy5cYYOeLh17/kHH1Y7SVcuxzVLc74Q== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-fetch@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-12.6.2.tgz#b1bca028db90263bafbad2636c18d838d842d439" + integrity sha512-8wM/Z9JJPWN1pzSpU7XxTI1ldj/AfC8hKioBlUahZ8gUiJaOF7K9XEFCrCDLis/A1BoOu7Ne6WMx/vsJJIbDWw== + dependencies: + "@polkadot/x-global" "12.6.2" + node-fetch "^3.3.2" + tslib "^2.6.2" + +"@polkadot/x-global@12.6.2", "@polkadot/x-global@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-12.6.2.tgz#31d4de1c3d4c44e4be3219555a6d91091decc4ec" + integrity sha512-a8d6m+PW98jmsYDtAWp88qS4dl8DyqUBsd0S+WgyfSMtpEXu6v9nXDgPZgwF5xdDvXhm+P0ZfVkVTnIGrScb5g== + dependencies: + tslib "^2.6.2" + +"@polkadot/x-randomvalues@12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-12.6.2.tgz#13fe3619368b8bf5cb73781554859b5ff9d900a2" + integrity sha512-Vr8uG7rH2IcNJwtyf5ebdODMcr0XjoCpUbI91Zv6AlKVYOGKZlKLYJHIwpTaKKB+7KPWyQrk4Mlym/rS7v9feg== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-textdecoder@12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-12.6.2.tgz#b86da0f8e8178f1ca31a7158257e92aea90b10e4" + integrity sha512-M1Bir7tYvNappfpFWXOJcnxUhBUFWkUFIdJSyH0zs5LmFtFdbKAeiDXxSp2Swp5ddOZdZgPac294/o2TnQKN1w== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-textencoder@12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-12.6.2.tgz#81d23bd904a2c36137a395c865c5fefa21abfb44" + integrity sha512-4N+3UVCpI489tUJ6cv3uf0PjOHvgGp9Dl+SZRLgFGt9mvxnvpW/7+XBADRMtlG4xi5gaRK7bgl5bmY6OMDsNdw== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-ws@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-12.6.2.tgz#b99094d8e53a03be1de903d13ba59adaaabc767a" + integrity sha512-cGZWo7K5eRRQCRl2LrcyCYsrc3lRbTlixZh3AzgU8uX4wASVGRlNWi/Hf4TtHNe1ExCDmxabJzdIsABIfrr7xw== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + ws "^8.15.1" + +"@scure/base@^1.1.1", "@scure/base@^1.1.5", "@scure/base@~1.2.5": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.6.tgz#ca917184b8231394dd8847509c67a0be522e59f6" + integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== + +"@scure/bip32@1.7.0", "@scure/bip32@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.7.0.tgz#b8683bab172369f988f1589640e53c4606984219" + integrity sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw== + dependencies: + "@noble/curves" "~1.9.0" + "@noble/hashes" "~1.8.0" + "@scure/base" "~1.2.5" + +"@scure/bip39@1.6.0", "@scure/bip39@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.6.0.tgz#475970ace440d7be87a6086cbee77cb8f1a684f9" + integrity sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A== + dependencies: + "@noble/hashes" "~1.8.0" + "@scure/base" "~1.2.5" + +"@sinclair/typebox@^0.34.0": + version "0.34.41" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.41.tgz#aa51a6c1946df2c5a11494a2cdb9318e026db16c" + integrity sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g== + +"@sinonjs/commons@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^13.0.0": + version "13.0.5" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" + integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== + dependencies: + "@sinonjs/commons" "^3.0.1" + +"@substrate/connect-extension-protocol@^2.0.0": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.2.2.tgz#2cf8f2eaf1879308d307a1a08df83cd5db918fe0" + integrity sha512-t66jwrXA0s5Goq82ZtjagLNd7DPGCNjHeehRlE/gcJmJ+G56C0W+2plqOMRicJ8XGR1/YFnUSEqUFiSNbjGrAA== + +"@substrate/connect-known-chains@^1.1.4": + version "1.10.3" + resolved "https://registry.yarnpkg.com/@substrate/connect-known-chains/-/connect-known-chains-1.10.3.tgz#71a89864f13626c412fa0a9d0ffc4f6ca39fdcec" + integrity sha512-OJEZO1Pagtb6bNE3wCikc2wrmvEU5x7GxFFLqqbz1AJYYxSlrPCGu4N2og5YTExo4IcloNMQYFRkBGue0BKZ4w== + +"@substrate/connect@0.8.10": + version "0.8.10" + resolved "https://registry.yarnpkg.com/@substrate/connect/-/connect-0.8.10.tgz#810b6589f848828aa840c731a1f36b84fe0e5956" + integrity sha512-DIyQ13DDlXqVFnLV+S6/JDgiGowVRRrh18kahieJxhgvzcWicw5eLc6jpfQ0moVVLBYkO7rctB5Wreldwpva8w== + dependencies: + "@substrate/connect-extension-protocol" "^2.0.0" + "@substrate/connect-known-chains" "^1.1.4" + "@substrate/light-client-extension-helpers" "^0.0.6" + smoldot "2.0.22" + +"@substrate/light-client-extension-helpers@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@substrate/light-client-extension-helpers/-/light-client-extension-helpers-0.0.6.tgz#bec1c7997241226db50b44ad85a992b4348d21c3" + integrity sha512-girltEuxQ1BvkJWmc8JJlk4ZxnlGXc/wkLcNguhY+UoDEMBK0LsdtfzQKIfrIehi4QdeSBlFEFBoI4RqPmsZzA== + dependencies: + "@polkadot-api/json-rpc-provider" "0.0.1" + "@polkadot-api/json-rpc-provider-proxy" "0.0.1" + "@polkadot-api/observable-client" "0.1.0" + "@polkadot-api/substrate-client" "0.0.1" + "@substrate/connect-extension-protocol" "^2.0.0" + "@substrate/connect-known-chains" "^1.1.4" + rxjs "^7.8.1" + +"@substrate/ss58-registry@^1.44.0": + version "1.51.0" + resolved "https://registry.yarnpkg.com/@substrate/ss58-registry/-/ss58-registry-1.51.0.tgz#39e0341eb4069c2d3e684b93f0d8cb0bec572383" + integrity sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ== + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@tybys/wasm-util@^0.10.0": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + dependencies: + tslib "^2.4.0" + +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/bn.js@*", "@types/bn.js@^5.1.5": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.2.0.tgz#4349b9710e98f9ab3cdc50f1c5e4dcbd8ef29c80" + integrity sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q== + dependencies: + "@types/node" "*" + +"@types/elliptic@^6.4.18": + version "6.4.18" + resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.18.tgz#bc96e26e1ccccbabe8b6f0e409c85898635482e1" + integrity sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw== + dependencies: + "@types/bn.js" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.1", "@types/istanbul-lib-coverage@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^30.0.0": + version "30.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-30.0.0.tgz#5e85ae568006712e4ad66f25433e9bdac8801f1d" + integrity sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA== + dependencies: + expect "^30.0.0" + pretty-format "^30.0.0" + +"@types/mocha@^10.0.10": + version "10.0.10" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.10.tgz#91f62905e8d23cbd66225312f239454a23bebfa0" + integrity sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q== + +"@types/node@*": + version "24.4.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.4.0.tgz#4ca9168c016a55ab15b7765ad1674ab807489600" + integrity sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ== + dependencies: + undici-types "~7.11.0" + +"@types/node@22.7.5": + version "22.7.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + dependencies: + undici-types "~6.19.2" + +"@types/node@^20.11.5": + version "20.19.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.17.tgz#41b52697373aef8a43b3b92f33b43f329b2d674b" + integrity sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ== + dependencies: + undici-types "~6.21.0" + +"@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.33": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@ungap/structured-clone@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@unrs/resolver-binding-android-arm-eabi@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81" + integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== + +"@unrs/resolver-binding-android-arm64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f" + integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== + +"@unrs/resolver-binding-darwin-arm64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf" + integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== + +"@unrs/resolver-binding-darwin-x64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc" + integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== + +"@unrs/resolver-binding-freebsd-x64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b" + integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== + +"@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a" + integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== + +"@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3" + integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== + +"@unrs/resolver-binding-linux-arm64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d" + integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== + +"@unrs/resolver-binding-linux-arm64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0" + integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== + +"@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44" + integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== + +"@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9" + integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== + +"@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165" + integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== + +"@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94" + integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== + +"@unrs/resolver-binding-linux-x64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935" + integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== + +"@unrs/resolver-binding-linux-x64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6" + integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== + +"@unrs/resolver-binding-wasm32-wasi@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d" + integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ== + dependencies: + "@napi-rs/wasm-runtime" "^0.2.11" + +"@unrs/resolver-binding-win32-arm64-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35" + integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== + +"@unrs/resolver-binding-win32-ia32-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6" + integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== + +"@unrs/resolver-binding-win32-x64-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" + integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== + +"@uphold/request-logger@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@uphold/request-logger/-/request-logger-2.0.0.tgz#c585c0bdb94210198945c6597e4fe23d6e63e084" + integrity sha512-UvGS+v87C7VTtQDcFHDLfvfl1zaZaLSwSmAnV35Ne7CzAVvotmZqt9lAIoNpMpaoRpdjVIcnUDwPSeIeA//EoQ== + dependencies: + uuid "^3.0.1" + +abitype@1.1.0, abitype@^1.0.9: + version "1.1.0" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.1.0.tgz#510c5b3f92901877977af5e864841f443bf55406" + integrity sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + +ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" + integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.3" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + +anymatch@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.13.2" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.2.tgz#0aa167216965ac9474ccfa83892cfb6b3e1e52ef" + integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== + +babel-jest@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-30.1.2.tgz#decd53b3a0cafca49443f93fb7a2c0fba55510da" + integrity sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g== + dependencies: + "@jest/transform" "30.1.2" + "@types/babel__core" "^7.20.5" + babel-plugin-istanbul "^7.0.0" + babel-preset-jest "30.0.1" + chalk "^4.1.2" + graceful-fs "^4.2.11" + slash "^3.0.0" + +babel-plugin-istanbul@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz#d8b518c8ea199364cf84ccc82de89740236daf92" + integrity sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-instrument "^6.0.2" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz#f271b2066d2c1fb26a863adb8e13f85b06247125" + integrity sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.27.3" + "@types/babel__core" "^7.20.5" + +babel-preset-current-node-syntax@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz#7d28db9531bce264e846c8483d54236244b8ae88" + integrity sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw== + dependencies: + babel-plugin-jest-hoist "30.0.1" + babel-preset-current-node-syntax "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base-x@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.1.tgz#817fb7b57143c501f649805cb247617ad016a885" + integrity sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw== + +baseline-browser-mapping@^2.8.3: + version "2.8.6" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz#c37dea4291ed8d01682f85661dbe87967028642e" + integrity sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw== + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + +bech32@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== + +bignumber.js@^9.0.0: + version "9.3.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.1.tgz#759c5aaddf2ffdc4f154f7b493e1c8770f88c4d7" + integrity sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ== + +bip174@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f" + integrity sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ== + +bitcoin-core@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bitcoin-core/-/bitcoin-core-5.0.0.tgz#6ae38e457814f55ee37c5fa6a5b33c48d9ca8f2d" + integrity sha512-XqHsD5LjtshN8yWzRrq2kof57e1eXCGDx3i5+sFKBRi9MktSlXOR4SRLyXLkfzfBmPEs5q/76RotQJuaWg75DQ== + dependencies: + "@uphold/request-logger" "^2.0.0" + debugnyan "^1.0.0" + json-bigint "^1.0.0" + lodash "^4.0.0" + request "^2.53.0" + semver "^5.1.0" + standard-error "^1.1.0" + +bitcoin-regtest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bitcoin-regtest/-/bitcoin-regtest-1.0.1.tgz#953591e77cf6db16a459e5565f73350337c8818e" + integrity sha512-BYQuendqQAdBl1jRZ4qytTdCqAoKrQcMHmlLcY/K74BBzrNIJNqlhWbY70zimHnIBFI1vUOV5aMoSm6JQFq6pA== + dependencies: + bitcoin-core "^5.0.0" + +bitcoinjs-lib@^6.1.6: + version "6.1.7" + resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.7.tgz#0f98dec1333d658574eefa455295668cfae38bb0" + integrity sha512-tlf/r2DGMbF7ky1MgUqXHzypYHakkEnm0SZP23CJKIqNY/5uNAnMbFhMJdhjrL/7anfb/U8+AlpdjPWjPnAalg== + dependencies: + "@noble/hashes" "^1.2.0" + bech32 "^2.0.0" + bip174 "^2.1.1" + bs58check "^3.0.1" + typeforce "^1.11.3" + varuint-bitcoin "^1.1.2" + +bn.js@^4.11.9: + version "4.12.2" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.2.tgz#3d8fed6796c24e177737f7cc5172ee04ef39ec99" + integrity sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw== + +bn.js@^5.2.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.2.tgz#82c09f9ebbb17107cd72cb7fd39bd1f9d0aaa566" + integrity sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw== + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browserslist@^4.24.0: + version "4.26.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.2.tgz#7db3b3577ec97f1140a52db4936654911078cef3" + integrity sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A== + dependencies: + baseline-browser-mapping "^2.8.3" + caniuse-lite "^1.0.30001741" + electron-to-chromium "^1.5.218" + node-releases "^2.0.21" + update-browserslist-db "^1.1.3" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== + dependencies: + base-x "^4.0.0" + +bs58check@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-3.0.1.tgz#2094d13720a28593de1cba1d8c4e48602fdd841c" + integrity sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ== + dependencies: + "@noble/hashes" "^1.2.0" + bs58 "^5.0.0" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +bunyan@^1.8.1: + version "1.8.15" + resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.15.tgz#8ce34ca908a17d0776576ca1b2f6cbd916e93b46" + integrity sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig== + optionalDependencies: + dtrace-provider "~0.8" + moment "^2.19.3" + mv "~2" + safe-json-stringify "~1" + +callsites@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001741: + version "1.0.30001743" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz#50ff91a991220a1ee2df5af00650dd5c308ea7cd" + integrity sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.0.tgz#c39b1013f8fdbd28cd78e62318357d02da160cd7" + integrity sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ== + +cjs-module-lexer@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz#586e87d4341cb2661850ece5190232ccdebcff8b" + integrity sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +coinselect@^3.1.13: + version "3.1.13" + resolved "https://registry.yarnpkg.com/coinselect/-/coinselect-3.1.13.tgz#b88c7f9659ed4891d1f1d0c894105b1c10ef89a1" + integrity sha512-iJOrKH/7N9gX0jRkxgOHuGjvzvoxUMSeylDhH1sHn+CjLjdin5R0Hz2WEBu/jrZV5OrHcm+6DMzxwu9zb5mSZg== + +collect-v8-coverage@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.3, cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + +debug@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +debugnyan@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/debugnyan/-/debugnyan-1.0.0.tgz#90386d5ebc2c63588f17f272be5c2a93b7665d83" + integrity sha512-dTvKxcLZCammDLFYi31NRVr5g6vjJ33uf1wcdbIPPxPxxnJ9/xj00Mh/YQkhFMw/VGavaG5KpjSC+4o9r/JjRg== + dependencies: + bunyan "^1.8.1" + debug "^2.2.0" + +dedent@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" + integrity sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ== + +deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +detect-newline@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dtrace-provider@~0.8: + version "0.8.8" + resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" + integrity sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg== + dependencies: + nan "^2.14.0" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +electron-to-chromium@^1.5.218: + version "1.5.222" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz#965c93783ad989116b74593ae3068b9466fdb237" + integrity sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w== + +elliptic@^6.6.1: + version "6.6.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" + integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +ethers@^6.15.0: + version "6.15.0" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.15.0.tgz#2980f2a3baf0509749b7e21f8692fa8a8349c0e3" + integrity sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "22.7.5" + aes-js "4.0.0-beta.5" + tslib "2.7.0" + ws "8.17.1" + +eventemitter3@5.0.1, eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + +execa@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit-x@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/exit-x/-/exit-x-0.2.2.tgz#1f9052de3b8d99a696b10dad5bced9bdd5c3aa64" + integrity sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== + +expect@30.1.2, expect@^30.0.0: + version "30.1.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.1.2.tgz#094909c2443f76b9e208fafac4a315aaaf924580" + integrity sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg== + dependencies: + "@jest/expect-utils" "30.1.2" + "@jest/get-type" "30.1.0" + jest-matcher-utils "30.1.2" + jest-message-util "30.1.0" + jest-mock "30.0.5" + jest-util "30.0.5" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fb-watchman@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +foreground-child@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + +glob@^10.3.10: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^6.0.1: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + integrity sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A== + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.2.11: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +import-local@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isows@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.7.tgz#1c06400b7eed216fbba3bcbd68f12490fc342915" + integrity sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg== + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^5.0.0: + version "5.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441" + integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A== + dependencies: + "@jridgewell/trace-mapping" "^0.3.23" + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + +istanbul-reports@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jest-changed-files@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-30.0.5.tgz#ec448f83bd9caa894dd7da8707f207c356a19924" + integrity sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A== + dependencies: + execa "^5.1.1" + jest-util "30.0.5" + p-limit "^3.1.0" + +jest-circus@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-30.1.3.tgz#7ee0089f22b2b3e72ab04aee8e037c364a6d73d1" + integrity sha512-Yf3dnhRON2GJT4RYzM89t/EXIWNxKTpWTL9BfF3+geFetWP4XSvJjiU1vrWplOiUkmq8cHLiwuhz+XuUp9DscA== + dependencies: + "@jest/environment" "30.1.2" + "@jest/expect" "30.1.2" + "@jest/test-result" "30.1.3" + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + co "^4.6.0" + dedent "^1.6.0" + is-generator-fn "^2.1.0" + jest-each "30.1.0" + jest-matcher-utils "30.1.2" + jest-message-util "30.1.0" + jest-runtime "30.1.3" + jest-snapshot "30.1.2" + jest-util "30.0.5" + p-limit "^3.1.0" + pretty-format "30.0.5" + pure-rand "^7.0.0" + slash "^3.0.0" + stack-utils "^2.0.6" + +jest-cli@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-30.1.3.tgz#3fb8dea88886379eb95a08f954bfc2ed17a9be4f" + integrity sha512-G8E2Ol3OKch1DEeIBl41NP7OiC6LBhfg25Btv+idcusmoUSpqUkbrneMqbW9lVpI/rCKb/uETidb7DNteheuAQ== + dependencies: + "@jest/core" "30.1.3" + "@jest/test-result" "30.1.3" + "@jest/types" "30.0.5" + chalk "^4.1.2" + exit-x "^0.2.2" + import-local "^3.2.0" + jest-config "30.1.3" + jest-util "30.0.5" + jest-validate "30.1.0" + yargs "^17.7.2" + +jest-config@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-30.1.3.tgz#10bcf4cd979119bfac6a130fb79d837057ce33d4" + integrity sha512-M/f7gqdQEPgZNA181Myz+GXCe8jXcJsGjCMXUzRj22FIXsZOyHNte84e0exntOvdPaeh9tA0w+B8qlP2fAezfw== + dependencies: + "@babel/core" "^7.27.4" + "@jest/get-type" "30.1.0" + "@jest/pattern" "30.0.1" + "@jest/test-sequencer" "30.1.3" + "@jest/types" "30.0.5" + babel-jest "30.1.2" + chalk "^4.1.2" + ci-info "^4.2.0" + deepmerge "^4.3.1" + glob "^10.3.10" + graceful-fs "^4.2.11" + jest-circus "30.1.3" + jest-docblock "30.0.1" + jest-environment-node "30.1.2" + jest-regex-util "30.0.1" + jest-resolve "30.1.3" + jest-runner "30.1.3" + jest-util "30.0.5" + jest-validate "30.1.0" + micromatch "^4.0.8" + parse-json "^5.2.0" + pretty-format "30.0.5" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.1.2.tgz#8ff4217e5b63fef49a5b37462999d8f5299a4eb4" + integrity sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ== + dependencies: + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + pretty-format "30.0.5" + +jest-docblock@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-30.0.1.tgz#545ff59f2fa88996bd470dba7d3798a8421180b1" + integrity sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA== + dependencies: + detect-newline "^3.1.0" + +jest-each@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-30.1.0.tgz#228756d5ea9e4dcb462fc2e90a44ec27dd482d23" + integrity sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ== + dependencies: + "@jest/get-type" "30.1.0" + "@jest/types" "30.0.5" + chalk "^4.1.2" + jest-util "30.0.5" + pretty-format "30.0.5" + +jest-environment-node@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.1.2.tgz#ae2f20442f8abc3c6b20120dc789fa38faff568f" + integrity sha512-w8qBiXtqGWJ9xpJIA98M0EIoq079GOQRQUyse5qg1plShUCQ0Ek1VTTcczqKrn3f24TFAgFtT+4q3aOXvjbsuA== + dependencies: + "@jest/environment" "30.1.2" + "@jest/fake-timers" "30.1.2" + "@jest/types" "30.0.5" + "@types/node" "*" + jest-mock "30.0.5" + jest-util "30.0.5" + jest-validate "30.1.0" + +jest-haste-map@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.1.0.tgz#e54d84e07fac15ea3a98903b735048e36d7d2ed3" + integrity sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + anymatch "^3.1.3" + fb-watchman "^2.0.2" + graceful-fs "^4.2.11" + jest-regex-util "30.0.1" + jest-util "30.0.5" + jest-worker "30.1.0" + micromatch "^4.0.8" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.3" + +jest-leak-detector@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-30.1.0.tgz#8b86e7c5f1e3e4f2a32d930ec769103ad0985874" + integrity sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g== + dependencies: + "@jest/get-type" "30.1.0" + pretty-format "30.0.5" + +jest-matcher-utils@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.1.2.tgz#3f1b63949f740025aff740c6c6a1b653ae370fbb" + integrity sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ== + dependencies: + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + jest-diff "30.1.2" + pretty-format "30.0.5" + +jest-message-util@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.1.0.tgz#653a9bb1a33306eddf13455ce0666ba621b767c4" + integrity sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.0.5" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.0.5" + slash "^3.0.0" + stack-utils "^2.0.6" + +jest-mock@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.0.5.tgz#ef437e89212560dd395198115550085038570bdd" + integrity sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + jest-util "30.0.5" + +jest-pnp-resolver@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" + integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== + +jest-resolve-dependencies@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.1.3.tgz#04bbe95c9f4af51046dde940698d7121b49d0167" + integrity sha512-DNfq3WGmuRyHRHfEet+Zm3QOmVFtIarUOQHHryKPc0YL9ROfgWZxl4+aZq/VAzok2SS3gZdniP+dO4zgo59hBg== + dependencies: + jest-regex-util "30.0.1" + jest-snapshot "30.1.2" + +jest-resolve@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-30.1.3.tgz#cc1019b28374ca7bcf7e58d57a4300449f390ec5" + integrity sha512-DI4PtTqzw9GwELFS41sdMK32Ajp3XZQ8iygeDMWkxlRhm7uUTOFSZFVZABFuxr0jvspn8MAYy54NxZCsuCTSOw== + dependencies: + chalk "^4.1.2" + graceful-fs "^4.2.11" + jest-haste-map "30.1.0" + jest-pnp-resolver "^1.2.3" + jest-util "30.0.5" + jest-validate "30.1.0" + slash "^3.0.0" + unrs-resolver "^1.7.11" + +jest-runner@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-30.1.3.tgz#3253a0faab8f404aa9e0010911e8acbaf220865b" + integrity sha512-dd1ORcxQraW44Uz029TtXj85W11yvLpDuIzNOlofrC8GN+SgDlgY4BvyxJiVeuabA1t6idjNbX59jLd2oplOGQ== + dependencies: + "@jest/console" "30.1.2" + "@jest/environment" "30.1.2" + "@jest/test-result" "30.1.3" + "@jest/transform" "30.1.2" + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + emittery "^0.13.1" + exit-x "^0.2.2" + graceful-fs "^4.2.11" + jest-docblock "30.0.1" + jest-environment-node "30.1.2" + jest-haste-map "30.1.0" + jest-leak-detector "30.1.0" + jest-message-util "30.1.0" + jest-resolve "30.1.3" + jest-runtime "30.1.3" + jest-util "30.0.5" + jest-watcher "30.1.3" + jest-worker "30.1.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-30.1.3.tgz#bca7cb48d53c5b5ae21399e7a65e21271f500004" + integrity sha512-WS8xgjuNSphdIGnleQcJ3AKE4tBKOVP+tKhCD0u+Tb2sBmsU8DxfbBpZX7//+XOz81zVs4eFpJQwBNji2Y07DA== + dependencies: + "@jest/environment" "30.1.2" + "@jest/fake-timers" "30.1.2" + "@jest/globals" "30.1.2" + "@jest/source-map" "30.0.1" + "@jest/test-result" "30.1.3" + "@jest/transform" "30.1.2" + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + cjs-module-lexer "^2.1.0" + collect-v8-coverage "^1.0.2" + glob "^10.3.10" + graceful-fs "^4.2.11" + jest-haste-map "30.1.0" + jest-message-util "30.1.0" + jest-mock "30.0.5" + jest-regex-util "30.0.1" + jest-resolve "30.1.3" + jest-snapshot "30.1.2" + jest-util "30.0.5" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@30.1.2: + version "30.1.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.1.2.tgz#4001a94d8394bb077a1c96246f0107c81aba4f12" + integrity sha512-4q4+6+1c8B6Cy5pGgFvjDy/Pa6VYRiGu0yQafKkJ9u6wQx4G5PqI2QR6nxTl43yy7IWsINwz6oT4o6tD12a8Dg== + dependencies: + "@babel/core" "^7.27.4" + "@babel/generator" "^7.27.5" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.27.1" + "@babel/types" "^7.27.3" + "@jest/expect-utils" "30.1.2" + "@jest/get-type" "30.1.0" + "@jest/snapshot-utils" "30.1.2" + "@jest/transform" "30.1.2" + "@jest/types" "30.0.5" + babel-preset-current-node-syntax "^1.1.0" + chalk "^4.1.2" + expect "30.1.2" + graceful-fs "^4.2.11" + jest-diff "30.1.2" + jest-matcher-utils "30.1.2" + jest-message-util "30.1.0" + jest-util "30.0.5" + pretty-format "30.0.5" + semver "^7.7.2" + synckit "^0.11.8" + +jest-util@30.0.5: + version "30.0.5" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.0.5.tgz#035d380c660ad5f1748dff71c4105338e05f8669" + integrity sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g== + dependencies: + "@jest/types" "30.0.5" + "@types/node" "*" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" + +jest-validate@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.1.0.tgz#585aae6c9ee1ac138dbacbece8a7838ca7773e60" + integrity sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA== + dependencies: + "@jest/get-type" "30.1.0" + "@jest/types" "30.0.5" + camelcase "^6.3.0" + chalk "^4.1.2" + leven "^3.1.0" + pretty-format "30.0.5" + +jest-watcher@30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-30.1.3.tgz#2f381da5c2c76a46c46ba2108e6607c585421dc0" + integrity sha512-6jQUZCP1BTL2gvG9E4YF06Ytq4yMb4If6YoQGRR6PpjtqOXSP3sKe2kqwB6SQ+H9DezOfZaSLnmka1NtGm3fCQ== + dependencies: + "@jest/test-result" "30.1.3" + "@jest/types" "30.0.5" + "@types/node" "*" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + emittery "^0.13.1" + jest-util "30.0.5" + string-length "^4.0.2" + +jest-worker@30.1.0: + version "30.1.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.1.0.tgz#a89c36772be449d4bdb60697fb695a1673b12ac2" + integrity sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA== + dependencies: + "@types/node" "*" + "@ungap/structured-clone" "^1.3.0" + jest-util "30.0.5" + merge-stream "^2.0.0" + supports-color "^8.1.1" + +jest@^30.1.3: + version "30.1.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-30.1.3.tgz#c962290f65c32d44a0624f785b2d780835525a23" + integrity sha512-Ry+p2+NLk6u8Agh5yVqELfUJvRfV51hhVBRIB5yZPY7mU0DGBmOuFG5GebZbMbm86cdQNK0fhJuDX8/1YorISQ== + dependencies: + "@jest/core" "30.1.3" + "@jest/types" "30.0.5" + import-local "^3.2.0" + jest-cli "30.1.3" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash@^4.0.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +"minimatch@2 || 3", minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +mkdirp@~0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mock-socket@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.3.1.tgz#24fb00c2f573c84812aa4a24181bb025de80cc8e" + integrity sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw== + +moment@^2.19.3: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mv@~2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" + integrity sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg== + dependencies: + mkdirp "~0.5.1" + ncp "~2.0.0" + rimraf "~2.4.0" + +nan@^2.14.0: + version "2.23.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.23.1.tgz#6f86a31dd87e3d1eb77512bf4b9e14c8aded3975" + integrity sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw== + +napi-postinstall@^0.3.0: + version "0.3.3" + resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.3.tgz#93d045c6b576803ead126711d3093995198c6eb9" + integrity sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +ncp@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" + integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nock@^13.5.0: + version "13.5.6" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.6.tgz#5e693ec2300bbf603b61dae6df0225673e6c4997" + integrity sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + propagate "^2.0.0" + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.21: + version "2.0.21" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.21.tgz#f59b018bc0048044be2d4c4c04e4c8b18160894c" + integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +ox@0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.9.3.tgz#92cc1008dcd913e919364fd4175c860b3eeb18db" + integrity sha512-KzyJP+fPV4uhuuqrTZyok4DC7vFzi7HLUFiUNEmpbyh59htKWkOC98IONC1zgXJPbHAhQgqs6B0Z6StCGhmQvg== + dependencies: + "@adraffy/ens-normalize" "^1.11.0" + "@noble/ciphers" "^1.3.0" + "@noble/curves" "1.9.1" + "@noble/hashes" "^1.8.0" + "@scure/bip32" "^1.7.0" + "@scure/bip39" "^1.6.0" + abitype "^1.0.9" + eventemitter3 "5.0.1" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +pirates@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pretty-format@30.0.5, pretty-format@^30.0.0: + version "30.0.5" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.0.5.tgz#e001649d472800396c1209684483e18a4d250360" + integrity sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw== + dependencies: + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + +psl@^1.1.28: + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" + +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-7.0.1.tgz#6f53a5a9e3e4a47445822af96821ca509ed37566" + integrity sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ== + +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + +react-is@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +request@^2.53.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +rimraf@~2.4.0: + version "2.4.5" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da" + integrity sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ== + dependencies: + glob "^6.0.1" + +rxjs@^7.8.1: + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + +safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-json-stringify@~1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" + integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +scale-ts@^1.6.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/scale-ts/-/scale-ts-1.6.1.tgz#45151e156d6c04792223c39d8e7484ce926445f2" + integrity sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g== + +semver@^5.1.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +smoldot@2.0.22: + version "2.0.22" + resolved "https://registry.yarnpkg.com/smoldot/-/smoldot-2.0.22.tgz#1e924d2011a31c57416e79a2b97a460f462a31c7" + integrity sha512-B50vRgTY6v3baYH6uCgL15tfaag5tcS2o/P5q1OiXcKGv1axZDfz2dzzMuIkVpyMR2ug11F6EAtQlmYBQd292g== + dependencies: + ws "^8.8.1" + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +sshpk@^1.7.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +standard-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/standard-error/-/standard-error-1.1.0.tgz#23e5168fa1c0820189e5812701a79058510d0d34" + integrity sha512-4v7qzU7oLJfMI5EltUSHCaaOd65J6S4BqKRWgzMi4EYaE5fvNabPxmAPGdxpGXqrcWjhDGI/H09CIdEuUOUeXg== + +string-length@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" + integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +synckit@^0.11.8: + version "0.11.11" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.11.tgz#c0b619cf258a97faa209155d9cd1699b5c998cb0" + integrity sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw== + dependencies: + "@pkgr/core" "^0.2.9" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +tiny-secp256k1@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-2.2.4.tgz#1d9e45c2facb8607847da71a0a3d9cb2fd027eb2" + integrity sha512-FoDTcToPqZE454Q04hH9o2EhxWsm7pOSpicyHkgTwKhdKWdsTUuqfP5MLq3g+VjAtl2vSx6JpXGdwA2qpYkI0Q== + dependencies: + uint8array-tools "0.0.7" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +ts-jest@^29.4.4: + version "29.4.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.4.tgz#fc6fefe28652ed81b8e1381ef8391901d9f81417" + integrity sha512-ccVcRABct5ZELCT5U0+DZwkXMCcOCLi2doHRrKy1nK/s7J7bch6TzJMsrY09WxgUUIP/ITfmcDS8D2yl63rnXw== + dependencies: + bs-logger "^0.2.6" + fast-json-stable-stringify "^2.1.0" + handlebars "^4.7.8" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.2" + type-fest "^4.41.0" + yargs-parser "^21.1.1" + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + +tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2, tslib@^2.7.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +typeforce@^1.11.3: + version "1.18.0" + resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" + integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== + +typescript@^5.3.3: + version "5.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" + integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== + +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +uint8array-tools@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/uint8array-tools/-/uint8array-tools-0.0.7.tgz#a7a2bb5d8836eae2fade68c771454e6a438b390d" + integrity sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ== + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici-types@~7.11.0: + version "7.11.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.11.0.tgz#075798115d0bbc4e4fc7c173f38727ca66bfb592" + integrity sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA== + +unrs-resolver@^1.7.11: + version "1.11.1" + resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" + integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== + dependencies: + napi-postinstall "^0.3.0" + optionalDependencies: + "@unrs/resolver-binding-android-arm-eabi" "1.11.1" + "@unrs/resolver-binding-android-arm64" "1.11.1" + "@unrs/resolver-binding-darwin-arm64" "1.11.1" + "@unrs/resolver-binding-darwin-x64" "1.11.1" + "@unrs/resolver-binding-freebsd-x64" "1.11.1" + "@unrs/resolver-binding-linux-arm-gnueabihf" "1.11.1" + "@unrs/resolver-binding-linux-arm-musleabihf" "1.11.1" + "@unrs/resolver-binding-linux-arm64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-arm64-musl" "1.11.1" + "@unrs/resolver-binding-linux-ppc64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-riscv64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-riscv64-musl" "1.11.1" + "@unrs/resolver-binding-linux-s390x-gnu" "1.11.1" + "@unrs/resolver-binding-linux-x64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-x64-musl" "1.11.1" + "@unrs/resolver-binding-wasm32-wasi" "1.11.1" + "@unrs/resolver-binding-win32-arm64-msvc" "1.11.1" + "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" + "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +uuid@^3.0.1, uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +varuint-bitcoin@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz#e76c138249d06138b480d4c5b40ef53693e24e92" + integrity sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw== + dependencies: + safe-buffer "^5.1.1" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +viem@^2.37.6: + version "2.37.6" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.37.6.tgz#3b05586555bd4b2c1b7351ed148f9fa98df72027" + integrity sha512-b+1IozQ8TciVQNdQUkOH5xtFR0z7ZxR8pyloENi/a+RA408lv4LoX12ofwoiT3ip0VRhO5ni1em//X0jn/eW0g== + dependencies: + "@noble/curves" "1.9.1" + "@noble/hashes" "1.8.0" + "@scure/bip32" "1.7.0" + "@scure/bip39" "1.6.0" + abitype "1.1.0" + isows "1.0.7" + ox "0.9.3" + ws "8.18.3" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + +ws@8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + +ws@8.18.3, ws@^8.15.1, ws@^8.8.1: + version "8.18.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" + integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==