diff --git a/Cargo.lock b/Cargo.lock index eeac8d4..2d638e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,12 +99,6 @@ dependencies = [ "syn", ] -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" version = "1.5.0" @@ -133,12 +127,6 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bech32" version = "0.11.1" @@ -172,8 +160,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ "bitcoin_hashes", - "rand 0.8.5", - "rand_core 0.6.4", + "rand", + "rand_core", "serde", "unicode-normalization", ] @@ -328,12 +316,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - [[package]] name = "cc" version = "1.2.56" @@ -425,15 +407,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -476,19 +449,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.31.1", + "nix 0.31.2", "windows-sys 0.61.2", ] -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - [[package]] name = "digest" version = "0.10.7" @@ -511,17 +475,6 @@ dependencies = [ "objc2", ] -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "dotenvy" version = "0.15.7" @@ -627,48 +580,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "slab", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -694,23 +605,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", @@ -793,16 +690,6 @@ dependencies = [ "arrayvec", ] -[[package]] -name = "hex-simd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7685beb53fc20efc2605f32f5d51e9ba18b8ef237961d1760169d2290d3bee" -dependencies = [ - "outref", - "vsimd", -] - [[package]] name = "hex_lit" version = "0.1.1" @@ -818,214 +705,12 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls 0.23.37", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots 1.0.6", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - [[package]] name = "id-arena" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - [[package]] name = "ignore" version = "0.4.25" @@ -1054,22 +739,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1093,9 +762,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.90" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -1127,9 +796,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "linux-raw-sys" @@ -1139,15 +808,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.1" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" @@ -1155,12 +818,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "matchers" version = "0.2.0" @@ -1201,22 +858,11 @@ version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05015102dad0f7d61691ca347e9d9d9006685a64aefb3d79eecf62665de2153d" dependencies = [ - "rustls 0.21.12", - "rustls-webpki 0.101.7", + "rustls", + "rustls-webpki", "serde", "serde_json", - "webpki-roots 0.25.4", -] - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", + "webpki-roots", ] [[package]] @@ -1235,9 +881,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ "bitflags 2.11.0", "cfg-if", @@ -1254,12 +900,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num-conv" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" - [[package]] name = "objc2" version = "0.6.4" @@ -1296,24 +936,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "outref" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" - [[package]] name = "pathdiff" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1326,21 +954,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1379,75 +992,20 @@ dependencies = [ "cc", ] -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls 0.23.37", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls 0.23.37", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.3.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" @@ -1456,18 +1014,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", + "rand_chacha", + "rand_core", ] [[package]] @@ -1477,17 +1025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", + "rand_core", ] [[package]] @@ -1499,15 +1037,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - [[package]] name = "regex" version = "1.12.3" @@ -1555,48 +1084,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] -name = "reqwest" -version = "0.12.28" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls 0.23.37", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 1.0.6", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", @@ -1606,12 +1097,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustix" version = "0.38.44" @@ -1627,14 +1112,14 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -1646,34 +1131,10 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", - "rustls-webpki 0.101.7", + "rustls-webpki", "sct", ] -[[package]] -name = "rustls" -version = "0.23.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki 0.103.9", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" -dependencies = [ - "web-time", - "zeroize", -] - [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1684,29 +1145,12 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustls-webpki" -version = "0.103.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - [[package]] name = "same-file" version = "1.0.6" @@ -1742,7 +1186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes", - "rand 0.8.5", + "rand", "secp256k1-sys", "serde", ] @@ -1763,7 +1207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52a44aed3002b5ae975f8624c5df3a949cfbf00479e18778b6058fcd213b76e3" dependencies = [ "bitcoin-private", - "rand 0.8.5", + "rand", "secp256k1", "secp256k1-zkp-sys", "serde", @@ -1837,18 +1281,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha2" version = "0.10.9" @@ -1884,7 +1316,6 @@ dependencies = [ "either", "serde", "simplex-macros", - "simplex-provider", "simplex-sdk", "simplex-test", "simplicityhl", @@ -1901,9 +1332,11 @@ dependencies = [ "ctrlc", "dotenvy", "electrsd", + "glob", "globwalk", "serde", "simplex-macros-core", + "simplex-regtest", "simplex-sdk", "simplex-template-gen-core", "simplex-test", @@ -1912,7 +1345,6 @@ dependencies = [ "tokio", "toml 0.9.12+spec-1.1.0", "tracing", - "tracing-appender", "tracing-subscriber", ] @@ -1938,19 +1370,15 @@ dependencies = [ ] [[package]] -name = "simplex-provider" +name = "simplex-regtest" version = "0.1.0" dependencies = [ - "async-trait", - "bitcoin_hashes", "electrsd", - "hex-simd", - "minreq", - "reqwest", "serde", - "serde_json", + "simplex-sdk", "simplicityhl", "thiserror", + "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -1959,13 +1387,15 @@ version = "0.1.0" dependencies = [ "async-trait", "bip39", + "bitcoin_hashes", "dyn-clone", + "electrsd", "elements-miniscript", "hex", "minreq", "serde", + "serde_json", "sha2", - "simplex-provider", "simplicityhl", "thiserror", ] @@ -1990,7 +1420,6 @@ version = "0.1.0" dependencies = [ "electrsd", "serde", - "simplex-provider", "simplex-sdk", "simplicityhl", "thiserror", @@ -2041,34 +1470,12 @@ dependencies = [ "simplicity-lang", ] -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - [[package]] name = "stacker" version = "0.1.23" @@ -2088,12 +1495,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "syn" version = "2.0.117" @@ -2105,26 +1506,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "target-triple" version = "1.0.0" @@ -2133,14 +1514,14 @@ checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -2182,47 +1563,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinyvec" version = "1.10.0" @@ -2240,40 +1580,25 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ - "bytes", - "libc", - "mio", "pin-project-lite", - "socket2", "tokio-macros", - "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls 0.23.37", - "tokio", -] - [[package]] name = "toml" version = "0.9.12+spec-1.1.0" @@ -2291,9 +1616,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.3+spec-1.1.0" +version = "1.0.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" +checksum = "c94c3321114413476740df133f0d8862c61d87c8d26f04c6841e033c8c80db47" dependencies = [ "indexmap", "serde_core", @@ -2337,51 +1662,6 @@ version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags 2.11.0", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - [[package]] name = "tracing" version = "0.1.44" @@ -2393,18 +1673,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-appender" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" -dependencies = [ - "crossbeam-channel", - "thiserror", - "time", - "tracing-subscriber", -] - [[package]] name = "tracing-attributes" version = "0.1.31" @@ -2455,12 +1723,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "trybuild" version = "1.0.116" @@ -2473,7 +1735,7 @@ dependencies = [ "serde_json", "target-triple", "termcolor", - "toml 1.0.3+spec-1.1.0", + "toml 1.0.4+spec-1.1.0", ] [[package]] @@ -2521,24 +1783,6 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -2563,12 +1807,6 @@ version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" -[[package]] -name = "vsimd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" - [[package]] name = "walkdir" version = "2.5.0" @@ -2579,15 +1817,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2614,9 +1843,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -2625,25 +1854,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a89f4650b770e4521aa6573724e2aed4704372151bd0de9d16a3bbabb87441a" -dependencies = [ - "cfg-if", - "futures-util", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2651,9 +1866,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -2664,9 +1879,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.113" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -2705,41 +1920,12 @@ dependencies = [ "semver", ] -[[package]] -name = "web-sys" -version = "0.3.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705eceb4ce901230f8625bd1d665128056ccbe4b7408faa625eec1ba80f59a97" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webpki-roots" version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" -[[package]] -name = "webpki-roots" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "which" version = "4.4.2" @@ -2773,7 +1959,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2782,16 +1968,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -2809,31 +1986,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -2842,96 +2002,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.7.14" @@ -3026,35 +2138,6 @@ dependencies = [ "wasmparser", ] -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "zerocopy" version = "0.8.40" @@ -3075,66 +2158,6 @@ dependencies = [ "syn", ] -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index 2d1a44e..bfb3f0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,11 @@ edition = "2024" multiple_crate_versions = "allow" [workspace.dependencies] -simplex-provider = { path = "./crates/provider" } simplex-macros-core = { path = "./crates/macros-core", features = ["bincode", "serde"] } -simplex-template-gen-core = { path = "./crates/template-gen" } simplex-macros = { path = "./crates/macros" } +simplex-template-gen-core = { path = "./crates/template-gen" } simplex-test = { path = "./crates/test" } +simplex-regtest = { path = "./crates/regtest" } simplex-sdk = { path = "./crates/sdk" } simplex = { path = "./crates/simplex" } diff --git a/assets/electrs b/assets/electrs new file mode 100755 index 0000000..4dd5a9d Binary files /dev/null and b/assets/electrs differ diff --git a/assets/elementsd b/assets/elementsd index 30a7b82..93c13ca 100755 Binary files a/assets/elementsd and b/assets/elementsd differ diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 0d00017..79ba7de 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -15,6 +15,7 @@ path = "src/bin/main.rs" workspace = true [dependencies] +simplex-regtest = { workspace = true } simplex-test = { workspace = true } simplex-sdk = { workspace = true } simplex-macros-core = { workspace = true } @@ -33,9 +34,5 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros"] } tracing = { version = "0.1.44" } tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } ctrlc = { version = "3.5.2", features = ["termination"] } +glob = { version = "0.3.3"} globwalk = { version = "0.9.1"} - -[dev-dependencies] -tracing = { workspace = true } -tracing-appender = { version = "0.2.3" } -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } diff --git a/crates/cli/Simplex.default.toml b/crates/cli/Simplex.default.toml new file mode 100644 index 0000000..0b7fa1b --- /dev/null +++ b/crates/cli/Simplex.default.toml @@ -0,0 +1,9 @@ +# TEST CONFIG + +# [test] +# esplora = "esplora_api_url" + +# [test.rpc] +# url = "rpc_url" +# username = "username" +# password = "password" diff --git a/crates/cli/Simplex.example.toml b/crates/cli/Simplex.example.toml deleted file mode 100644 index fd8148f..0000000 --- a/crates/cli/Simplex.example.toml +++ /dev/null @@ -1 +0,0 @@ -network = "liquidtestnet" \ No newline at end of file diff --git a/crates/cli/src/bin/main.rs b/crates/cli/src/bin/main.rs index 78d30c5..c23114a 100644 --- a/crates/cli/src/bin/main.rs +++ b/crates/cli/src/bin/main.rs @@ -1,13 +1,9 @@ -#![warn(clippy::all, clippy::pedantic)] - use clap::Parser; #[tokio::main] async fn main() -> anyhow::Result<()> { let _ = dotenvy::dotenv(); - simplex_cli::logging::init(); - Box::pin(simplex_cli::cli::Cli::parse().run()).await?; Ok(()) diff --git a/crates/cli/src/cache_storage.rs b/crates/cli/src/cache_storage.rs deleted file mode 100644 index 5fff7e9..0000000 --- a/crates/cli/src/cache_storage.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::error::Error; -use simplex_test::ElementsDConf; -use std::fs::OpenOptions; -use std::io::Write; -use std::path::{Path, PathBuf}; - -pub struct CacheStorage {} - -impl CacheStorage { - pub fn save_cached_test_config(test_config: &ElementsDConf) -> Result { - let cache_dir = Self::get_cache_dir()?; - std::fs::create_dir_all(&cache_dir)?; - let test_config_cache_name = Self::create_test_cache_name(&cache_dir); - - let mut file = OpenOptions::new() - .create(true) - .write(true) - .open(&test_config_cache_name)?; - file.write(toml::to_string_pretty(&test_config).unwrap().as_bytes())?; - file.flush()?; - Ok(test_config_cache_name) - } - - pub fn get_cache_dir() -> Result { - const TARGET_DIR_NAME: &str = "target"; - const SIMPLEX_CACHE_DIR_NAME: &str = "simplex"; - - let cwd = std::env::current_dir()?; - Ok(cwd.join(TARGET_DIR_NAME).join(SIMPLEX_CACHE_DIR_NAME)) - } - - pub fn create_test_cache_name(path: impl AsRef) -> PathBuf { - const TEST_CACHE_NAME: &str = "test_config.toml"; - - path.as_ref().join(TEST_CACHE_NAME) - } -} diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs new file mode 100644 index 0000000..8852764 --- /dev/null +++ b/crates/cli/src/cli.rs @@ -0,0 +1,77 @@ +use std::path::PathBuf; + +use clap::Parser; + +use crate::commands::commands::Command; +use crate::commands::regtest::Regtest; +use crate::commands::test::Test; +use crate::config::{Config, INIT_CONFIG}; +use crate::error::CliError; + +#[derive(Debug, Parser)] +#[command(name = "Simplex")] +#[command(about = "Simplicity development framework")] +pub struct Cli { + pub config: Option, + + #[command(subcommand)] + pub command: Command, +} + +impl Cli { + pub async fn run(&self) -> Result<(), CliError> { + match &self.command { + Command::Init => { + let config_path = Config::get_default_path()?; + std::fs::write(&config_path, INIT_CONFIG)?; + + println!("Config written to: '{}'", config_path.display()); + + Ok(()) + } + Command::Config => { + let config_path = Config::get_default_path()?; + let loaded_config = Config::load(config_path)?; + + println!("{loaded_config:#?}"); + + Ok(()) + } + Command::Test { command } => { + let config_path = Config::get_default_path()?; + let loaded_config = Config::load(config_path)?; + + println!("{loaded_config:#?}"); + + let test_config = loaded_config.test.unwrap_or_default(); + + Ok(Test::run(test_config, command)?) + } + Command::Regtest => { + // TODO: pass config + Ok(Regtest::run()?) + } + Command::Build { out_dir: _out_dir } => { + // let loaded_config = + // Config::load_or_discover(self.config.clone()).map_err(|e| Error::ConfigDiscoveryFailure(e))?; + + // if loaded_config.build_config.is_none() { + // return Err(Error::Config( + // "No build config to build contracts environment, please add appropriate config".to_string(), + // )); + // } + + // let build_config = loaded_config.build_config.unwrap(); + // if build_config.compile_simf.is_empty() { + // return Err(Error::Config("No files listed to build contracts environment, please check glob patterns or 'compile_simf' field in config.".to_string())); + // } + + // CodeGenerator::generate_files(&build_config.out_dir, &build_config.compile_simf)?; + + // println!("{build_config:#?}"); + + Ok(()) + } + } + } +} diff --git a/crates/cli/src/cli/commands.rs b/crates/cli/src/cli/commands.rs deleted file mode 100644 index 4a6d8f0..0000000 --- a/crates/cli/src/cli/commands.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::config::ConfigOverride; -use clap::{Args, Subcommand}; -use std::path::PathBuf; - -#[derive(Debug, Subcommand)] -pub enum Command { - /// Initialize a project with the default configuration - Init, - /// Show the current configuration - Config, - /// Launch `elementsd` in regtest mode with a default config - Regtest, - /// Launch test with - Test { - #[command(subcommand)] - command: TestCommand, - }, - Build, -} - -/// Test management commands -#[derive(Debug, Subcommand)] -pub enum TestCommand { - /// Run integration tests using simplex conventions - Integration { - #[command(flatten)] - additional_flags: TestFlags, - }, - /// Run only specific files by path for testing - Run { - #[arg(short = 't', long)] - tests: Vec, - #[command(flatten)] - additional_flags: TestFlags, - }, -} - -/// Additional flags for tests management -#[derive(Debug, Args, Copy, Clone)] -pub struct TestFlags { - /// Flag for not capturing output in tests - #[arg(long)] - pub nocapture: bool, - /// Show output - #[arg(long = "show-output")] - pub show_output: bool, - /// Run ignored tests - #[arg(long = "ignored")] - pub ignored: bool, -} - -/// Build override arguments -#[derive(Debug, Args, Clone)] -pub struct OverrideArgs { - #[command(flatten)] - pub build_args: BuildOverrideArgs, -} - -/// Build override arguments -#[derive(Debug, Args, Clone)] -pub struct BuildOverrideArgs { - /// Output directory for build artifacts - #[arg(global = true, long, env = "OUT_DIR")] - pub out_dir: Option, - /// Flag to generate only files for contracts without module artifacts - #[arg(global = true, long)] - pub only_files: bool, -} - -impl OverrideArgs { - pub fn generate(self) -> Option { - Some(ConfigOverride { - rpc_creds: None, - network: None, - build_conf: if self.build_args.out_dir.is_some() || self.build_args.only_files { - Some(self.build_args) - } else { - None - }, - }) - } -} diff --git a/crates/cli/src/cli/mod.rs b/crates/cli/src/cli/mod.rs deleted file mode 100644 index a03527d..0000000 --- a/crates/cli/src/cli/mod.rs +++ /dev/null @@ -1,221 +0,0 @@ -pub mod commands; - -use crate::cache_storage::CacheStorage; -use crate::cli::commands::{Command, OverrideArgs, TestCommand, TestFlags}; -use crate::config::{BuildConf, Config, DEFAULT_CONFIG}; -use crate::error::Error; -use clap::Parser; -use simplex_test::TestClientProvider; -use std::env; -use std::path::PathBuf; -use std::process::Stdio; -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; - -#[derive(Debug, Parser)] -#[command(name = "simplicity-dex")] -#[command(about = "CLI for Simplicity Options trading on Liquid")] -pub struct Cli { - #[arg(short, long, env = "SIMPLEX_CONFIG")] - config: Option, - - #[command(subcommand)] - command: Command, - - #[command(flatten)] - override_args: OverrideArgs, -} - -struct TestParams { - cache_path: PathBuf, - test_path: TestPaths, - test_flags: TestFlags, -} - -enum TestPaths { - AllIntegration, - Names(Vec), -} - -impl Cli { - /// Runs the CLI command. - /// - /// # Errors - /// Returns an error if the command execution fails. - pub async fn run(self) -> Result<(), Error> { - let config_override = self.override_args.generate(); - - match self.command { - Command::Init => { - let config_path = Config::get_path()?; - std::fs::write(&config_path, DEFAULT_CONFIG)?; - println!("Config written to: '{}'", config_path.display()); - Ok(()) - } - Command::Config => { - let loaded_config = Config::load_or_discover(self.config.clone()) - .map_err(|e| Error::ConfigDiscoveryFailure(e))? - .override_cfg(config_override)?; - - dbg!(&loaded_config); - Ok(()) - } - Command::Test { command } => { - let loaded_config = Config::load_or_discover(self.config.clone()) - .map_err(|e| Error::ConfigDiscoveryFailure(e))? - .override_cfg(config_override)?; - - Self::run_test_command(loaded_config, &command)?; - - Ok(()) - } - Command::Regtest => { - let loaded_config = Config::load_or_discover(self.config.clone()) - .map_err(|e| Error::ConfigDiscoveryFailure(e))? - .override_cfg(config_override)?; - - let running = Arc::new(AtomicBool::new(true)); - let r = running.clone(); - - ctrlc::set_handler(move || { - r.store(false, Ordering::SeqCst); - }) - .expect("Error setting Ctrl-C handler"); - - let mut node = - TestClientProvider::create_default_node_with_stdin(loaded_config.test_config.elemendsd_path); - - println!("======================================"); - println!("Waiting for Ctrl-C..."); - println!("url: {}", node.rpc_url()); - let cookie_values = node.params.get_cookie_values()?.unwrap(); - println!("user: {:?}, password: {:?}", cookie_values.user, cookie_values.password); - println!("======================================"); - - while running.load(Ordering::SeqCst) {} - let _ = node.stop(); - println!("Exiting..."); - Ok(()) - } - Command::Build => { - let loaded_config = Config::load_or_discover(self.config.clone()) - .map_err(|e| Error::ConfigDiscoveryFailure(e))? - .override_cfg(config_override)?; - - if loaded_config.build_config.is_none() { - return Err(Error::Config( - "No build config to build contracts environment, please add appropriate config".to_string(), - )); - } - dbg!(&loaded_config); - - let build_config = dbg!(BuildConf::check_or_unwrap(loaded_config.build_config)?); - - let out_dir_unwrapped = build_config.out_dir.unwrap(); - let cwd = env::current_dir()?; - - match build_config.only_files { - true => { - simplex_template_gen_core::expand_only_files( - &cwd, - &out_dir_unwrapped, - &build_config.simf_files, - )?; - } - false => { - simplex_template_gen_core::expand_files_with_nested_dirs( - &cwd, - &build_config.base_dir, - &out_dir_unwrapped, - &build_config.simf_files, - )?; - } - } - - Ok(()) - } - } - } - - pub(crate) fn run_test_command(config: Config, command: &TestCommand) -> Result<(), Error> { - let cache_path = CacheStorage::save_cached_test_config(&config.test_config)?; - let mut test_command = match command { - TestCommand::Integration { additional_flags } => Self::form_test_command(TestParams { - cache_path, - test_path: TestPaths::AllIntegration, - test_flags: *additional_flags, - }), - TestCommand::Run { - tests, - additional_flags, - } => { - let test_path = if tests.is_empty() { - TestPaths::AllIntegration - } else { - TestPaths::Names(tests.clone()) - }; - Self::form_test_command(TestParams { - cache_path, - test_path, - test_flags: *additional_flags, - }) - } - }; - let output = test_command.output()?; - match output.status.code() { - Some(code) => { - println!("Exit Status: {}", code); - - if code == 0 { - println!("{}", String::from_utf8(output.stdout).unwrap()); - } - } - None => { - println!("Process terminated."); - } - } - Ok(()) - } - - fn form_test_command(params: TestParams) -> std::process::Command { - let mut test_command = std::process::Command::new("sh"); - test_command.arg("-c"); - let mut command_as_arg = String::new(); - match params.test_path { - TestPaths::AllIntegration => { - command_as_arg.push_str("cargo test --tests"); - } - TestPaths::Names(names) => { - let mut arg = "cargo test".to_string(); - for test_name in names { - arg.push_str(&format!(" --test {test_name}")); - } - command_as_arg.push_str(&arg); - } - } - { - let mut opt_params = String::new(); - if params.test_flags.show_output { - opt_params.push_str(" --show-output"); - } - if params.test_flags.nocapture { - opt_params.push_str(" --nocapture"); - } - if params.test_flags.ignored { - opt_params.push_str(" --ignored"); - } - if params.test_flags.show_output || params.test_flags.nocapture || params.test_flags.ignored { - command_as_arg.push_str(" --"); - command_as_arg.push_str(&opt_params); - } - } - test_command.args([command_as_arg]); - let _ = dbg!(test_command.get_args()); - test_command - .env(simplex_test::TEST_ENV_NAME, params.cache_path) - .stdin(Stdio::inherit()) - .stderr(Stdio::inherit()) - .stdout(Stdio::inherit()); - test_command - } -} diff --git a/crates/cli/src/commands/commands.rs b/crates/cli/src/commands/commands.rs new file mode 100644 index 0000000..f0297cf --- /dev/null +++ b/crates/cli/src/commands/commands.rs @@ -0,0 +1,41 @@ +use std::path::PathBuf; + +use clap::{Args, Subcommand}; + +#[derive(Debug, Subcommand)] +pub enum Command { + Init, + Config, + Regtest, + Test { + #[command(subcommand)] + command: TestCommand, + }, + Build { + out_dir: Option, + }, +} + +#[derive(Debug, Subcommand)] +pub enum TestCommand { + Integration { + #[command(flatten)] + additional_flags: TestFlags, + }, + Run { + #[arg(long)] + tests: Vec, + #[command(flatten)] + additional_flags: TestFlags, + }, +} + +#[derive(Debug, Args, Copy, Clone)] +pub struct TestFlags { + #[arg(long)] + pub nocapture: bool, + #[arg(long = "show-output")] + pub show_output: bool, + #[arg(long)] + pub ignored: bool, +} diff --git a/crates/cli/src/commands/error.rs b/crates/cli/src/commands/error.rs new file mode 100644 index 0000000..612bf82 --- /dev/null +++ b/crates/cli/src/commands/error.rs @@ -0,0 +1,11 @@ +#[derive(thiserror::Error, Debug)] +pub enum CommandError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error(transparent)] + Client(#[from] simplex_regtest::error::ClientError), + + #[error(transparent)] + Test(#[from] simplex_test::error::TestError), +} diff --git a/crates/cli/src/commands/mod.rs b/crates/cli/src/commands/mod.rs new file mode 100644 index 0000000..26f5e87 --- /dev/null +++ b/crates/cli/src/commands/mod.rs @@ -0,0 +1,4 @@ +pub mod commands; +pub mod test; +pub mod regtest; +pub mod error; diff --git a/crates/cli/src/commands/regtest.rs b/crates/cli/src/commands/regtest.rs new file mode 100644 index 0000000..d6afe2a --- /dev/null +++ b/crates/cli/src/commands/regtest.rs @@ -0,0 +1,34 @@ +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; + +use simplex_regtest::TestClient; + +use crate::commands::error::CommandError; + +pub struct Regtest {} + +impl Regtest { + pub fn run() -> Result<(), CommandError> { + let mut client = TestClient::new(); + + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + + ctrlc::set_handler(move || { + r.store(false, Ordering::SeqCst); + }) + .expect("Error setting Ctrl-C handler"); + + println!("======================================"); + println!("Waiting for Ctrl-C..."); + println!("rpc: {}", client.rpc_url()); + println!("esplora: {}", client.esplora_url()); + let auth = client.auth().get_user_pass().unwrap(); + println!("user: {:?}, password: {:?}", auth.0.unwrap(), auth.1.unwrap()); + println!("======================================"); + + while running.load(Ordering::SeqCst) {} + + Ok(client.kill()?) + } +} diff --git a/crates/cli/src/commands/test.rs b/crates/cli/src/commands/test.rs new file mode 100644 index 0000000..359c3da --- /dev/null +++ b/crates/cli/src/commands/test.rs @@ -0,0 +1,118 @@ +use std::path::PathBuf; +use std::process::Stdio; + +use simplex_test::TestConfig; + +use super::commands::{TestCommand, TestFlags}; +use super::error::CommandError; + +pub struct Test {} + +impl Test { + pub fn run(config: TestConfig, command: &TestCommand) -> Result<(), CommandError> { + let cache_path = Self::get_test_config_cache_name()?; + config.to_file(&cache_path)?; + + let mut cargo_test_command = Self::build_cargo_test_command(&cache_path, command); + + let output = cargo_test_command.output()?; + + match output.status.code() { + Some(code) => { + println!("Exit Status: {}", code); + + if code == 0 { + println!("{}", String::from_utf8(output.stdout).unwrap()); + } + } + None => { + println!("Process terminated."); + } + } + + Ok(()) + } + + fn build_cargo_test_command(cache_path: &PathBuf, command: &TestCommand) -> std::process::Command { + let mut command_as_arg = String::new(); + + match command { + TestCommand::Integration { additional_flags } => { + command_as_arg.push_str("cargo test --tests"); + + let flag_args = Self::build_test_flags(&additional_flags); + + if !flag_args.is_empty() { + command_as_arg.push_str(" --"); + command_as_arg.push_str(&flag_args); + } + } + TestCommand::Run { + tests, + additional_flags, + } => { + // TODO: check this behavior + if tests.is_empty() { + command_as_arg.push_str("cargo test --tests"); + } else { + let mut arg = "cargo test".to_string(); + + for test_name in tests { + arg.push_str(&format!(" --test {test_name}")); + } + + command_as_arg.push_str(&arg); + } + + let flag_args = Self::build_test_flags(&additional_flags); + + if !flag_args.is_empty() { + command_as_arg.push_str(" --"); + command_as_arg.push_str(&flag_args); + } + } + } + + let mut cargo_test_command = std::process::Command::new("sh"); + cargo_test_command.args(["-c".to_string(), command_as_arg]); + + cargo_test_command + .env(simplex_test::TEST_ENV_NAME, cache_path) + .stdin(Stdio::inherit()) + .stderr(Stdio::inherit()) + .stdout(Stdio::inherit()); + + cargo_test_command + } + + fn build_test_flags(flags: &TestFlags) -> String { + let mut opt_params = String::new(); + + if flags.nocapture { + opt_params.push_str(" --nocapture"); + } + + if flags.show_output { + opt_params.push_str(" --show-output"); + } + + if flags.ignored { + opt_params.push_str(" --ignored"); + } + + opt_params + } + + fn get_test_config_cache_name() -> Result { + const TARGET_DIR_NAME: &str = "target"; + const SIMPLEX_CACHE_DIR_NAME: &str = "simplex"; + const SIMPLEX_TEST_CONFIG_NAME: &str = "simplex_test_config.toml"; + + let cwd = std::env::current_dir()?; + + Ok(cwd + .join(TARGET_DIR_NAME) + .join(SIMPLEX_CACHE_DIR_NAME) + .join(SIMPLEX_TEST_CONFIG_NAME)) + } +} diff --git a/crates/cli/src/config.rs b/crates/cli/src/config.rs deleted file mode 100644 index 9bdc00e..0000000 --- a/crates/cli/src/config.rs +++ /dev/null @@ -1,292 +0,0 @@ -use crate::cli::commands::BuildOverrideArgs; -use crate::error::Error; -use globwalk::FileType; -use serde::{Deserialize, Serialize}; -use simplex_sdk::constants::SimplicityNetwork; -use simplex_test::{ElementsDConf, RpcCreds}; -use std::io; -use std::path::{Path, PathBuf}; -use std::str::FromStr; - -pub const DEFAULT_CONFIG: &str = include_str!("../Simplex.example.toml"); -const CONFIG_FILENAME: &str = "Simplex.toml"; - -#[derive(thiserror::Error, Debug)] -pub enum ConfigError { - /// Standard I/O errors. - #[error("IO error: {0}")] - Io(#[from] std::io::Error), - - /// Errors when parsing TOML configuration files. - #[error("TOML parse error: {0}")] - TomlParse(#[from] toml::de::Error), - - /// Errors when parsing TOML configuration files. - #[error("Unable to deserialize config: {0}")] - UnableToDeserialize(toml::de::Error), - - /// Errors when parsing env variable. - #[error("Unable to get env variable: {0}")] - UnableToGetEnv(#[from] std::env::VarError), - - /// Errors when getting a path to config. - #[error("Path doesn't a file: '{0}'")] - PathIsNotFile(PathBuf), - - /// Errors when getting a path to config. - #[error("Path doesn't exist: '{0}'")] - PathNotExist(PathBuf), - - /// Config is missing. - #[error("Config is missing in path: '{0}'")] - MissingConfig(PathBuf), -} - -#[derive(Debug, Clone)] -pub struct Config { - pub provider_config: ProviderConf, - pub test_config: ElementsDConf, - pub build_config: Option, -} - -#[derive(Debug, Clone)] -pub struct ProviderConf { - simplicity_network: SimplicityNetwork, -} - -#[derive(Debug, Default, Clone)] -pub struct ConfigOverride { - pub rpc_creds: Option, - pub network: Option, - pub build_conf: Option, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct BuildConf { - pub simf_files: Vec, - pub out_dir: Option, - pub base_dir: PathBuf, - pub only_files: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct _BuildConf { - simf_files: Vec, - out_dir: Option, - base_dir: Option, - #[serde(default)] - only_files: bool, -} - -impl BuildConf { - pub(crate) fn check_or_unwrap(loaded_config: Option) -> Result { - let loaded_config = loaded_config.unwrap(); - if loaded_config.simf_files.is_empty() { - return Err(Error::Config("No files listed to build contracts environment, please check glob patterns or 'compile_simf' field in config.".to_string())); - } - if loaded_config.out_dir.is_none() { - return Err(Error::Config("No out directory is set to build contracts environment, please check glob patterns or 'out_dir' field in config.".to_string())); - } - Ok(loaded_config) - } -} - -impl Default for ProviderConf { - fn default() -> Self { - ProviderConf { - simplicity_network: SimplicityNetwork::LiquidTestnet, - } - } -} - -impl Config { - pub fn get_path() -> Result { - let cwd = std::env::current_dir()?; - Ok(cwd.join(CONFIG_FILENAME)) - } - - pub fn discover() -> Result { - Config::_discover() - } - - pub fn override_cfg(mut self, cfg_override: Option) -> io::Result { - if let Some(cfg_override) = cfg_override { - if let Some(test_conf) = cfg_override.rpc_creds.clone() { - self.test_config = test_conf; - } - if let Some(network) = cfg_override.network { - self.provider_config.simplicity_network = network; - } - if let Some(build_args) = cfg_override.build_conf { - if let Some(ref mut build_conf) = self.build_config { - if build_args.out_dir.is_some() { - build_conf.out_dir = build_args.out_dir; - } - build_conf.only_files |= build_args.only_files; - } else { - // TODO: refactor config gathering, change io error into smth else - return Err(io::Error::other( - "Empty build_conf configuration, configure at least 'base_dir', 'simf_files' and 'out_dir'", - )); - } - } - } - Ok(self) - } - - pub fn load(path_buf: impl AsRef) -> Result { - Self::from_path(&path_buf) - } - - pub fn load_or_discover(path_buf: Option>) -> Result { - match path_buf { - Some(path) => Self::load(path), - None => Self::_discover(), - } - } - - fn _discover() -> Result { - let path = Self::get_path()?; - dbg!(&path); - if !path.is_file() { - return Err(ConfigError::PathIsNotFile(path)); - } - if !path.exists() { - return Err(ConfigError::PathNotExist(path)); - } - Ok(Config::from_path(&path)?) - } - - fn from_path(p: impl AsRef) -> Result { - std::fs::read_to_string(p.as_ref())?.parse() - } - - #[inline] - fn resolve_optional_dir_or_cwd(base: Option) -> io::Result { - match base { - None => std::env::current_dir(), - Some(b) => Ok(resolve_dir_path(b)?), - } - } - - #[inline] - fn resolve_optional_dir(base: Option) -> io::Result> { - match base { - None => Ok(None), - Some(b) => Ok(Some(resolve_dir_path(b)?)), - } - } -} - -impl FromStr for Config { - type Err = ConfigError; - - fn from_str(s: &str) -> Result { - let cfg: _Config = toml::from_str(s).map_err(ConfigError::UnableToDeserialize)?; - Ok(Config { - provider_config: ProviderConf { - simplicity_network: cfg.network.unwrap_or_default().into(), - }, - test_config: cfg - .test - .map(|x| ElementsDConf { - elemendsd_path: x - .elementsd_path - .unwrap_or(ElementsDConf::obtain_default_elementsd_path()), - rpc_creds: x.rpc_creds.unwrap_or_default(), - }) - .unwrap_or(ElementsDConf { - elemendsd_path: ElementsDConf::obtain_default_elementsd_path(), - rpc_creds: RpcCreds::None, - }), - build_config: match cfg.build { - None => None, - Some(x) => { - let base_dir = Self::resolve_optional_dir_or_cwd(x.base_dir)?; - Some(BuildConf { - simf_files: resolve_glob_paths(&base_dir, &x.simf_files)?, - out_dir: Self::resolve_optional_dir(x.out_dir)?, - base_dir, - only_files: x.only_files, - }) - } - }, - }) - } -} - -#[derive(Debug, Serialize, Deserialize)] -struct _Config { - network: Option<_NetworkName>, - test: Option, - build: Option<_BuildConf>, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TestingConfig { - elementsd_path: Option, - rpc_creds: Option, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] -#[serde(rename_all = "lowercase")] -enum _NetworkName { - #[default] - Liquid, - LiquidTestnet, - ElementsRegtest, -} - -impl Into for _NetworkName { - fn into(self) -> SimplicityNetwork { - match self { - _NetworkName::Liquid => SimplicityNetwork::Liquid, - _NetworkName::LiquidTestnet => SimplicityNetwork::LiquidTestnet, - _NetworkName::ElementsRegtest => SimplicityNetwork::default_regtest(), - } - } -} - -fn resolve_glob_paths(base_dir: impl AsRef, patterns: &[impl AsRef]) -> io::Result> { - let mut paths = Vec::new(); - - let walker = globwalk::GlobWalkerBuilder::from_patterns(base_dir, patterns) - .follow_links(true) - .file_type(FileType::FILE) - .build()? - .into_iter() - .filter_map(Result::ok); - - for img in walker { - paths.push(img.path().to_path_buf().canonicalize()?); - } - Ok(paths) -} - -fn resolve_dir_path(path: impl AsRef) -> io::Result { - let mut path_outer = PathBuf::from(path.as_ref()); - - if !path_outer.is_absolute() { - let manifest_dir = std::env::current_dir()?; - - let mut path_local = PathBuf::from(manifest_dir); - path_local.push(path_outer); - - path_outer = path_local; - } - - if path_outer.extension().is_some() { - return Err(io::Error::other(format!( - "Folder can't have an extension, path: '{}'", - path_outer.display() - ))); - } - if path_outer.is_file() { - return Err(io::Error::other(format!( - "Folder can't be a path, path: '{}'", - path_outer.display() - ))); - } - let path_outer = path_outer.canonicalize()?; - Ok(path_outer) -} diff --git a/crates/cli/src/config/config.rs b/crates/cli/src/config/config.rs new file mode 100644 index 0000000..68d327c --- /dev/null +++ b/crates/cli/src/config/config.rs @@ -0,0 +1,48 @@ +use serde::Deserialize; +use std::path::{Path, PathBuf}; + +use simplex_test::TestConfig; + +use super::error::ConfigError; + +pub const INIT_CONFIG: &str = include_str!("../../Simplex.default.toml"); +pub const CONFIG_FILENAME: &str = "Simplex.toml"; + +#[derive(Debug, Clone, Deserialize)] +pub struct Config { + #[serde(default)] + pub test: Option, + #[serde(default)] + pub build: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct BuildConf { + pub compile_simf: Option>, + pub out_dir: Option, +} + +impl Config { + pub fn get_default_path() -> Result { + let cwd = std::env::current_dir()?; + + Ok(cwd.join(CONFIG_FILENAME)) + } + + // TODO: load default values like `simf` path + pub fn load(path_buf: impl AsRef) -> Result { + let path = path_buf.as_ref().to_path_buf(); + + if !path.is_file() { + return Err(ConfigError::PathIsNotFile(path)); + } + + if !path.exists() { + return Err(ConfigError::PathNotExists(path)); + } + + let conf_str = std::fs::read_to_string(path)?; + + Ok(toml::from_str(conf_str.as_str()).map_err(ConfigError::UnableToDeserialize)?) + } +} diff --git a/crates/cli/src/config/error.rs b/crates/cli/src/config/error.rs new file mode 100644 index 0000000..86de7ff --- /dev/null +++ b/crates/cli/src/config/error.rs @@ -0,0 +1,22 @@ +use std::path::PathBuf; + +#[derive(thiserror::Error, Debug)] +pub enum ConfigError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("TOML parse error: {0}")] + TomlParse(#[from] toml::de::Error), + + #[error("Unable to deserialize config: {0}")] + UnableToDeserialize(toml::de::Error), + + #[error("Unable to get env variable: {0}")] + UnableToGetEnv(#[from] std::env::VarError), + + #[error("Path doesn't a file: '{0}'")] + PathIsNotFile(PathBuf), + + #[error("Path doesn't exist: '{0}'")] + PathNotExists(PathBuf), +} diff --git a/crates/cli/src/config/mod.rs b/crates/cli/src/config/mod.rs new file mode 100644 index 0000000..ce90ebd --- /dev/null +++ b/crates/cli/src/config/mod.rs @@ -0,0 +1,4 @@ +pub mod error; +pub mod config; + +pub use config::{Config, INIT_CONFIG}; diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs index 8748226..5f07829 100644 --- a/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -1,37 +1,16 @@ -use simplicityhl::simplicity::hex::HexToArrayError; +use crate::{commands::error::CommandError, config}; -/// Errors that can occur when using the Simplex CLI. #[derive(thiserror::Error, Debug)] -pub enum Error { - /// Errors related to configuration loading or validation. - #[error("Configuration error: '{0}'")] - Config(String), +pub enum CliError { + #[error(transparent)] + Config(#[from] config::error::ConfigError), + + #[error(transparent)] + Command(#[from] CommandError), - /// Standard I/O errors. #[error("IO error: '{0}'")] Io(#[from] std::io::Error), - /// Errors related to Partially Signed Elements Transactions (PSET). - #[error("PSET error: '{0}'")] - Pset(#[from] simplicityhl::elements::pset::Error), - - /// Errors when converting hex strings to byte arrays. - #[error("Hex to array error: '{0}'")] - HexToArray(#[from] HexToArrayError), - - /// Errors when using test suite to run elementsd node in regtest. - #[error("Occurred error with test suite, error: '{0}'")] - Test(#[from] Box), - - /// Errors when building config. - #[error("Occurred error with config building, error: '{0}'")] - ConfigError(#[from] crate::config::ConfigError), - - /// Errors when building config. - #[error("Failed to discover config, check existence or create new one with `simplex init`, error: '{0}'")] - ConfigDiscoveryFailure(crate::config::ConfigError), - - /// Errors when generating code for simplicity environment. #[error("Occurred code generation error, error: '{0}'")] CodeGenerator(#[from] simplex_template_gen_core::CodeGeneratorError), } diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index eb7d538..9a253d5 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,7 +1,4 @@ -#![warn(clippy::all, clippy::pedantic)] - -pub mod cache_storage; -pub mod cli; pub mod config; pub mod error; -pub mod logging; +pub mod cli; +pub mod commands; diff --git a/crates/cli/src/logging.rs b/crates/cli/src/logging.rs deleted file mode 100644 index 6247051..0000000 --- a/crates/cli/src/logging.rs +++ /dev/null @@ -1,10 +0,0 @@ -use tracing_subscriber::{EnvFilter, fmt, prelude::*}; - -pub fn init() { - let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); - - tracing_subscriber::registry() - .with(fmt::layer().with_target(true)) - .with(filter) - .init(); -} diff --git a/crates/macros-core/src/lib.rs b/crates/macros-core/src/lib.rs index 414eb5d..27285d0 100644 --- a/crates/macros-core/src/lib.rs +++ b/crates/macros-core/src/lib.rs @@ -20,5 +20,6 @@ pub fn expand_include_simf(input: &attr::parse::SynFilePath) -> syn::Result syn::Result { - test::expand(args, input) + // test::expand(args, input) + todo!() } diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml deleted file mode 100644 index 20fa314..0000000 --- a/crates/provider/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "simplex-provider" -version = "0.1.0" -license.workspace = true -edition.workspace = true -rust-version = "1.90.0" -description = "High-level explorer helper for standardising blocks exploration in Simplicity." -repository = "https://github.com/BlockstreamResearch/simplex" -documentation = "https://docs.rs/simplex" -keywords = ["esplora", "waterfall", "bitcoin", "elements", "explorer"] -categories = ["cryptography::cryptocurrencies", "web-programming::http-client", "wasm"] - -[lints] -workspace = true - -[dependencies] -simplicityhl = { workspace = true } -thiserror = { workspace = true } -electrsd = { workspace = true } - -async-trait = { version = "0.1.89" } -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } -minreq = { version = "2.14", features = ["https", "json-using-serde"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -hex-simd = "0.8.0" -bitcoin_hashes = "0.14.1" diff --git a/crates/provider/api-esplora.md b/crates/provider/api-esplora.md deleted file mode 100644 index 4d909f8..0000000 --- a/crates/provider/api-esplora.md +++ /dev/null @@ -1,442 +0,0 @@ -# Esplora HTTP API - -JSON over RESTful HTTP. Amounts are always represented in satoshis. - -The blockstream.info public APIs are available at: -- Bitcoin: https://blockstream.info/api/ -- Bitcoin Testnet: https://blockstream.info/testnet/api/ -- Bitcoin Signet: https://blockstream.info/signet/api/ -- Liquid: https://blockstream.info/liquid/api/ -- Liquid Testnet: https://blockstream.info/liquidtestnet/api/ - -For example: -```bash -$ curl https://blockstream.info/api/blocks/tip/hash -``` - -You can also [self-host the Esplora API server](https://github.com/Blockstream/esplora#how-to-run-the-explorer-for-bitcoin-mainnet), which provides better privacy and security. - -## Transactions - -### `GET /tx/:txid` - -Returns information about the transaction. - -Available fields: `txid`, `version`, `locktime`, `size`, `weight`, `fee`, `vin`, `vout` and `status` -(see [transaction format](#transaction-format) for details). - -### `GET /tx/:txid/status` - -Returns the transaction confirmation status. - -Available fields: `confirmed` (boolean), `block_height` (optional) and `block_hash` (optional). - -### `GET /tx/:txid/hex` -### `GET /tx/:txid/raw` - -Returns the raw transaction in hex or as binary data. - -### `GET /tx/:txid/merkleblock-proof` - -Returns a merkle inclusion proof for the transaction using -[bitcoind's merkleblock](https://bitcoin.org/en/glossary/merkle-block) format. - -*Note:* This endpoint is not currently available for Liquid/Elements-based chains. - -### `GET /tx/:txid/merkle-proof` - -Returns a merkle inclusion proof for the transaction using -[Electrum's `blockchain.transaction.get_merkle`](https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle) -format. - -### `GET /tx/:txid/outspend/:vout` - -Returns the spending status of a transaction output. - -Available fields: `spent` (boolean), `txid` (optional), `vin` (optional) and `status` (optional, the status of the spending tx). - -### `GET /tx/:txid/outspends` - -Returns the spending status of all transaction outputs. - -### `POST /tx` - -Broadcast a raw transaction to the network. - -The transaction should be provided as hex in the request body. -The `txid` will be returned on success. - -### `POST /txs/package` - -Broadcast a package of raw transactions to the network. - -A transaction package is a group of related transactions that may depend on each other (e.g., a child transaction spending outputs from an unconfirmed parent transaction). This is useful for CPFP (Child Pays For Parent) and other scenarios where transactions need to be evaluated together. - -The request body should contain a JSON array of transaction hex strings. - -Example request body: -```json -["02000000...", "02000000..."] -``` - -Returns a JSON object containing the package acceptance result. On success, returns information about each transaction in the package. - -*Note:* This endpoint requires Bitcoin Core 28.0 or later. - -## Addresses - -### `GET /address/:address` -### `GET /scripthash/:hash` - -Get information about an address/scripthash. - -Available fields: `address`/`scripthash`, `chain_stats` and `mempool_stats`. - -`{chain,mempool}_stats` each contain an object with `tx_count`, `funded_txo_count`, `funded_txo_sum`, `spent_txo_count` and `spent_txo_sum`. - -Elements-based chains don't have the `{funded,spent}_txo_sum` fields. - -### `GET /address/:address/txs` -### `GET /scripthash/:hash/txs` - -Get transaction history for the specified address/scripthash, sorted with newest first. - -Returns up to 50 mempool transactions plus the first 25 confirmed transactions. -You can request more confirmed transactions using `:last_seen_txid`(see below). - -### `GET /address/:address/txs/chain[/:last_seen_txid]` -### `GET /scripthash/:hash/txs/chain[/:last_seen_txid]` - -Get confirmed transaction history for the specified address/scripthash, sorted with newest first. - -Returns 25 transactions per page. More can be requested by specifying the last txid seen by the previous query. - -### `GET /address/:address/txs/mempool` -### `GET /scripthash/:hash/txs/mempool` - -Get unconfirmed transaction history for the specified address/scripthash. - -Returns up to 50 transactions (no paging). - -### `GET /address/:address/utxo` -### `GET /scripthash/:hash/utxo` - -Get the list of unspent transaction outputs associated with the address/scripthash. - -Available fields: `txid`, `vout`, `value` and `status` (with the status of the funding tx). - -Elements-based chains have a `valuecommitment` field that may appear in place of `value`, plus the following additional fields: `asset`/`assetcommitment`, `nonce`/`noncecommitment`, `surjection_proof` and `range_proof`. - -### `GET /address-prefix/:prefix` - -Search for addresses beginning with `:prefix`. - -Returns a JSON array with up to 10 results. - -## Blocks - -### `GET /block/:hash` - -Returns information about a block. - -Available fields: `id`, `height`, `version`, `timestamp`, `mediantime`, `bits`, `nonce`, `merkle_root`, `tx_count`, `size`, `weight`, and `previousblockhash`. -Elements-based chains have an additional `proof` field. -See [block format](#block-format) for more details. - -The response from this endpoint can be cached indefinitely. - -### `GET /block/:hash/header` - -Returns the hex-encoded block header. - -The response from this endpoint can be cached indefinitely. - -### `GET /block/:hash/status` - -Returns the block status. - -Available fields: `in_best_chain` (boolean, false for orphaned blocks), `next_best` (the hash of the next block, only available for blocks in the best chain). - -### `GET /block/:hash/txs[/:start_index]` - -Returns a list of transactions in the block (up to 25 transactions beginning at `start_index`). - -*Note:* The `start_index` value must be a multiple of 25. - -The response from this endpoint can be cached indefinitely. - -### `GET /block/:hash/txids` - -Returns a list of all txids in the block. - -The response from this endpoint can be cached indefinitely. - -### `GET /block/:hash/txid/:index` - -Returns the transaction at index `:index` within the specified block. - -The response from this endpoint can be cached indefinitely. - -### `GET /block/:hash/raw` - -Returns the raw block representation in binary. - -The response from this endpoint can be cached indefinitely. - -### `GET /block-height/:height` - -Returns the hash of the block currently at `height`. - -### `GET /blocks[/:start_height]` - -Returns the 10 newest blocks starting at the tip or at `start_height` if specified. - -### `GET /blocks/tip/height` - -Returns the height of the last block. - -### `GET /blocks/tip/hash` - -Returns the hash of the last block. - -## Mempool - -### `GET /mempool` - -Get mempool backlog statistics. Returns an object with: - -- `count`: the number of transactions in the mempool - -- `vsize`: the total size of mempool transactions in virtual bytes - -- `total_fee`: the total fee paid by mempool transactions in satoshis - -- `fee_histogram`: mempool fee-rate distribution histogram - - An array of `(feerate, vsize)` tuples, where each entry's `vsize` is the total vsize of transactions - paying more than `feerate` but less than the previous entry's `feerate` (except for the first entry, which has no upper bound). - This matches the format used by the Electrum RPC protocol for `mempool.get_fee_histogram`. - -Example output: - -``` -{ - "count": 8134, - "vsize": 3444604, - "total_fee":29204625, - "fee_histogram": [[53.01, 102131], [38.56, 110990], [34.12, 138976], [24.34, 112619], [3.16, 246346], [2.92, 239701], [1.1, 775272]] -} -``` - -> In this example, there are transactions weighting a total of 102,131 vbytes that are paying more than 53 sat/vB, -110,990 vbytes of transactions paying between 38 and 53 sat/vB, 138,976 vbytes paying between 34 and 38, etc. - - -### `GET /mempool/txids` - -Get the full list of txids in the mempool as an array. - -The order of the txids is arbitrary and does not match bitcoind's. - -### `GET /mempool/recent` - -Get a list of the last 10 transactions to enter the mempool. - -Each transaction object contains simplified overview data, with the following fields: `txid`, `fee`, `vsize` and `value` - -## Fee estimates - -### `GET /fee-estimates` - -Get an object where the key is the confirmation target (in number of blocks) -and the value is the estimated feerate (in sat/vB). - -The available confirmation targets are 1-25, 144, 504 and 1008 blocks. - -For example: `{ "1": 87.882, "2": 87.882, "3": 87.882, "4": 87.882, "5": 81.129, "6": 68.285, ..., "144": 1.027, "504": 1.027, "1008": 1.027 }` - -## Assets (Elements/Liquid only) - -### `GET /asset/:asset_id` - -Get information about an asset. - -For the network's native asset (i.e. LBTC in Liquid), returns an object with: - -- `asset_id` -- `chain_stats` and `mempool_stats`, each with: - - `tx_count` - - `peg_in_count` - - `peg_in_amount` - - `peg_out_amount` - - `peg_out_count` - - `burn_count` - - `burned_amount` - -For user-issued assets, returns an object with: - -- `asset_id` -- `issuance_txin`: the issuance transaction input - - `txid` - - `vin` -- `issuance_prevout`: the previous output spent for the issuance - - `txid` - - `vout` -- `status`: the confirmation status of the initial asset issuance transaction -- `contract_hash`: the contract hash committed as the issuance entropy -- `reissuance_token`: the asset id of the reissuance token -- `chain_stats` and `mempool_stats`, each with: - - `tx_count`: the number of transactions associated with this asset (does not include confidential transactions) - - `issuance_count`: the number of (re)issuance transactions - - `issued_amount`: the total known amount issued (should be considered a minimum bound when `has_blinded_issuances` is true) - - `burned_amount`: the total amount provably burned - - `has_blinded_issuances`: whether at least one of the (re)issuances were blind - - `reissuance_tokens`: the number of reissuance tokens - - `burned_reissuance_tokens`: the number of reissuance tokens burned - -If the asset is available on the registry, the following fields are returned as well: - -- `contract`: the full json contract json committed in the issuance -- `entity`: the entity linked to this asset. the only available type is currently `domain`, which is encoded as `{ "domain": "foobar.com>" }` (required) -- `ticker`: a 3-5 characters ticker associated with the asset (optional) -- `precision`: the number of decimal places for units of this asset (defaults to 0) -- `name`: a description for the asset (up to 255 characters) - -Example native asset: - -``` -{ - "asset_id": "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d", - "chain_stats": {"tx_count": 54, "peg_in_count": 2, "peg_in_amount": 1600000000, "peg_out_count": 51, "peg_out_amount": 250490000, "burn_count":0, "burned_amount": 0 }, - "mempool_stats": {"tx_count": 3, "peg_in_count": 0, "peg_in_amount": 0, "peg_out_count": 3, "peg_out_amount": 70020000, "burn_count": 0, "burned_amount": 0 } -} -``` - -Example user-issued asset: - -``` -{ - "asset_id": "d8a317ce2c14241192cbb3ebdb9696250ca1251a58ba6251c29fcfe126c9ca1f", - "issuance_txin":{ "txid": "39affca34bd51ed080f89f1e7a5c7a49d6d9e4779c84424ae50df67dd60dcaf7", "vin": 0}, - "issuance_prevout": { "txid": "0cdd74c540af637d5a3874ce8500891fd8e94ec8e3d5d436d86e87b6759a7674", "vout": 0 }, - "reissuance_token": "eb8b210d42566699796dbf78649120fd5c9d9b04cabc8f480856e04bd5e9fc22", - "contract_hash": "025d983cc774da665f412ccc6ccf51cb017671c2cb0d3c32d10d50ffdf0a57de", - "status": { "confirmed": true, "block_height": 105, "block_hash": "7bf84f2aea30b02981a220943f543a6d6e7ac646d59ef76cff27dca8d27b2b67", "block_time": 1586248729 }, - "chain_stats": { "tx_count": 1, "issuance_count": 1, "issued_amount": 0, "burned_amount": 0, "has_blinded_issuances": true, "reissuance_tokens": 0, "burned_reissuance_tokens": 0 }, - "mempool_stats": { "tx_count": 0, "issuance_count": 0, "issued_amount": 0, "burned_amount": 0, "has_blinded_issuances": false, "reissuance_tokens": null, "burned_reissuance_tokens": 0 } -} -``` - -### `GET /asset/:asset_id/txs` -### `GET /asset/:asset_id/txs/mempool` -### `GET /asset/:asset_id/txs/chain[/:last_seen]` - -Get transactions associated with the specified asset. - -For the network's native asset, returns a list of peg in, peg out and burn transactions. - -For user-issued assets, returns a list of issuance, reissuance and burn transactions. - -Does not include regular transactions transferring this asset. - -### `GET /asset/:asset_id/supply` -### `GET /asset/:asset_id/supply/decimal` - -Get the current total supply of the specified asset. - -For the native asset (LBTC), this is calculated as `{chain,mempool}_stats.peg_in_amount - {chain,mempool}_stats.peg_out_amount - {chain,mempool}_stats.burned_amount`. - -For issued assets, this is calculated as `{chain,mempool}_stats.issued_amount - {chain,mempool}_stats.burned_amount`. - -Not available for assets with blinded issuances. - -If `/decimal` is specified, returns the supply as a decimal according to the asset's divisibility. -Otherwise, returned in base units. - -### `GET /assets/registry` - -Get the list of issued assets in the asset registry. - -Query string parameters: - -- `start_index`: the start index to use for paging. defaults to 0. -- `limit`: maximum number of assets to return. defaults to 25, maximum 100. -- `sort_field`: field to sort assets by. one of `name`, `ticker` or `domain`. defaults to `ticker`. -- `sort_dir`: sorting direction. one of `asc` or `desc`. defaults to `asc`. - -Assets are returned in the same format as in `GET /asset/:asset_id`. - - -The total number of results will be returned as the `x-total-results` header. - -## Transaction format - -- `txid` -- `version` -- `locktime` -- `size` -- `weight` -- `fee` -- `vin[]` - - `txid` - - `vout` - - `is_coinbase` - - `scriptsig` - - `scriptsig_asm` - - `inner_redeemscript_asm` - - `inner_witnessscript_asm` - - `sequence` - - `witness[]` - - `prevout` (previous output in the same format as in `vout` below) - - *(Elements only)* - - `is_pegin` - - `issuance` (available for asset issuance transactions, `null` otherwise) - - `asset_id` - - `is_reissuance` - - `asset_id` - - `asset_blinding_nonce` - - `asset_entropy` - - `contract_hash` - - `assetamount` or `assetamountcommitment` - - `tokenamount` or `tokenamountcommitment` -- `vout[]` - - `scriptpubkey` - - `scriptpubkey_asm` - - `scriptpubkey_type` - - `scriptpubkey_address` - - `value` - - *(Elements only)* - - `valuecommitment` - - `asset` or `assetcommitment` - - `pegout` (available for peg-out outputs, `null` otherwise) - - `genesis_hash` - - `scriptpubkey` - - `scriptpubkey_asm` - - `scriptpubkey_address` -- `status` - - `confirmed` (boolean) - - `block_height` (available for confirmed transactions, `null` otherwise) - - `block_hash` (available for confirmed transactions, `null` otherwise) - - `block_time` (available for confirmed transactions, `null` otherwise) - -## Block format - -- `id` -- `height` -- `version` -- `timestamp` -- `bits` -- `nonce` -- `difficulty` -- `merkle_root` -- `tx_count` -- `size` -- `weight` -- `previousblockhash` -- `mediantime` (median time-past) -- *(Elements only)* -- `proof` - - `challenge` - - `challenge_asm` - - `solution` - - `solution_asm` \ No newline at end of file diff --git a/crates/provider/api-waterfall.md b/crates/provider/api-waterfall.md deleted file mode 100644 index a9fdf5d..0000000 --- a/crates/provider/api-waterfall.md +++ /dev/null @@ -1,364 +0,0 @@ -# Waterfalls API Documentation - -This document describes all available API endpoints for the Waterfalls server, which provides blockchain data indexing and querying capabilities for Bitcoin and Elements/Liquid networks. - -## Waterfalls Endpoints - -These endpoints provide transaction history and UTXO data for descriptors or addresses. Available in both JSON and CBOR formats. - -### Waterfalls Data (JSON) -``` -GET /v2/waterfalls? -``` - -### Waterfalls Data (CBOR) -``` -GET /v2/waterfalls.cbor? -``` - -**Note:** v1 exists for compatibility and v3 endpoints have been removed and return 404. - -**Query Parameters:** - -- `descriptor` (string): Bitcoin/Elements descriptor (plain text or encrypted with server key) - - Cannot be used together with `addresses` - - Supports encryption using age encryption with server's public key - - Network validation: mainnet descriptors (xpub) cannot be used on testnet/regtest - -- `addresses` (string): Comma-separated list of Bitcoin/Elements addresses - - Cannot be used together with `descriptor` - - Maximum addresses limited by server configuration - - Addresses cannot be blinded (confidential) - -- `page` (integer, optional): Page number for pagination (default: 0) - -- `to_index` (integer, optional): Maximum derivation index for descriptors (default: 0) - -- `utxo_only` (boolean, optional): Return only unspent outputs (default: false) - -**Response Format (JSON):** -```json -{ - "txs_seen": { - "descriptor_or_addresses": [ - { - "txid": "transaction_id", - "height": 12345, - "block_hash": "block_hash", - "block_timestamp": 1234567890, - "v": 1 - } - ] - }, - "page": 0, - "tip": "current_tip_hash" -} -``` - -**Differences between v1 and v2:** -- v2 includes `tip` field in response - -### Waterfalls Data with Full Tip Metadata (v4) - -``` -GET /v4/waterfalls? -GET /v4/waterfalls.cbor? -``` - -The v4 endpoints accept the same query parameters as v2 but return extended tip metadata including block height. This is particularly useful for Bitcoin, where the block height cannot be derived from the header alone. - -**Response Format (JSON):** -```json -{ - "txs_seen": { - "descriptor_or_addresses": [ - { - "txid": "transaction_id", - "height": 12345, - "block_hash": "block_hash", - "block_timestamp": 1234567890, - "v": 1 - } - ] - }, - "page": 0, - "tip_meta": { - "b": "current_tip_block_hash", - "t": 1234567890, - "h": 876543 - } -} -``` - -**Differences between v2 and v4:** -- v4 returns `tip_meta` object instead of `tip` hash string -- `tip_meta` contains: - - `b` (string): Block hash of the current tip - - `t` (integer): Block timestamp (Unix epoch seconds) - - `h` (integer): Block height - -### Last Used Index -``` -GET /v1/last_used_index?descriptor= -``` - -Returns the highest derivation index that has been used (has transaction history) for both external and internal chains. This is useful for quickly determining the next unused address without downloading full transaction history. - -**Query Parameters:** - -- `descriptor` (string, required): Bitcoin/Elements descriptor (plain text or encrypted with server key) - - Supports encryption using age encryption with server's public key - - Network validation: mainnet descriptors (xpub) cannot be used on testnet/regtest - -**Response Format (JSON):** -```json -{ - "external": 42, - "internal": 15, - "tip": "current_tip_hash" -} -``` - -**Response Fields:** - -- `external` (integer or null): Last used index on the external (receive) chain, or null if no addresses have been used -- `internal` (integer or null): Last used index on the internal (change) chain, or null if no addresses have been used -- `tip` (string, optional): Current blockchain tip hash for reference - -**Use Case:** - -This endpoint is optimized for applications that only need to know the next unused address index (e.g., Point of Sale systems) without the overhead of downloading full transaction history or computing balances. - -**Example:** - -To get the next unused external address, use index `external + 1` (or index `0` if `external` is null). - -## Base Endpoints - -### Server Information - -#### Get Server Public Key -``` -GET /v1/server_recipient -``` -Returns the server's public key for encryption purposes. - -**Response:** Plain text string containing the public key - -#### Get Server Address -``` -GET /v1/server_address -``` -Returns the server's Bitcoin/Elements address for message signing verification. - -**Response:** Plain text string containing the address - -#### Time Since Last Block -``` -GET /v1/time_since_last_block -``` -Returns the time elapsed since the last block and a freshness indicator. - -**Response:** Plain text describing time elapsed and status (e.g., "120 seconds since last block, less than 10 minutes") - -#### Build Information -``` -GET /v1/build_info -``` -Returns build and version information including git commit ID. - -**Response (JSON):** -```json -{ - "version": "0.9.4", - "git_commit": "a1b2c3d4e5f6789..." -} -``` - - - -## Blockchain Data Endpoints - -### Get Tip Hash -``` -GET /blocks/tip/hash -``` -Returns the hash of the current blockchain tip. - -**Response:** Plain text string containing the block hash - -### Get Block Hash by Height -``` -GET /block-height/{height} -``` -Returns the block hash for a specific block height. - -**Parameters:** -- `height` (integer): Block height - -**Response:** Plain text string containing the block hash, or 404 if not found - -### Get Block Header -``` -GET /block/{hash}/header -``` -Returns the block header for a specific block hash. - -**Parameters:** -- `hash` (string): Block hash - -**Response:** Hex-encoded block header, or 404 if not found - -### Get Raw Transaction -``` -GET /tx/{txid}/raw -``` -Returns the raw transaction data. - -**Parameters:** -- `txid` (string): Transaction ID - -**Response:** Binary transaction data (application/octet-stream) - -### Get Address Transactions -``` -GET /address/{address}/txs -``` -Returns transaction history for a specific address in Esplora-compatible format. - -**Parameters:** -- `address` (string): Bitcoin/Elements address - -**Response (JSON):** -```json -[ - { - "txid": "transaction_id", - "status": { - "block_height": 12345, - "block_hash": "block_hash_or_null" - } - } -] -``` - -## Transaction Operations - -### Broadcast Transaction -``` -POST /tx -``` -Broadcasts a raw transaction to the network. - -**Request Body:** Raw transaction hex string - -**Response:** -- Success (200): Transaction ID -- Error (400): Error message - -## Monitoring - -### Prometheus Metrics -``` -GET /metrics -``` -Returns Prometheus-formatted metrics for monitoring. - -**Response:** Text format metrics (text/plain) - -## Error Responses - -The API returns appropriate HTTP status codes: - -- `200 OK`: Successful request -- `400 Bad Request`: Invalid parameters or transaction broadcast failure -- `404 Not Found`: Resource not found (block, transaction, endpoint) -- `422 Unprocessable Entity`: Decryption failure (wrong identity used for encrypted descriptor) -- `500 Internal Server Error`: Server error - -Common error conditions: -- `AtLeastOneFieldMandatory`: Neither descriptor nor addresses provided -- `CannotSpecifyBothDescriptorAndAddresses`: Both descriptor and addresses provided -- `WrongNetwork`: Network mismatch (e.g., mainnet descriptor on testnet) -- `TooManyAddresses`: Exceeds maximum address limit -- `AddressCannotBeBlinded`: Blinded/confidential address provided -- `InvalidTxid`: Malformed transaction ID -- `InvalidBlockHash`: Malformed block hash -- `CannotFindTx`: Transaction not found -- `CannotFindBlockHeader`: Block header not found - -## Client Usage Examples - -The codebase includes a `WaterfallClient` class with the following methods: - -### Waterfalls Queries -```rust -// Query with descriptor (v2, JSON) -let (response, headers) = client.waterfalls(descriptor).await?; - -// Query with addresses -let (response, headers) = client.waterfalls_addresses(&addresses).await?; - -// Version-specific queries -let (response, headers) = client.waterfalls_v1(descriptor).await?; -let (response, headers) = client.waterfalls_v2(descriptor).await?; - -// UTXO-only query -let (response, headers) = client.waterfalls_v2_utxo_only(descriptor).await?; - -// Generic version with all parameters -let (response, headers) = client.waterfalls_version( - descriptor, - version, - page, - to_index, - utxo_only -).await?; -``` - -### Blockchain Data -```rust -// Get current tip -let tip_hash = client.tip_hash().await?; - -// Get block header -let header = client.header(block_hash).await?; - -// Get transaction -let transaction = client.tx(txid).await?; - -// Get address transactions -let txs_json = client.address_txs(&address).await?; -``` - -### Server Information -```rust -// Get server public key -let recipient = client.server_recipient().await?; - -// Get server address -let address = client.server_address().await?; -``` - -### Transaction Broadcasting -```rust -// Broadcast transaction -let txid = client.broadcast(&transaction).await?; -``` - -## Security Features - -- **Message Signing**: Responses include cryptographic signatures in headers: - - `X-Content-Signature`: Message signature - - `X-Content-Digest`: Content digest - - `X-Server-Address`: Server address for verification - -- **Encryption Support**: Descriptors can be encrypted using age encryption with the server's public key - -- **CORS Support**: Configurable CORS headers for web client access - -## Rate Limiting and Caching - -- Responses include appropriate cache control headers -- Address and transaction endpoints have long cache times for confirmed data -- Mempool/tip data has shorter cache times or no caching diff --git a/crates/provider/src/elements_rpc/mod.rs b/crates/provider/src/elements_rpc/mod.rs deleted file mode 100644 index 667d82d..0000000 --- a/crates/provider/src/elements_rpc/mod.rs +++ /dev/null @@ -1,419 +0,0 @@ -mod types; - -pub use types::*; - -use crate::error::ExplorerError; -use bitcoind::bitcoincore_rpc::{Auth, Client, RpcApi, bitcoin}; -use electrsd::bitcoind; -use serde_json::Value; -use simplicityhl::elements::{Address, AssetId, BlockHash, Txid}; -use std::str::FromStr; - -pub struct ElementsRpcClient { - inner: Client, - #[allow(unused)] - auth: Auth, - #[allow(unused)] - url: String, -} - -impl ElementsRpcClient { - pub fn new(url: &str, auth: Auth) -> Result { - let inner = Client::new(url, auth.clone())?; - inner.ping()?; - Ok(Self { - inner, - auth, - url: url.to_string(), - }) - } - - pub fn new_from_credentials(url: &str, user: &str, pass: &str) -> Result { - let auth = Auth::UserPass(user.to_string(), pass.to_string()); - Self::new(url, auth) - } - - pub fn client(&self) -> &Client { - &self.inner - } -} - -impl ElementsRpcClient { - pub fn height(client: &Client) -> Result { - const METHOD: &str = "getblockcount"; - - client - .call::(METHOD, &[])? - .as_u64() - .ok_or_else(|| ExplorerError::ElementsRpcUnexpectedReturn(METHOD.into())) - } - - pub fn blockchain_info(client: &Client) -> Result { - const METHOD: &str = "getblockchaininfo"; - - Ok(client.call::(METHOD, &[])?) - } - - pub fn sendtoaddress( - client: &Client, - address: &Address, - satoshi: u64, - asset: Option, - ) -> Result { - const METHOD: &str = "sendtoaddress"; - - let btc = sat2btc(satoshi); - let r = match asset { - Some(asset) => client.call::( - METHOD, - &[ - address.to_string().into(), - btc.into(), - "".into(), - "".into(), - false.into(), - false.into(), - 1.into(), - "UNSET".into(), - false.into(), - asset.to_string().into(), - ], - )?, - None => client.call::(METHOD, &[address.to_string().into(), btc.into()])?, - }; - Ok(Txid::from_str(r.as_str().unwrap()).unwrap()) - } - - pub fn rescanblockchain(client: &Client, start: Option, stop: Option) -> Result<(), ExplorerError> { - const METHOD: &str = "rescanblockchain"; - - let mut args = Vec::with_capacity(2); - if start.is_some() { - args.push(start.into()); - } - if stop.is_some() { - args.push(stop.into()); - } - client.call::(METHOD, &args)?; - Ok(()) - } - - pub fn getnewaddress(client: &Client, label: &str, kind: AddressType) -> Result { - const METHOD: &str = "getnewaddress"; - - let addr: Value = client.call(METHOD, &[label.into(), kind.to_string().into()])?; - Ok(Address::from_str(addr.as_str().unwrap()).unwrap()) - } - - pub fn generate_blocks(client: &Client, block_num: u32) -> Result<(), ExplorerError> { - const METHOD: &str = "generatetoaddress"; - - let address = Self::getnewaddress(client, "", AddressType::default())?.to_string(); - client.call::(METHOD, &[block_num.into(), address.into()])?; - Ok(()) - } - - pub fn sweep_initialfreecoins(client: &Client) -> Result<(), ExplorerError> { - const METHOD: &str = "sendtoaddress"; - - let address = Self::getnewaddress(client, "", AddressType::default())?; - client.call::( - METHOD, - &[ - address.to_string().into(), - "21".into(), - "".into(), - "".into(), - true.into(), - ], - )?; - Ok(()) - } - - pub fn issueasset(client: &Client, satoshi: u64) -> Result { - const METHOD: &str = "issueasset"; - - let btc = sat2btc(satoshi); - let r = client.call::(METHOD, &[btc.into(), 0.into()])?; - let asset = r.get("asset").unwrap().as_str().unwrap().to_string(); - Ok(AssetId::from_str(&asset)?) - } - - /// Get the genesis block hash from the running elementsd node. - /// - /// Could differ from the hardcoded one because parameters like `-initialfreecoins` - /// change the genesis hash. - pub fn genesis_block_hash(client: &Client) -> Result { - Self::block_hash(client, 0) - } - - pub fn block_hash(client: &Client, height: u64) -> Result { - const METHOD: &str = "getblockhash"; - - let raw: Value = client.call(METHOD, &[height.into()])?; - Ok(BlockHash::from_str(raw.as_str().unwrap())?) - } - - pub fn getpeginaddress(client: &Client) -> Result<(bitcoin::Address, String), ExplorerError> { - #[derive(serde::Deserialize)] - struct GetpeginaddressResult { - getpeginaddress: String, - claim_script: String, - } - - const METHOD: &str = "getpeginaddress"; - let value: GetpeginaddressResult = client.call(METHOD, &[]).unwrap(); - - let mainchain_address = bitcoin::Address::from_str(&value.getpeginaddress) - .unwrap() - .assume_checked(); - - Ok((mainchain_address, value.claim_script)) - } - - pub fn raw_createpsbt(client: &Client, inputs: Value, outputs: Value) -> Result { - const METHOD: &str = "createpsbt"; - - let psbt: serde_json::Value = client.call(METHOD, &[inputs, outputs, 0.into(), false.into()])?; - Ok(psbt.as_str().unwrap().to_string()) - } - - pub fn expected_next(client: &Client, base64: &str) -> Result { - const METHOD: &str = "analyzepsbt"; - - let value: serde_json::Value = client.call(METHOD, &[base64.into()])?; - Ok(value.get("next").unwrap().as_str().unwrap().to_string()) - } - - pub fn walletprocesspsbt(client: &Client, psbt: &str) -> Result { - const METHOD: &str = "walletprocesspsbt"; - - let value: serde_json::Value = client.call(METHOD, &[psbt.into()])?; - Ok(value.get("psbt").unwrap().as_str().unwrap().to_string()) - } - - pub fn finalizepsbt(client: &Client, psbt: &str) -> Result { - const METHOD: &str = "finalizepsbt"; - - let value: serde_json::Value = client.call(METHOD, &[psbt.into()])?; - assert!(value.get("complete").unwrap().as_bool().unwrap()); - Ok(value.get("hex").unwrap().as_str().unwrap().to_string()) - } - - pub fn sendrawtransaction(client: &Client, tx: &str) -> Result { - const METHOD: &str = "sendrawtransaction"; - - let value: serde_json::Value = client.call(METHOD, &[tx.into()])?; - Ok(SendRawTransaction { - txid: value.as_str().unwrap().to_string(), - }) - } - - pub fn sendrawtransaction_txid(client: &Client, tx: &str) -> Result { - const METHOD: &str = "sendrawtransaction"; - - let value: serde_json::Value = client.call(METHOD, &[tx.into()])?; - Ok(value.as_str().unwrap().to_string()) - } - - pub fn testmempoolaccept(client: &Client, tx: &str) -> Result { - const METHOD: &str = "testmempoolaccept"; - - let value: serde_json::Value = client.call(METHOD, &[[tx].into()])?; - Ok(value.as_array().unwrap()[0].get("allowed").unwrap().as_bool().unwrap()) - } - - pub fn create_wallet(client: &Client, wallet_name: Option) -> Result { - const METHOD: &str = "createwallet"; - - #[derive(serde::Deserialize)] - pub struct CreatewalletResult { - name: String, - #[allow(dead_code)] - warning: String, - } - - let value: CreatewalletResult = client.call( - METHOD, - &[ - wallet_name.unwrap_or("my_wallet_name".to_string()).into(), - false.into(), - false.into(), - "".into(), - false.into(), - false.into(), - true.into(), - false.into(), - ], - )?; - Ok(WalletMeta { name: value.name }) - } - - pub fn getbalance(client: &Client, conf: Option) -> Result { - const METHOD: &str = "getbalance"; - - Ok(client.call::(METHOD, &["*".into(), conf.unwrap_or_default().into()])?) - } - - pub fn listunspent( - client: &Client, - min_conf: Option, - max_conf: Option, - addresses: Option>, - include_unsafe: Option, - query_options: Option, - ) -> Result, ExplorerError> { - const METHOD: &str = "listunspent"; - - let mut args = Vec::new(); - args.push(min_conf.unwrap_or(1).into()); - args.push(max_conf.unwrap_or(9_999_999).into()); - - if let Some(addrs) = addresses { - args.push(addrs.into()); - } else { - args.push(serde_json::to_value(Vec::::new()).unwrap()); - } - - if include_unsafe.is_some() || query_options.is_some() { - args.push(include_unsafe.unwrap_or(true).into()); - } - - if let Some(opts) = query_options { - args.push(serde_json::to_value(opts).unwrap()); - } - - Ok(client.call::>(METHOD, &args)?) - } - - pub fn importaddress( - client: &Client, - address: &str, - label: Option<&str>, - rescan: Option, - p2sh: Option, - ) -> Result<(), ExplorerError> { - const METHOD: &str = "importaddress"; - - let mut args = vec![address.into()]; - - if let Some(lbl) = label { - args.push(lbl.into()); - } else { - args.push("".into()); - } - - if rescan.is_some() || p2sh.is_some() { - args.push(rescan.unwrap_or(true).into()); - } - - if let Some(p2sh_val) = p2sh { - args.push(p2sh_val.into()); - } - - client.call::(METHOD, &args)?; - Ok(()) - } - - pub fn validateaddress(client: &Client, address: &str) -> Result { - const METHOD: &str = "validateaddress"; - - let value: serde_json::Value = client.call(METHOD, &[address.into()])?; - value - .get("isvalid") - .and_then(serde_json::Value::as_bool) - .ok_or_else(|| ExplorerError::ElementsRpcUnexpectedReturn(METHOD.into())) - } - - pub fn scantxoutset( - client: &Client, - action: &str, - scanobjects: Option>, - ) -> Result { - const METHOD: &str = "scantxoutset"; - - let mut args = vec![action.into()]; - - match action { - "start" => { - if let Some(objects) = scanobjects { - args.push(serde_json::to_value(objects).map_err(|e| { - ExplorerError::InvalidInput(format!( - "Failed to transform objects into serde_json::Value, err: '{e}'" - )) - })?); - } else { - return Err(ExplorerError::InvalidInput( - "scantxoutset 'start' action requires scanobjects".to_string(), - )); - } - } - "abort" | "status" => { - if scanobjects.is_some() { - return Err(ExplorerError::InvalidInput(format!( - "scantxoutset '{action}' action does not accept scanobjects", - ))); - } - } - _ => { - return Err(ExplorerError::InvalidInput(format!( - "unknown scantxoutset action: {action}" - ))); - } - } - - let response = client.call::(METHOD, &args)?; - dbg!("response: {}", response.to_string()); - ScantxoutsetResult::from_value(response, action) - .map_err(|e| ExplorerError::ElementsRpcUnexpectedReturn(e.to_string())) - } - - pub fn gettransaction( - client: &Client, - txid: &str, - include_watchonly: Option, - ) -> Result { - const METHOD: &str = "gettransaction"; - - let mut args = vec![txid.into()]; - - if let Some(watchonly) = include_watchonly { - args.push(watchonly.into()); - } - - Ok(client.call::(METHOD, &args)?) - } - - pub fn getrawtransaction( - client: &Client, - txid: &str, - verbose: Option, - ) -> Result { - const METHOD: &str = "getrawtransaction"; - - let mut args = vec![txid.into()]; - - if let Some(v) = verbose { - args.push(v.into()); - } else { - args.push(true.into()); - } - - Ok(client.call::(METHOD, &args)?) - } - - pub fn getrawtransaction_hex(client: &Client, txid: &str) -> Result { - const METHOD: &str = "getrawtransaction"; - - let value: serde_json::Value = client.call(METHOD, &[txid.into(), false.into()])?; - let value = value - .as_str() - .ok_or_else(|| ExplorerError::InvalidInput("Failed to deserialize a String".to_string()))?; - Ok(value.to_string()) - } -} - -fn sat2btc(sat: u64) -> String { - let amount = bitcoin::Amount::from_sat(sat); - amount.to_string_in(bitcoin::amount::Denomination::Bitcoin) -} diff --git a/crates/provider/src/elements_rpc/types.rs b/crates/provider/src/elements_rpc/types.rs deleted file mode 100644 index 9de3e84..0000000 --- a/crates/provider/src/elements_rpc/types.rs +++ /dev/null @@ -1,328 +0,0 @@ -use serde::ser::Error; -use serde_json::Value; -use std::collections::HashMap; - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct GetBlockchainInfo { - pub chain: String, - pub blocks: u64, - pub headers: u64, - pub bestblockhash: String, - // pub difficulty: f64, - pub time: u64, - pub mediantime: u64, - pub verificationprogress: f64, - pub initialblockdownload: bool, - // pub chainwork: String, - pub size_on_disk: u64, - pub pruned: bool, - - // Elements specific fields - pub current_params_root: String, - // pub signblock_asm: String, - // pub signblock_hex: String, - pub current_signblock_asm: String, - pub current_signblock_hex: String, - pub max_block_witness: u64, - pub epoch_length: u64, - pub total_valid_epochs: u64, - pub epoch_age: u64, - - // Using Value here as the documentation describes it generically as "extension fields" - pub extension_space: Vec, - - // Optional pruning fields (only present if pruning is enabled) - #[serde(skip_serializing_if = "Option::is_none")] - pub pruneheight: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub automatic_pruning: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub prune_target_size: Option, - - // Softforks are deprecated but might still be present if configured - #[serde(skip_serializing_if = "Option::is_none")] - pub softforks: Option>, - - pub warnings: String, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct Softfork { - #[serde(rename = "type")] - pub fork_type: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub height: Option, - pub active: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub bip9: Option, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct SoftforkBip9 { - #[serde(skip_serializing_if = "Option::is_none")] - pub bit: Option, - pub start_time: u64, - pub timeout: u64, - pub min_activation_height: u64, - pub status: String, - pub since: u64, - pub status_next: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub statistics: Option, - pub signalling: String, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct SoftforkStatistics { - pub period: u64, - #[serde(skip_serializing_if = "Option::is_none")] - pub threshold: Option, - pub elapsed: u64, - pub count: u64, - #[serde(skip_serializing_if = "Option::is_none")] - pub possible: Option, -} - -pub struct WalletMeta { - pub name: String, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct GetBalance { - #[serde(rename = "mine")] - pub mine: BalanceDetails, - #[serde(rename = "watchonly")] - pub watchonly: Option, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct BalanceDetails { - pub trusted: f64, - pub untrusted_pending: f64, - pub immature: f64, -} - -#[derive(Default, Debug, Clone, Copy)] -pub enum AddressType { - Legacy, - P2shSegwit, - #[default] - Bech32, - Bech32m, -} - -impl std::fmt::Display for AddressType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let str = match self { - AddressType::Legacy => "legacy".to_string(), - AddressType::P2shSegwit => "p2sh-segwit".to_string(), - AddressType::Bech32 => "bech32".to_string(), - AddressType::Bech32m => "bech32m".to_string(), - }; - write!(f, "{str}") - } -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct ListUnspent { - pub txid: String, - pub vout: u32, - pub address: String, - pub label: Option, - #[serde(rename = "scriptPubKey")] - pub script_pubkey: String, - pub amount: f64, - pub amountcommitment: Option, - pub asset: Option, - pub assetcommitment: Option, - pub confirmations: u64, - pub bcconfirmations: Option, - pub spendable: bool, - pub solvable: bool, - pub safe: bool, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct QueryOptions { - #[serde(rename = "minimumAmount")] - pub minimum_amount: Option, - #[serde(rename = "maximumAmount")] - pub maximum_amount: Option, - #[serde(rename = "maximumCount")] - pub maximum_count: Option, - #[serde(rename = "minimumSumAmount")] - pub minimum_sum_amount: Option, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -#[serde(untagged)] -pub enum ScantxoutsetResult { - Start { - bestblock: String, - height: u64, - success: bool, - total_unblinded_bitcoin_amount: f64, - txouts: u32, - unspents: Vec, - }, - Abort { - success: bool, - }, - Status { - progress: f64, - searched_items: Option, - }, -} - -impl ScantxoutsetResult { - /// Parse the RPC response based on the action that was sent - pub fn from_value(value: serde_json::Value, action: &str) -> Result { - match action { - "start" => serde_json::from_value(value).map(|start_data: StartData| ScantxoutsetResult::Start { - bestblock: start_data.bestblock, - height: start_data.height, - success: start_data.success, - total_unblinded_bitcoin_amount: start_data.total_unblinded_bitcoin_amount, - txouts: start_data.txouts, - unspents: start_data.unspents, - }), - "abort" => serde_json::from_value(value).map(|abort_data: AbortData| ScantxoutsetResult::Abort { - success: abort_data.success, - }), - "status" => serde_json::from_value(value).map(|status_data: StatusData| ScantxoutsetResult::Status { - progress: status_data.progress, - searched_items: status_data.searched_items, - }), - _ => Err(serde_json::Error::custom(format!("unknown action: {action}"))), - } - } -} - -#[derive(Debug, serde::Deserialize)] -struct StartData { - bestblock: String, - height: u64, - success: bool, - total_unblinded_bitcoin_amount: f64, - txouts: u32, - unspents: Vec, -} - -#[derive(Debug, serde::Deserialize)] -struct AbortData { - success: bool, -} - -#[derive(Debug, serde::Deserialize)] -struct StatusData { - progress: f64, - searched_items: Option, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct ScantxoutsetUtxo { - pub amount: f64, - pub asset: String, - pub desc: String, - pub height: u64, - #[serde(rename = "scriptPubKey")] - pub scriptpubkey: String, - pub txid: String, - pub vout: u32, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct GetTransaction { - pub amount: f64, - pub fee: Option, - pub confirmations: i32, - pub blockhash: Option, - pub blockindex: Option, - pub blocktime: Option, - pub txid: String, - pub time: u64, - pub timereceived: u64, - #[serde(default)] - pub bip125_replaceable: String, - pub details: Vec, - pub hex: String, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct TransactionDetail { - pub involveswatchonly: Option, - pub address: Option, - pub category: String, - pub amount: f64, - pub label: Option, - pub vout: u32, - #[serde(default)] - pub fee: Option, - pub abandoned: Option, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct SendRawTransaction { - pub txid: String, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct GetRawTransaction { - pub in_active_chain: Option, - pub hex: String, - pub txid: String, - pub hash: String, - pub size: u32, - pub vsize: u32, - pub weight: u32, - pub version: u32, - pub locktime: u32, - pub vin: Vec, - pub vout: Vec, - pub blockhash: Option, - pub confirmations: Option, - pub time: Option, - pub blocktime: Option, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RawTransactionInput { - pub txid: String, - pub vout: u32, - #[serde(rename = "scriptSig")] - pub script_sig: ScriptSig, - #[serde(default)] - pub txinwitness: Vec, - pub sequence: u32, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct ScriptSig { - pub asm: String, - pub hex: String, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RawTransactionOutput { - pub value: f64, - pub n: u32, - #[serde(rename = "scriptPubKey")] - pub script_pubkey: ScriptPubKey, - #[serde(default)] - pub asset: Option, - #[serde(default)] - pub assetcommitment: Option, - #[serde(default)] - pub valuecommitment: Option, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct ScriptPubKey { - pub asm: String, - pub hex: String, - #[serde(rename = "reqSigs")] - pub req_sigs: Option, - #[serde(rename = "type")] - pub script_type: String, - pub addresses: Option>, -} diff --git a/crates/provider/src/error.rs b/crates/provider/src/error.rs deleted file mode 100644 index 60ddc9d..0000000 --- a/crates/provider/src/error.rs +++ /dev/null @@ -1,124 +0,0 @@ -use electrsd::bitcoind::bitcoincore_rpc::jsonrpc::minreq; -use reqwest::{StatusCode, Url}; - -#[derive(thiserror::Error, Debug)] -pub enum ExplorerError { - #[error("Failed to type to Url, {0}")] - UrlConversion(String), - - #[error("Failed to send request, [url: '{url:?}', code: {status:?}, text: '{text}']")] - Request { - url: Option, - status: Option, - text: String, - }, - - #[error("Failed to minreq send request, [err: '{err}']")] - RequestMinreq { err: minreq::Error }, - - #[error("Erroneous response, [url: '{url:?}', code: {status:?}, text: '{text}']")] - ErroneousRequest { - url: Option, - status: Option, - text: String, - }, - - #[error("Erroneous minreq response, [err: '{err}']")] - ErroneousRequestMinreq { err: minreq::Error }, - - #[error("Failed to deserialize response, [url: '{url:?}', code: {status:?}, text: '{text}']")] - Deserialize { - url: Option, - status: Option, - text: String, - }, - - #[error("Failed to deserialize minreq response, [err: '{err}']")] - DeserializeMinreq { err: minreq::Error }, - - #[error("Failed to decode hex value to array, {0}")] - BitcoinHashesHex(#[from] bitcoin_hashes::hex::HexToArrayError), - - #[error("Failed to decode hex value to array, {0}")] - ElementsHex(simplicityhl::elements::hex::Error), - - #[error("Failed to convert address value to Address, {0}")] - AddressConversion(String), - - #[error("Failed to decode commitment, type: {commitment_type:?}, error: {error}")] - CommitmentDecode { - commitment_type: CommitmentType, - error: simplicityhl::elements::encode::Error, - }, - - #[error("Failed to decode hex string using hex_simd, error: {0}")] - HexSimdDecode(hex_simd::Error), - - #[error("Failed to deserialize Transaction from hex, error: {0}")] - TransactionDecode(String), - - #[error(transparent)] - ElementsRpcError(#[from] electrsd::bitcoind::bitcoincore_rpc::Error), - - #[error("Elements RPC returned an unexpected value for call {0}")] - ElementsRpcUnexpectedReturn(String), - - #[error("Invalid input, err: {0}")] - InvalidInput(String), -} - -#[derive(Debug, Clone)] -pub enum CommitmentType { - Asset, - Nonce, - Value, -} - -impl ExplorerError { - #[inline] - pub(crate) fn response_failed_reqwest(e: &reqwest::Error) -> Self { - ExplorerError::Request { - url: e.url().cloned().map(|x| x.to_string()), - status: e.status(), - text: e.to_string(), - } - } - - #[inline] - pub(crate) fn erroneous_response_reqwest(e: &reqwest::Response) -> Self { - ExplorerError::ErroneousRequest { - url: Some(e.url().clone().to_string()), - status: Some(e.status()), - text: String::new(), - } - } - - #[inline] - pub(crate) fn response_failed_minreq(e: minreq::Error) -> Self { - ExplorerError::RequestMinreq { err: e } - } - - #[inline] - #[allow(clippy::cast_sign_loss)] - pub(crate) fn erroneous_response_minreq(e: &minreq::Response) -> Self { - ExplorerError::ErroneousRequest { - url: Some(e.url.clone()), - status: Some(StatusCode::from_u16(e.status_code as u16).unwrap()), - text: e.reason_phrase.clone(), - } - } - - #[inline] - pub(crate) fn deserialize_reqwest(e: &reqwest::Error) -> Self { - ExplorerError::Deserialize { - url: e.url().cloned(), - status: e.status(), - text: e.to_string(), - } - } - - #[inline] - pub(crate) fn deserialize_minreq(e: minreq::Error) -> Self { - ExplorerError::DeserializeMinreq { err: e } - } -} diff --git a/crates/provider/src/esplora/mod.rs b/crates/provider/src/esplora/mod.rs deleted file mode 100644 index bda1ba8..0000000 --- a/crates/provider/src/esplora/mod.rs +++ /dev/null @@ -1,2207 +0,0 @@ -mod types; - -use crate::error::ExplorerError; -use crate::esplora::deserializable::TypeConversion; -use simplicityhl::elements::pset::serialize::Deserialize; -use simplicityhl::elements::{BlockHash, Txid}; -use std::str::FromStr; - -const ESPLORA_LIQUID_TESTNET: &str = "https://blockstream.info/liquidtestnet/api"; -const ESPLORA_LIQUID: &str = "https://blockstream.info/liquid/api"; - -pub struct EsploraClientAsync { - url_builder: UrlBuilder, - client: reqwest::Client, -} - -pub struct EsploraClientSync { - url_builder: UrlBuilder, -} - -#[derive(Debug, Clone)] -pub struct EsploraClientBuilder { - url: Option, -} - -#[allow(dead_code)] -pub struct EsploraConfig { - url: String, -} - -// TODO: Illia add caching as optional parameter -impl EsploraClientBuilder { - fn default_url() -> String { - ESPLORA_LIQUID_TESTNET.to_string() - } - - #[must_use] - pub fn liquid_testnet() -> Self { - Self { - url: Some(ESPLORA_LIQUID_TESTNET.to_string()), - } - } - - #[must_use] - pub fn liquid_mainnet() -> Self { - Self { - url: Some(ESPLORA_LIQUID.to_string()), - } - } - - pub fn custom(url: impl AsRef) -> Self { - let url = { - let url = url.as_ref(); - url.trim_matches('/').to_string() - }; - EsploraClientBuilder { url: Some(url.into()) } - } - - #[must_use] - pub fn build_async(self) -> EsploraClientAsync { - EsploraClientAsync { - url_builder: UrlBuilder { - base_url: self.url.unwrap_or(Self::default_url()), - }, - client: reqwest::Client::new(), - } - } - - #[must_use] - pub fn build_sync(self) -> EsploraClientSync { - EsploraClientSync { - url_builder: UrlBuilder { - base_url: self.url.unwrap_or(Self::default_url()), - }, - } - } -} - -impl Default for EsploraClientBuilder { - fn default() -> Self { - EsploraClientBuilder::liquid_testnet() - } -} - -impl Default for EsploraClientAsync { - fn default() -> Self { - EsploraClientBuilder::default().build_async() - } -} - -impl Default for EsploraClientSync { - fn default() -> Self { - EsploraClientBuilder::default().build_sync() - } -} - -mod deserializable { - use crate::error::{CommitmentType, ExplorerError}; - use crate::esplora::types; - use crate::esplora::types::Stats; - use bitcoin_hashes::sha256d::Hash; - use simplicityhl::elements::confidential::{Asset, Nonce, Value}; - use simplicityhl::elements::{Address, AssetId, BlockHash, OutPoint, Script, TxMerkleNode, Txid}; - use std::str::FromStr; - - pub(crate) trait TypeConversion { - fn convert(self) -> Result; - } - - #[derive(serde::Deserialize)] - pub struct EsploraTransaction { - pub txid: String, - pub version: u32, - pub locktime: u32, - pub size: u64, - pub weight: u64, - pub fee: u64, - pub vin: Vec, - pub vout: Vec, - pub status: TxStatus, - pub discount_vsize: u64, - pub discount_weight: u64, - } - - #[allow(dead_code)] - #[derive(serde::Deserialize)] - pub struct Vin { - pub txid: String, - pub vout: u32, - pub is_coinbase: bool, - pub scriptsig: String, - pub scriptsig_asm: String, - pub inner_redeemscript_asm: Option, - pub inner_witnessscript_asm: Option, - pub sequence: u32, - #[serde(default)] - pub witness: Vec, - pub prevout: Option, - } - - #[derive(serde::Deserialize)] - pub struct Vout { - pub scriptpubkey: String, - pub scriptpubkey_asm: String, - pub scriptpubkey_type: String, - pub scriptpubkey_address: Option, - pub value: Option, - } - - #[derive(serde::Deserialize)] - pub struct TxStatus { - pub confirmed: bool, - pub block_height: Option, - pub block_hash: Option, - pub block_time: Option, - } - - #[derive(serde::Deserialize)] - pub struct AddressUtxo { - pub txid: String, - pub vout: u32, - pub status: TxStatus, - #[serde(flatten)] - pub utxo_info: UtxoInfo, - } - - #[derive(serde::Deserialize)] - #[serde(untagged)] - pub enum UtxoInfo { - Confidential { - valuecommitment: String, - assetcommitment: String, - noncecommitment: String, - }, - Explicit { - value: u64, - asset: String, - }, - } - - #[derive(serde::Deserialize)] - pub struct AddressInfo { - pub address: String, - pub chain_stats: types::ChainStats, - pub mempool_stats: types::MempoolStats, - } - - #[derive(serde::Deserialize)] - pub struct MerkleProof { - pub block_height: u64, - pub merkle: Vec, - pub pos: u64, - } - - #[derive(serde::Deserialize)] - pub struct Outspend { - pub spent: bool, - pub txid: Option, - pub vin: Option, - pub status: Option, - } - - #[allow(dead_code)] - #[derive(serde::Deserialize)] - pub struct MempoolRecent { - pub txid: String, - pub fee: u64, - pub vsize: u64, - pub discount_vsize: u64, - } - - #[derive(serde::Deserialize)] - pub struct ScripthashInfo { - pub scripthash: String, - pub chain_stats: Stats, - pub mempool_stats: Stats, - } - - #[derive(serde::Deserialize)] - pub struct Block { - pub id: String, - pub height: u64, - pub version: u32, - pub timestamp: u64, - pub mediantime: u64, - pub merkle_root: String, - pub tx_count: u64, - pub size: u64, - pub weight: u64, - pub previousblockhash: String, - pub ext: Option, - } - - #[allow(dead_code)] - #[derive(serde::Deserialize)] - #[serde(untagged)] - pub enum BlockExtDataRaw { - Proof { - challenge: String, - solution: String, - }, - Dynafed { - current: DynafedParamsRaw, - proposed: DynafedParamsRaw, - signblock_witness: Vec>, - }, - } - - #[allow(dead_code)] - #[derive(serde::Deserialize)] - #[serde(untagged)] - pub enum DynafedParamsRaw { - Null {}, - Compact { - signblockscript: String, - signblock_witness_limit: u32, - elided_root: String, - }, - } - - impl TypeConversion for TxStatus { - fn convert(self) -> Result { - let block_hash = match self.block_hash { - None => None, - Some(val) => match BlockHash::from_str(&val) { - Ok(x) => Some(x), - Err(e) => return Err(ExplorerError::BitcoinHashesHex(e)), - }, - }; - Ok(types::TxStatus { - confirmed: self.confirmed, - block_height: self.block_height, - block_hash, - block_time: self.block_time, - }) - } - } - - impl TypeConversion for AddressUtxo { - fn convert(self) -> Result { - let block_hash = self.status.block_hash.map(|hash| BlockHash::from_str(&hash)); - let block_hash = match block_hash { - None => None, - Some(Err(err)) => return Err(ExplorerError::BitcoinHashesHex(err)), - Some(Ok(x)) => Some(x), - }; - let utxo_info = match self.utxo_info { - UtxoInfo::Confidential { - assetcommitment, - noncecommitment, - valuecommitment, - } => types::UtxoInfo::Confidential { - asset_comm: Asset::from_commitment( - &hex_simd::decode_to_vec(assetcommitment).map_err(ExplorerError::HexSimdDecode)?, - ) - .map_err(|e| ExplorerError::CommitmentDecode { - commitment_type: CommitmentType::Asset, - error: e, - })?, - value_comm: Value::from_commitment( - &hex_simd::decode_to_vec(valuecommitment).map_err(ExplorerError::HexSimdDecode)?, - ) - .map_err(|e| ExplorerError::CommitmentDecode { - commitment_type: CommitmentType::Asset, - error: e, - })?, - nonce_comm: Nonce::from_commitment( - &hex_simd::decode_to_vec(noncecommitment).map_err(ExplorerError::HexSimdDecode)?, - ) - .map_err(|e| ExplorerError::CommitmentDecode { - commitment_type: CommitmentType::Asset, - error: e, - })?, - }, - UtxoInfo::Explicit { asset, value } => types::UtxoInfo::Explicit { - value, - asset: AssetId::from_str(&asset).map_err(ExplorerError::BitcoinHashesHex)?, - }, - }; - - Ok(types::AddressUtxo { - outpoint: OutPoint::new(Txid::from_str(&self.txid)?, self.vout), - status: types::TxStatus { - confirmed: self.status.confirmed, - block_height: self.status.block_height, - block_hash, - block_time: self.status.block_time, - }, - utxo_info, - }) - } - } - - impl TypeConversion for MerkleProof { - fn convert(self) -> Result { - let hashes = self - .merkle - .into_iter() - .map(|x| Hash::from_str(&x)) - .collect::, bitcoin_hashes::hex::HexToArrayError>>()?; - let merkle_proofs = hashes.into_iter().map(TxMerkleNode::from_raw_hash).collect(); - Ok(types::MerkleProof { - block_height: self.block_height, - merkle: merkle_proofs, - pos: self.pos, - }) - } - } - - impl TypeConversion for AddressInfo { - fn convert(self) -> Result { - Ok(types::AddressInfo { - address: Address::from_str(&self.address) - .map_err(|e| ExplorerError::AddressConversion(e.to_string()))?, - chain_stats: self.chain_stats, - mempool_stats: self.mempool_stats, - }) - } - } - - impl TypeConversion for EsploraTransaction { - fn convert(self) -> Result { - let status = self.status.convert()?; - let vin = self - .vin - .into_iter() - .map(TypeConversion::convert) - .collect::>()?; - let vout = self - .vout - .into_iter() - .map(TypeConversion::convert) - .collect::>()?; - - Ok(types::EsploraTransaction { - txid: Txid::from_str(&self.txid)?, - version: self.version, - locktime: self.locktime, - size: self.size, - weight: self.weight, - fee: self.fee, - vin, - vout, - status, - discount_vsize: self.discount_vsize, - discount_weight: self.discount_weight, - }) - } - } - - impl TypeConversion for Vout { - fn convert(self) -> Result { - Ok(types::Vout { - scriptpubkey: Script::from_str(&self.scriptpubkey).map_err(ExplorerError::ElementsHex)?, - scriptpubkey_asm: self.scriptpubkey_asm, - scriptpubkey_type: self.scriptpubkey_type, - scriptpubkey_address: self.scriptpubkey_address, - value: self.value, - }) - } - } - impl TypeConversion for Vin { - fn convert(self) -> Result { - let prevout = match self.prevout { - None => None, - Some(val) => Some(val.convert()?), - }; - - Ok(types::Vin { - out_point: OutPoint::default(), - is_coinbase: self.is_coinbase, - scriptsig: self.scriptsig, - scriptsig_asm: self.scriptsig_asm, - inner_redeemscript_asm: self.inner_redeemscript_asm, - inner_witnessscript_asm: self.inner_witnessscript_asm, - sequence: self.sequence, - witness: self.witness, - prevout, - }) - } - } - - impl TypeConversion for Outspend { - fn convert(self) -> Result { - let status = match self.status { - None => None, - Some(val) => Some(val.convert()?), - }; - let txid = match self.txid { - None => None, - Some(val) => Some(Txid::from_str(&val)?), - }; - - Ok(types::Outspend { - spent: self.spent, - txid, - vin: self.vin, - status, - }) - } - } - - impl TypeConversion for MempoolRecent { - fn convert(self) -> Result { - Ok(types::MempoolRecent { - txid: Txid::from_str(&self.txid)?, - fee: 0, - vsize: 0, - discount_vsize: 0, - }) - } - } - - impl TypeConversion for ScripthashInfo { - fn convert(self) -> Result { - Ok(types::ScripthashInfo { - scripthash: Script::from_str(&self.scripthash).map_err(ExplorerError::ElementsHex)?, - chain_stats: self.chain_stats, - mempool_stats: self.mempool_stats, - }) - } - } - - impl TypeConversion for Block { - fn convert(self) -> Result { - let ext = match self.ext { - None => None, - Some(val) => Some(val.convert()?), - }; - Ok(types::Block { - id: self.id, - height: self.height, - version: self.version, - timestamp: self.timestamp, - tx_count: self.tx_count, - size: self.size, - weight: self.weight, - merkle_root: TxMerkleNode::from_str(&self.merkle_root)?, - mediantime: self.mediantime, - previousblockhash: BlockHash::from_str(&self.previousblockhash)?, - ext, - }) - } - } - - impl TypeConversion for BlockExtDataRaw { - fn convert(self) -> Result { - todo!() - } - } -} - -impl EsploraClientAsync { - #[inline] - fn filter_resp(resp: &reqwest::Response) -> Result<(), ExplorerError> { - if is_resp_ok(i32::from(resp.status().as_u16())) { - return Err(ExplorerError::erroneous_response_reqwest(resp)); - } - Ok(()) - } - - /// Retrieves transaction details by transaction ID. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub async fn get_tx(&self, txid: &str) -> Result { - let url = self.url_builder.get_tx_url(txid)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp.convert()?; - - Ok(resp) - } - - /// Retrieves transaction status by transaction ID. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if block hash parsing fails - pub async fn get_tx_status(&self, txid: &str) -> Result { - let url = self.url_builder.get_tx_status_url(txid)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp.convert()?; - Ok(resp) - } - - /// Retrieves transaction hex by transaction ID. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response text extraction fails - pub async fn get_tx_hex(&self, txid: &str) -> Result { - let url = self.url_builder.get_tx_hex_url(txid)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.text().await.map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } - - /// Retrieves raw transaction bytes by transaction ID. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response bytes extraction fails - pub async fn get_tx_raw(&self, txid: &str) -> Result, ExplorerError> { - let url = self.url_builder.get_tx_raw_url(txid)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.bytes() - .await - .map(|b| b.to_vec()) - .map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } - - /// Retrieves and deserializes a transaction as an Elements transaction. - /// - /// # Errors - /// - Returns all errors from `get_tx_raw` - /// - Returns `ExplorerError::TransactionDecode` if transaction deserialization fails - pub async fn get_tx_elements(&self, txid: &str) -> Result { - let bytes = self.get_tx_raw(txid).await?; - simplicityhl::elements::Transaction::deserialize(&bytes) - .map_err(|e| ExplorerError::TransactionDecode(e.to_string())) - } - - /// Retrieves merkle proof for a transaction. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if merkle hash parsing fails - pub async fn get_tx_merkle_proof(&self, txid: &str) -> Result { - let url = self.url_builder.get_tx_merkle_proof_url(txid)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp.convert()?; - Ok(resp) - } - - /// Retrieves outspend information for a specific output. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub async fn get_tx_outspend(&self, txid: &str, vout: u32) -> Result { - let url = self.url_builder.get_tx_outspend_url(txid, vout)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp.convert()?; - Ok(resp) - } - - /// Retrieves outspend information for all outputs of a transaction. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub async fn get_tx_outspends(&self, txid: &str) -> Result, ExplorerError> { - let url = self.url_builder.get_tx_outspends_url(txid)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - resp.into_iter() - .map(deserializable::TypeConversion::convert) - .collect::, _>>() - } - - /// Broadcasts a transaction to the network. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response text extraction fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub async fn broadcast_tx(&self, tx: &simplicityhl::elements::Transaction) -> Result { - let tx_hex = simplicityhl::elements::encode::serialize_hex(tx); - let url = self.url_builder.get_broadcast_tx_url()?; - let resp = self - .client - .post(url) - .body(tx_hex) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp.text().await.map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - Ok(Txid::from_str(&resp)?) - } - - /// Broadcasts a package of transactions. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - pub async fn broadcast_tx_package( - &self, - txs: &[simplicityhl::elements::Transaction], - ) -> Result { - let url = self.url_builder.get_broadcast_tx_package_url()?; - let tx_hexes = txs - .iter() - .map(simplicityhl::elements::encode::serialize_hex) - .collect::>(); - - let resp = self - .client - .post(url) - .json(&tx_hexes) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.json().await.map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } - - /// Retrieves address information. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::AddressConversion` if address parsing fails - pub async fn get_address(&self, address: &str) -> Result { - let url = self.url_builder.get_address_url(address)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp.convert()?; - - Ok(resp) - } - - /// Retrieves all transactions for an address. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID/block hash parsing fails - pub async fn get_address_txs(&self, address: &str) -> Result, ExplorerError> { - let url = self.url_builder.get_address_txs_url(address)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - let resp = resp - .json::>() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp.into_iter().map(|x| x.convert()).collect::>()?; - Ok(resp) - } - - /// Retrieves confirmed transactions for an address with pagination. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID/block hash parsing fails - pub async fn get_address_txs_chain( - &self, - address: &str, - last_seen_txid: Option<&str>, - ) -> Result, ExplorerError> { - let url = self.url_builder.get_address_txs_chain_url(address, last_seen_txid)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves mempool transactions for an address. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub async fn get_address_txs_mempool( - &self, - address: &str, - ) -> Result, ExplorerError> { - let url = self.url_builder.get_address_txs_mempool_url(address)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves UTXOs for an address. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::HexSimdDecode` if hex decoding fails - /// - Returns `ExplorerError::CommitmentDecode` if commitment parsing fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID/block hash parsing fails - pub async fn get_address_utxo(&self, address: &str) -> Result, ExplorerError> { - let url = self.url_builder.get_address_utxo_url(address)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - resp.into_iter() - .map(deserializable::TypeConversion::convert) - .collect::, _>>() - } - - /// Retrieves scripthash information. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::ElementsHex` if script parsing fails - pub async fn get_scripthash(&self, hash: &str) -> Result { - let url = self.url_builder.get_scripthash_url(hash)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp.convert()?; - Ok(resp) - } - - /// Retrieves transactions for a scripthash. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response text extraction fails - pub async fn get_scripthash_txs(&self, hash: &str) -> Result { - let url = self.url_builder.get_scripthash_txs_url(hash)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.text().await.map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } - - /// Retrieves confirmed transactions for a scripthash with pagination. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response text extraction fails - pub async fn get_scripthash_txs_chain( - &self, - hash: &str, - last_seen_txid: Option<&str>, - ) -> Result { - let url = self.url_builder.get_scripthash_txs_chain_url(hash, last_seen_txid)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.text().await.map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } - - /// Retrieves mempool transactions for a scripthash. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response text extraction fails - pub async fn get_scripthash_txs_mempool(&self, hash: &str) -> Result { - let url = self.url_builder.get_scripthash_txs_mempool_url(hash)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.text().await.map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } - - /// Retrieves UTXOs for a scripthash. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response text extraction fails - pub async fn get_scripthash_utxo(&self, hash: &str) -> Result { - let url = self.url_builder.get_scripthash_utxo_url(hash)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.text().await.map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } - - /// Retrieves block information by block hash. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if hash/merkle node parsing fails - pub async fn get_block(&self, hash: &str) -> Result { - let url = self.url_builder.get_block_url(hash)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp.convert()?; - Ok(resp) - } - - /// Retrieves block header as hex string. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response text extraction fails - pub async fn get_block_header(&self, hash: &str) -> Result { - let url = self.url_builder.get_block_header_url(hash)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp.text().await.map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - Ok(resp) - } - - /// Retrieves block status information. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - pub async fn get_block_status(&self, hash: &str) -> Result { - let url = self.url_builder.get_block_status_url(hash)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.json::() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } - - /// Retrieves transactions in a block with optional pagination. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID/block hash parsing fails - pub async fn get_block_txs( - &self, - hash: &str, - start_index: Option, - ) -> Result, ExplorerError> { - let url = self.url_builder.get_block_txs_url(hash, start_index)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves transaction IDs in a block. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub async fn get_block_txids(&self, hash: &str) -> Result, ExplorerError> { - let url = self.url_builder.get_block_txids_url(hash)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - - let resp = resp - .into_iter() - .map(|val| Txid::from_str(&val)) - .collect::>()?; - Ok(resp) - } - - /// Retrieves a specific transaction ID from a block. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response text extraction fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub async fn get_block_txid(&self, hash: &str, index: u32) -> Result { - let url = self.url_builder.get_block_txid_url(hash, index)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp.text().await.map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - - Ok(Txid::from_str(&resp)?) - } - - /// Retrieves raw block bytes. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response bytes extraction fails - pub async fn get_block_raw(&self, hash: &str) -> Result, ExplorerError> { - let url = self.url_builder.get_block_raw_url(hash)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.bytes() - .await - .map(|b| b.to_vec()) - .map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } - - /// Retrieves block hash by block height. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response text extraction fails - /// - Returns `ExplorerError::BitcoinHashesHex` if block hash parsing fails - pub async fn get_block_height(&self, height: u64) -> Result { - let url = self.url_builder.get_block_height_url(height)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp.text().await.map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = BlockHash::from_str(&resp)?; - Ok(resp) - } - - /// Retrieves blocks starting from a given height. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if hash/merkle node parsing fails - pub async fn get_blocks(&self, start_height: Option) -> Result, ExplorerError> { - let url = self.url_builder.get_blocks_url(start_height)?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves the height of the blockchain tip. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - pub async fn get_blocks_tip_height(&self) -> Result { - let url = self.url_builder.get_blocks_tip_height_url()?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - Ok(resp) - } - - /// Retrieves the hash of the blockchain tip. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if response text extraction fails - /// - Returns `ExplorerError::BitcoinHashesHex` if block hash parsing fails - pub async fn get_blocks_tip_hash(&self) -> Result { - let url = self.url_builder.get_blocks_tip_hash_url()?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp.text().await.map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = BlockHash::from_str(&resp)?; - Ok(resp) - } - - /// Retrieves mempool information. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - pub async fn get_mempool(&self) -> Result { - let url = self.url_builder.get_mempool_url()?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.json().await.map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } - - /// Retrieves all transaction IDs in the mempool. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub async fn get_mempool_txids(&self) -> Result, ExplorerError> { - let url = self.url_builder.get_mempool_txids_url()?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp - .into_iter() - .map(|val| Txid::from_str(&val)) - .collect::>()?; - Ok(resp) - } - - /// Retrieves recent mempool transactions. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub async fn get_mempool_recent(&self) -> Result, ExplorerError> { - let url = self.url_builder.get_mempool_recent_url()?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e))?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves fee estimates. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_reqwest` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_reqwest` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_reqwest` if JSON deserialization fails - pub async fn get_fee_estimates(&self) -> Result { - let url = self.url_builder.get_fee_estimates_url()?; - let resp = self - .client - .get(url) - .send() - .await - .map_err(|e| ExplorerError::response_failed_reqwest(&e))?; - Self::filter_resp(&resp)?; - - resp.json::() - .await - .map_err(|e| ExplorerError::deserialize_reqwest(&e)) - } -} - -impl EsploraClientSync { - #[inline] - fn filter_resp(resp: &minreq::Response) -> Result<(), ExplorerError> { - if is_resp_ok(resp.status_code) { - return Err(ExplorerError::erroneous_response_minreq(resp)); - } - Ok(()) - } - - /// Retrieves transaction details by transaction ID. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub fn get_tx(&self, txid: &str) -> Result { - let url: String = self.url_builder.get_tx_url(txid)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .map_err(ExplorerError::deserialize_minreq)?; - let resp = resp.convert()?; - - Ok(resp) - } - - /// Retrieves transaction status by transaction ID. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if block hash parsing fails - pub fn get_tx_status(&self, txid: &str) -> Result { - let url: String = self.url_builder.get_tx_status_url(txid)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .map_err(ExplorerError::deserialize_minreq)?; - let resp = resp.convert()?; - Ok(resp) - } - - /// Retrieves transaction hex by transaction ID. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if response text extraction fails - pub fn get_tx_hex(&self, txid: &str) -> Result { - let url: String = self.url_builder.get_tx_hex_url(txid)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - Ok(resp.as_str().map_err(ExplorerError::deserialize_minreq)?.to_string()) - } - - /// Retrieves raw transaction bytes by transaction ID. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - pub fn get_tx_raw(&self, txid: &str) -> Result, ExplorerError> { - let url: String = self.url_builder.get_tx_raw_url(txid)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - Ok(resp.as_bytes().to_vec()) - } - - /// Retrieves and deserializes a transaction as an Elements transaction. - /// - /// # Errors - /// - Returns all errors from `get_tx_raw` - /// - Returns `ExplorerError::TransactionDecode` if transaction deserialization fails - pub fn get_tx_elements(&self, txid: &str) -> Result { - let bytes = self.get_tx_raw(txid)?; - simplicityhl::elements::Transaction::deserialize(&bytes) - .map_err(|e| ExplorerError::TransactionDecode(e.to_string())) - } - - /// Retrieves merkle proof for a transaction. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if merkle hash parsing fails - pub fn get_tx_merkle_proof(&self, txid: &str) -> Result { - let url: String = self.url_builder.get_tx_merkle_proof_url(txid)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp.convert()?; - Ok(resp) - } - - /// Retrieves outspend information for a specific output. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub fn get_tx_outspend(&self, txid: &str, vout: u32) -> Result { - let url: String = self.url_builder.get_tx_outspend_url(txid, vout)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp.convert()?; - Ok(resp) - } - - /// Retrieves outspend information for all outputs of a transaction. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub fn get_tx_outspends(&self, txid: &str) -> Result, ExplorerError> { - let url: String = self.url_builder.get_tx_outspends_url(txid)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .map_err(ExplorerError::response_failed_minreq)?; - resp.into_iter() - .map(deserializable::TypeConversion::convert) - .collect::, _>>() - } - - /// Broadcasts a transaction to the network. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON serialization or response text extraction fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub fn broadcast_tx(&self, tx: &simplicityhl::elements::Transaction) -> Result { - let tx_hex = simplicityhl::elements::encode::serialize_hex(tx); - let url: String = self.url_builder.get_broadcast_tx_url()?; - let resp = minreq::post(url) - .with_json(&tx_hex) - .map_err(ExplorerError::deserialize_minreq)? - .send() - .map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .as_str() - .map_err(ExplorerError::response_failed_minreq)? - .to_string(); - Ok(Txid::from_str(&resp)?) - } - - /// Broadcasts a package of transactions. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code or JSON parsing fails - /// - Returns `ExplorerError::deserialize_minreq` if JSON serialization fails - pub fn broadcast_tx_package( - &self, - txs: &[simplicityhl::elements::Transaction], - ) -> Result { - let url: String = self.url_builder.get_broadcast_tx_package_url()?; - let tx_hexes = txs - .iter() - .map(simplicityhl::elements::encode::serialize_hex) - .collect::>(); - - let resp = minreq::post(url) - .with_json(&tx_hexes) - .map_err(ExplorerError::deserialize_minreq)? - .send() - .map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - resp.json().map_err(ExplorerError::response_failed_minreq) - } - - /// Retrieves address information. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::AddressConversion` if address parsing fails - pub fn get_address(&self, address: &str) -> Result { - let url: String = self.url_builder.get_address_url(address)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp.convert()?; - - Ok(resp) - } - - /// Retrieves all transactions for an address. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID/block hash parsing fails - pub fn get_address_txs(&self, address: &str) -> Result, ExplorerError> { - let url: String = self.url_builder.get_address_txs_url(address)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - let resp = resp - .json::>() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves confirmed transactions for an address with pagination. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID/block hash parsing fails - pub fn get_address_txs_chain( - &self, - address: &str, - last_seen_txid: Option<&str>, - ) -> Result, ExplorerError> { - let url: String = self.url_builder.get_address_txs_chain_url(address, last_seen_txid)?; - - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves mempool transactions for an address. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub fn get_address_txs_mempool(&self, address: &str) -> Result, ExplorerError> { - let url: String = self.url_builder.get_address_txs_mempool_url(address)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves UTXOs for an address. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::HexSimdDecode` if hex decoding fails - /// - Returns `ExplorerError::CommitmentDecode` if commitment parsing fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID/block hash parsing fails - pub fn get_address_utxo(&self, address: &str) -> Result, ExplorerError> { - let url: String = self.url_builder.get_address_utxo_url(address)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .map_err(ExplorerError::response_failed_minreq)?; - resp.into_iter() - .map(deserializable::TypeConversion::convert) - .collect::, _>>() - } - - /// Retrieves scripthash information. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::ElementsHex` if script parsing fails - pub fn get_scripthash(&self, hash: &str) -> Result { - let url: String = self.url_builder.get_scripthash_url(hash)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp.convert()?; - Ok(resp) - } - - /// Retrieves transactions for a scripthash. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if response text extraction fails - pub fn get_scripthash_txs(&self, hash: &str) -> Result { - let url: String = self.url_builder.get_scripthash_txs_url(hash)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - Ok(resp - .as_str() - .map_err(ExplorerError::response_failed_minreq)? - .to_string()) - } - - /// Retrieves confirmed transactions for a scripthash with pagination. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if response text extraction fails - pub fn get_scripthash_txs_chain(&self, hash: &str, last_seen_txid: Option<&str>) -> Result { - let url: String = self.url_builder.get_scripthash_txs_chain_url(hash, last_seen_txid)?; - - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - Ok(resp - .as_str() - .map_err(ExplorerError::response_failed_minreq)? - .to_string()) - } - - /// Retrieves mempool transactions for a scripthash. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if response text extraction fails - pub fn get_scripthash_txs_mempool(&self, hash: &str) -> Result { - let url: String = self.url_builder.get_scripthash_txs_mempool_url(hash)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - Ok(resp - .as_str() - .map_err(ExplorerError::response_failed_minreq)? - .to_string()) - } - - /// Retrieves UTXOs for a scripthash. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if response text extraction fails - pub fn get_scripthash_utxo(&self, hash: &str) -> Result { - let url: String = self.url_builder.get_scripthash_utxo_url(hash)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - Ok(resp - .as_str() - .map_err(ExplorerError::response_failed_minreq)? - .to_string()) - } - - /// Retrieves block information by block hash. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if hash/merkle node parsing fails - pub fn get_block(&self, hash: &str) -> Result { - let url: String = self.url_builder.get_block_url(hash)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp.convert()?; - Ok(resp) - } - - /// Retrieves block header as hex string. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if response text extraction fails - pub fn get_block_header(&self, hash: &str) -> Result { - let url: String = self.url_builder.get_block_header_url(hash)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .as_str() - .map_err(ExplorerError::response_failed_minreq)? - .to_string(); - Ok(resp) - } - - /// Retrieves block status information. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - pub fn get_block_status(&self, hash: &str) -> Result { - let url: String = self.url_builder.get_block_status_url(hash)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - resp.json::() - .map_err(ExplorerError::response_failed_minreq) - } - - /// Retrieves transactions in a block with optional pagination. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID/block hash parsing fails - pub fn get_block_txs( - &self, - hash: &str, - start_index: Option, - ) -> Result, ExplorerError> { - let url: String = self.url_builder.get_block_txs_url(hash, start_index)?; - - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves transaction IDs in a block. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub fn get_block_txids(&self, hash: &str) -> Result, ExplorerError> { - let url: String = self.url_builder.get_block_txids_url(hash)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .map_err(ExplorerError::response_failed_minreq)?; - - let resp = resp - .into_iter() - .map(|val| Txid::from_str(&val)) - .collect::>()?; - Ok(resp) - } - - /// Retrieves a specific transaction ID from a block. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if response text extraction fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub fn get_block_txid(&self, hash: &str, index: u32) -> Result { - let url: String = self.url_builder.get_block_txid_url(hash, index)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp.as_str().map_err(ExplorerError::response_failed_minreq)?; - - Ok(Txid::from_str(resp)?) - } - - /// Retrieves raw block bytes. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - pub fn get_block_raw(&self, hash: &str) -> Result, ExplorerError> { - let url: String = self.url_builder.get_block_raw_url(hash)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - Ok(resp.as_bytes().to_vec()) - } - - /// Retrieves block hash by block height. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if response text extraction fails - /// - Returns `ExplorerError::BitcoinHashesHex` if block hash parsing fails - pub fn get_block_height(&self, height: u64) -> Result { - let url: String = self.url_builder.get_block_height_url(height)?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp.as_str().map_err(ExplorerError::response_failed_minreq)?; - - let resp = BlockHash::from_str(resp)?; - Ok(resp) - } - - /// Retrieves blocks starting from a given height. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if hash/merkle node parsing fails - pub fn get_blocks(&self, start_height: Option) -> Result, ExplorerError> { - let url = self.url_builder.get_blocks_url(start_height)?; - - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves the height of the blockchain tip. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - pub fn get_blocks_tip_height(&self) -> Result { - let url: String = self.url_builder.get_blocks_tip_height_url()?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp.json::().map_err(ExplorerError::response_failed_minreq)?; - Ok(resp) - } - - /// Retrieves the hash of the blockchain tip. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if response text extraction fails - /// - Returns `ExplorerError::BitcoinHashesHex` if block hash parsing fails - pub fn get_blocks_tip_hash(&self) -> Result { - let url: String = self.url_builder.get_blocks_tip_hash_url()?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp.as_str().map_err(ExplorerError::response_failed_minreq)?; - let resp = BlockHash::from_str(resp)?; - Ok(resp) - } - - /// Retrieves mempool information. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - pub fn get_mempool(&self) -> Result { - let url: String = self.url_builder.get_mempool_url()?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - resp.json().map_err(ExplorerError::response_failed_minreq) - } - - /// Retrieves all transaction IDs in the mempool. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub fn get_mempool_txids(&self) -> Result, ExplorerError> { - let url: String = self.url_builder.get_mempool_txids_url()?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp - .into_iter() - .map(|val| Txid::from_str(&val)) - .collect::>()?; - Ok(resp) - } - - /// Retrieves recent mempool transactions. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - /// - Returns `ExplorerError::BitcoinHashesHex` if TXID parsing fails - pub fn get_mempool_recent(&self) -> Result, ExplorerError> { - let url: String = self.url_builder.get_mempool_recent_url()?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - let resp = resp - .json::>() - .map_err(ExplorerError::response_failed_minreq)?; - let resp = resp - .into_iter() - .map(deserializable::TypeConversion::convert) - .collect::>()?; - Ok(resp) - } - - /// Retrieves fee estimates. - /// - /// # Errors - /// - Returns `ExplorerError::response_failed_minreq` if the HTTP request fails - /// - Returns `ExplorerError::erroneous_response_minreq` if the API returns an error status code - /// - Returns `ExplorerError::deserialize_minreq` if JSON deserialization fails - pub fn get_fee_estimates(&self) -> Result { - let url: String = self.url_builder.get_fee_estimates_url()?; - let resp = minreq::get(url).send().map_err(ExplorerError::response_failed_minreq)?; - Self::filter_resp(&resp)?; - - resp.json::() - .map_err(ExplorerError::response_failed_minreq) - } -} - -struct UrlBuilder { - base_url: String, -} - -impl UrlBuilder { - fn get_tx_url(&self, txid: &str) -> Result { - self.join_url(format!("/tx/{txid}")) - } - - fn get_tx_status_url(&self, txid: &str) -> Result { - self.join_url(format!("tx/{txid}/status")) - } - - fn get_tx_hex_url(&self, txid: &str) -> Result { - self.join_url(format!("tx/{txid}/hex")) - } - - fn get_tx_raw_url(&self, txid: &str) -> Result { - self.join_url(format!("tx/{txid}/raw")) - } - - fn get_tx_merkle_proof_url(&self, txid: &str) -> Result { - self.join_url(format!("tx/{txid}/merkle-proof")) - } - - fn get_tx_outspend_url(&self, txid: &str, vout: u32) -> Result { - self.join_url(format!("tx/{txid}/outspend/{vout}")) - } - - fn get_tx_outspends_url(&self, txid: &str) -> Result { - self.join_url(format!("tx/{txid}/outspends")) - } - - fn get_broadcast_tx_url(&self) -> Result { - self.join_url("tx") - } - - fn get_broadcast_tx_package_url(&self) -> Result { - self.join_url("txs/package") - } - - fn get_address_url(&self, address: &str) -> Result { - self.join_url(format!("address/{address}")) - } - - fn get_address_txs_url(&self, address: &str) -> Result { - self.join_url(format!("address/{address}/txs")) - } - - fn get_address_txs_chain_url(&self, address: &str, last_seen_txid: Option<&str>) -> Result { - if let Some(txid) = last_seen_txid { - self.join_url(format!("address/{address}/txs/chain/{txid}")) - } else { - self.join_url(format!("address/{address}/txs/chain")) - } - } - - fn get_address_txs_mempool_url(&self, address: &str) -> Result { - self.join_url(format!("address/{address}/txs/mempool")) - } - - fn get_address_utxo_url(&self, address: &str) -> Result { - self.join_url(format!("address/{address}/utxo")) - } - - fn get_scripthash_url(&self, hash: &str) -> Result { - self.join_url(format!("scripthash/{hash}")) - } - - fn get_scripthash_txs_url(&self, hash: &str) -> Result { - self.join_url(format!("scripthash/{hash}/txs")) - } - - fn get_scripthash_txs_chain_url(&self, hash: &str, last_seen_txid: Option<&str>) -> Result { - if let Some(txid) = last_seen_txid { - self.join_url(format!("scripthash/{hash}/txs/chain/{txid}")) - } else { - self.join_url(format!("scripthash/{hash}/txs/chain")) - } - } - - fn get_scripthash_txs_mempool_url(&self, hash: &str) -> Result { - self.join_url(format!("scripthash/{hash}/txs/mempool")) - } - - fn get_scripthash_utxo_url(&self, hash: &str) -> Result { - self.join_url(format!("scripthash/{hash}/utxo")) - } - - fn get_block_url(&self, hash: &str) -> Result { - self.join_url(format!("block/{hash}")) - } - - fn get_block_header_url(&self, hash: &str) -> Result { - self.join_url(format!("block/{hash}/header")) - } - - fn get_block_status_url(&self, hash: &str) -> Result { - self.join_url(format!("block/{hash}/status")) - } - - fn get_block_txs_url(&self, hash: &str, start_index: Option) -> Result { - if let Some(index) = start_index { - self.join_url(format!("block/{hash}/txs/{index}")) - } else { - self.join_url(format!("block/{hash}/txs")) - } - } - - fn get_block_txids_url(&self, hash: &str) -> Result { - self.join_url(format!("block/{hash}/txids")) - } - - fn get_block_txid_url(&self, hash: &str, index: u32) -> Result { - self.join_url(format!("block/{hash}/txid/{index}")) - } - - fn get_block_raw_url(&self, hash: &str) -> Result { - self.join_url(format!("block/{hash}/raw")) - } - - fn get_block_height_url(&self, height: u64) -> Result { - self.join_url(format!("block-height/{height}")) - } - - fn get_blocks_url(&self, start_height: Option) -> Result { - if let Some(height) = start_height { - self.join_url(format!("blocks/{height}")) - } else { - self.join_url("blocks") - } - } - - fn get_blocks_tip_height_url(&self) -> Result { - self.join_url("blocks/tip/height") - } - - fn get_blocks_tip_hash_url(&self) -> Result { - self.join_url("blocks/tip/hash") - } - - fn get_mempool_url(&self) -> Result { - self.join_url("mempool") - } - - fn get_mempool_txids_url(&self) -> Result { - self.join_url("mempool/txids") - } - - fn get_mempool_recent_url(&self) -> Result { - self.join_url("mempool/recent") - } - - fn get_fee_estimates_url(&self) -> Result { - self.join_url("fee-estimates") - } -} - -trait BaseUrlGetter { - fn get_base_url(&self) -> &str; -} - -trait UrlAppender { - fn join_url(&self, str: impl AsRef) -> Result; -} - -impl UrlAppender for T { - #[inline] - fn join_url(&self, str: impl AsRef) -> Result { - Ok(format!("{}/{}", self.get_base_url(), str.as_ref())) - } -} - -impl BaseUrlGetter for UrlBuilder { - fn get_base_url(&self) -> &str { - self.base_url.as_str() - } -} - -impl BaseUrlGetter for EsploraClientAsync { - fn get_base_url(&self) -> &str { - self.url_builder.get_base_url() - } -} - -impl BaseUrlGetter for EsploraClientSync { - fn get_base_url(&self) -> &str { - self.url_builder.get_base_url() - } -} - -fn is_resp_ok(code: i32) -> bool { - !(200..300).contains(&code) -} diff --git a/crates/provider/src/esplora/types.rs b/crates/provider/src/esplora/types.rs deleted file mode 100644 index 5f0a733..0000000 --- a/crates/provider/src/esplora/types.rs +++ /dev/null @@ -1,167 +0,0 @@ -use serde::Deserialize; -use simplicityhl::elements::{AssetId, BlockHash, OutPoint, Script, TxMerkleNode, Txid}; -use std::collections::HashMap; - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct EsploraTransaction { - pub txid: Txid, - pub version: u32, - pub locktime: u32, - pub vin: Vec, - pub vout: Vec, - pub size: u64, - pub weight: u64, - pub fee: u64, - pub status: TxStatus, - pub discount_vsize: u64, - pub discount_weight: u64, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct Vin { - pub out_point: OutPoint, - pub is_coinbase: bool, - pub scriptsig: String, - pub scriptsig_asm: String, - pub inner_redeemscript_asm: Option, - pub inner_witnessscript_asm: Option, - pub sequence: u32, - pub witness: Vec, - pub prevout: Option, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct Vout { - pub scriptpubkey: Script, - pub scriptpubkey_asm: String, - pub scriptpubkey_type: String, - pub scriptpubkey_address: Option, - pub value: Option, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct TxStatus { - pub confirmed: bool, - pub block_height: Option, - pub block_hash: Option, - pub block_time: Option, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct Outspend { - pub spent: bool, - pub txid: Option, - pub vin: Option, - pub status: Option, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct AddressInfo { - pub address: simplicityhl::elements::Address, - pub chain_stats: ChainStats, - pub mempool_stats: MempoolStats, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct ScripthashInfo { - pub scripthash: Script, - pub chain_stats: Stats, - pub mempool_stats: Stats, -} - -pub type MempoolStats = ChainStats; - -#[derive(Debug, Clone, Deserialize, Hash, Eq, PartialEq)] -pub struct ChainStats { - #[serde(rename = "funded_txo_count")] - pub funded_txo: u64, - #[serde(rename = "spent_txo_count")] - pub spent_txo: u64, - #[serde(rename = "tx_count")] - pub tx: u64, -} - -#[derive(Debug, Clone, Deserialize, Hash, Eq, PartialEq)] -pub struct Stats { - #[serde(rename = "tx_count")] - pub tx: u64, - #[serde(rename = "funded_txo_count")] - pub funded_txo: u64, - #[serde(rename = "spent_txo_count")] - pub spent_txo: u64, -} - -#[allow(dead_code)] -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct Utxo { - pub outpoint: OutPoint, - pub value: u64, - pub status: TxStatus, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct AddressUtxo { - pub outpoint: OutPoint, - pub status: TxStatus, - pub utxo_info: UtxoInfo, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub enum UtxoInfo { - Confidential { - value_comm: simplicityhl::elements::confidential::Value, - asset_comm: simplicityhl::elements::confidential::Asset, - nonce_comm: simplicityhl::elements::confidential::Nonce, - }, - Explicit { - value: u64, - asset: AssetId, - }, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Block { - pub id: String, - pub height: u64, - pub version: u32, - pub timestamp: u64, - pub tx_count: u64, - pub size: u64, - pub weight: u64, - pub merkle_root: TxMerkleNode, - pub mediantime: u64, - pub previousblockhash: BlockHash, - pub ext: Option, -} - -#[derive(Debug, Clone, Deserialize, Hash, Eq, PartialEq)] -pub struct BlockStatus { - pub in_best_chain: bool, - pub height: u64, - pub next_best: Option, -} - -#[derive(Debug, Clone, Deserialize, PartialEq)] -pub struct MempoolInfo { - pub count: u64, - pub vsize: u64, - pub total_fee: u64, - pub fee_histogram: Vec<(f64, u64)>, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct MempoolRecent { - pub txid: Txid, - pub fee: u64, - pub vsize: u64, - pub discount_vsize: u64, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct MerkleProof { - pub block_height: u64, - pub merkle: Vec, - pub pos: u64, -} - -pub type FeeEstimates = HashMap; diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs deleted file mode 100644 index 7c1ac7c..0000000 --- a/crates/provider/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![warn(clippy::all, clippy::pedantic)] - -pub mod elements_rpc; -mod error; -pub mod esplora; - -pub use error::*; diff --git a/crates/regtest/Cargo.toml b/crates/regtest/Cargo.toml new file mode 100644 index 0000000..d5ff34a --- /dev/null +++ b/crates/regtest/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "simplex-regtest" +version = "0.1.0" +license.workspace = true +edition.workspace = true + +[lints] +workspace = true + +[dependencies] +simplex-sdk = { workspace = true } + +thiserror = { workspace = true } +simplicityhl = { workspace = true } +electrsd = { workspace = true } +serde = { workspace = true } +toml = { workspace = true } diff --git a/crates/regtest/src/args.rs b/crates/regtest/src/args.rs new file mode 100644 index 0000000..8469f8c --- /dev/null +++ b/crates/regtest/src/args.rs @@ -0,0 +1,23 @@ +pub const DEFAULT_SAT_AMOUNT_FAUCET: u64 = 100000; + +pub fn get_elementsd_bin_args() -> Vec { + vec![ + "-fallbackfee=0.0001".to_string(), + "-dustrelayfee=0.00000001".to_string(), + "-acceptdiscountct=1".to_string(), + "-rest".to_string(), + "-evbparams=simplicity:-1:::".to_string(), + "-minrelaytxfee=0".to_string(), + "-blockmintxfee=0".to_string(), + "-chain=liquidregtest".to_string(), + "-txindex=1".to_string(), + "-validatepegin=0".to_string(), + "-initialfreecoins=2100000000000000".to_string(), + "-listen=1".to_string(), + "-txindex=1".to_string(), + ] +} + +pub fn get_electrs_bin_args() -> Vec { + vec!["-v".to_string()] +} diff --git a/crates/regtest/src/client.rs b/crates/regtest/src/client.rs new file mode 100644 index 0000000..3beae5a --- /dev/null +++ b/crates/regtest/src/client.rs @@ -0,0 +1,83 @@ +use std::path::{Path, PathBuf}; + +use electrsd::ElectrsD; +use electrsd::bitcoind; +use electrsd::bitcoind::bitcoincore_rpc::Auth; +use electrsd::bitcoind::{BitcoinD, Conf}; + +use super::error::ClientError; +use crate::args::{get_electrs_bin_args, get_elementsd_bin_args}; + +pub struct TestClient { + pub electrs: ElectrsD, + pub elements: BitcoinD, +} + +impl TestClient { + // TODO: pass custom config + pub fn new() -> Self { + let (electrs_path, elementsd_path) = Self::default_bin_paths(); + let elements = Self::create_bitcoind_node(elementsd_path); + let electrs = Self::create_electrs_node(electrs_path, &elements); + + Self { + electrs: electrs, + elements: elements, + } + } + + pub fn default_bin_paths() -> (PathBuf, PathBuf) { + // TODO: change binary into installed one in $PATH dir + const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); + const ELEMENTSD_BIN_PATH: &str = "../../assets/elementsd"; + const ELECTRS_BIN_PATH: &str = "../../assets/electrs"; + + ( + Path::new(MANIFEST_DIR).join(ELECTRS_BIN_PATH), + Path::new(MANIFEST_DIR).join(ELEMENTSD_BIN_PATH), + ) + } + + pub fn rpc_url(&self) -> String { + self.elements.rpc_url() + } + + pub fn esplora_url(&self) -> String { + self.electrs.esplora_url.clone().unwrap() + } + + pub fn auth(&self) -> Auth { + let cookie = self.elements.params.get_cookie_values().unwrap().unwrap(); + + Auth::UserPass(cookie.user, cookie.password) + } + + pub fn kill(&mut self) -> Result<(), ClientError> { + self.electrs.kill().map_err(|_| ClientError::ElectrsTermination())?; + self.elements.stop().map_err(|_| ClientError::ElementsTermination())?; + + Ok(()) + } + + fn create_bitcoind_node(bin_path: impl AsRef) -> BitcoinD { + let mut conf = Conf::default(); + let bin_args = get_elementsd_bin_args(); + + conf.args = bin_args.iter().map(|x| x.as_ref()).collect::>(); + conf.network = "liquidregtest"; + conf.p2p = bitcoind::P2P::Yes; + + BitcoinD::with_conf(bin_path.as_ref(), &conf).unwrap() + } + + fn create_electrs_node(bin_path: impl AsRef, elementsd: &BitcoinD) -> ElectrsD { + let mut conf = electrsd::Conf::default(); + let bin_args = get_electrs_bin_args(); + + conf.args = bin_args.iter().map(|x| x.as_ref()).collect::>(); + conf.http_enabled = true; + conf.network = "liquidregtest"; + + ElectrsD::with_conf(bin_path.as_ref(), &elementsd, &conf).unwrap() + } +} diff --git a/crates/regtest/src/error.rs b/crates/regtest/src/error.rs new file mode 100644 index 0000000..966185e --- /dev/null +++ b/crates/regtest/src/error.rs @@ -0,0 +1,8 @@ +#[derive(thiserror::Error, Debug)] +pub enum ClientError { + #[error("Failed to terminate elements")] + ElementsTermination(), + + #[error("Failed to terminate electrs")] + ElectrsTermination(), +} diff --git a/crates/regtest/src/lib.rs b/crates/regtest/src/lib.rs new file mode 100644 index 0000000..7a0cd31 --- /dev/null +++ b/crates/regtest/src/lib.rs @@ -0,0 +1,5 @@ +mod args; +pub mod client; +pub mod error; + +pub use client::TestClient; diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 9880bed..62f9067 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -10,16 +10,17 @@ readme = "README.md" workspace = true [dependencies] -simplex-provider = { workspace = true } - async-trait = { workspace = true } thiserror = { workspace = true } sha2 = { workspace = true } minreq = { workspace = true } simplicityhl = { workspace = true } +electrsd = { workspace = true } hex = { version = "0.4" } -serde = { version = "1.0.228", features = ["derive"]} +serde = { version = "1.0.228", features = ["derive"] } +serde_json = { version = "1.0" } elements-miniscript = { version = "0.4", features = ["base64", "serde"] } bip39 = { version = "2.0.0", features = ["rand"] } dyn-clone = { version = "1.0.20" } +bitcoin_hashes = { version = "0.14.1" } diff --git a/crates/sdk/src/constants.rs b/crates/sdk/src/constants.rs index 01aab2d..b9c3b45 100644 --- a/crates/sdk/src/constants.rs +++ b/crates/sdk/src/constants.rs @@ -1,17 +1,7 @@ -use simplicityhl::simplicity::elements; -use simplicityhl::simplicity::hashes::{Hash, sha256}; - -use std::str::FromStr; - pub const PUBLIC_SECRET_BLINDER_KEY: [u8; 32] = [1; 32]; pub const DUMMY_SIGNATURE: [u8; 64] = [1; 64]; -pub const DEFAULT_TARGET_BLOCKS: u32 = 0; -pub const DEFAULT_FEE_RATE: f32 = 100.0; pub const MIN_FEE: u64 = 10; -pub const PLACEHOLDER_FEE: u64 = 1; - -pub const WITNESS_SCALE_FACTOR: usize = 4; /// Policy asset id (hex, BE) for Liquid mainnet. pub const LIQUID_POLICY_ASSET_STR: &str = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d"; @@ -23,74 +13,4 @@ pub const LIQUID_TESTNET_POLICY_ASSET_STR: &str = "144c654344aa716d6f3abcc1ca90e pub const LIQUID_DEFAULT_REGTEST_ASSET_STR: &str = "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225"; /// Example test asset id (hex, BE) on Liquid testnet. -pub static LIQUID_TESTNET_TEST_ASSET_ID_STR: &str = "38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5"; - -pub static LIQUID_TESTNET_BITCOIN_ASSET: std::sync::LazyLock = std::sync::LazyLock::new(|| { - elements::AssetId::from_inner(sha256::Midstate([ - 0x49, 0x9a, 0x81, 0x85, 0x45, 0xf6, 0xba, 0xe3, 0x9f, 0xc0, 0x3b, 0x63, 0x7f, 0x2a, 0x4e, 0x1e, 0x64, 0xe5, - 0x90, 0xca, 0xc1, 0xbc, 0x3a, 0x6f, 0x6d, 0x71, 0xaa, 0x44, 0x43, 0x65, 0x4c, 0x14, - ])) -}); - -pub static LIQUID_MAINNET_GENESIS: std::sync::LazyLock = std::sync::LazyLock::new(|| { - elements::BlockHash::from_byte_array([ - 0x03, 0x60, 0x20, 0x8a, 0x88, 0x96, 0x92, 0x37, 0x2c, 0x8d, 0x68, 0xb0, 0x84, 0xa6, 0x2e, 0xfd, 0xf6, 0x0e, - 0xa1, 0xa3, 0x59, 0xa0, 0x4c, 0x94, 0xb2, 0x0d, 0x22, 0x36, 0x58, 0x27, 0x66, 0x14, - ]) -}); - -pub static LIQUID_TESTNET_GENESIS: std::sync::LazyLock = std::sync::LazyLock::new(|| { - elements::BlockHash::from_byte_array([ - 0xc1, 0xb1, 0x6a, 0xe2, 0x4f, 0x24, 0x23, 0xae, 0xa2, 0xea, 0x34, 0x55, 0x22, 0x92, 0x79, 0x3b, 0x5b, 0x5e, - 0x82, 0x99, 0x9a, 0x1e, 0xed, 0x81, 0xd5, 0x6a, 0xee, 0x52, 0x8e, 0xda, 0x71, 0xa7, - ]) -}); - -pub static LIQUID_REGTEST_GENESIS: std::sync::LazyLock = std::sync::LazyLock::new(|| { - elements::BlockHash::from_byte_array([ - 0x21, 0xca, 0xb1, 0xe5, 0xda, 0x47, 0x18, 0xea, 0x14, 0x0d, 0x97, 0x16, 0x93, 0x17, 0x02, 0x42, 0x2f, 0x0e, - 0x6a, 0xd9, 0x15, 0xc8, 0xd9, 0xb5, 0x83, 0xca, 0xc2, 0x70, 0x6b, 0x2a, 0x90, 0x00, - ]) -}); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum SimplicityNetwork { - Liquid, - LiquidTestnet, - ElementsRegtest { policy_asset: elements::AssetId }, -} - -impl SimplicityNetwork { - pub fn default_regtest() -> Self { - let policy_asset = elements::AssetId::from_str(LIQUID_DEFAULT_REGTEST_ASSET_STR).unwrap(); - Self::ElementsRegtest { policy_asset } - } - - pub fn policy_asset(&self) -> elements::AssetId { - match self { - Self::Liquid => elements::AssetId::from_str(LIQUID_POLICY_ASSET_STR).unwrap(), - Self::LiquidTestnet => elements::AssetId::from_str(LIQUID_TESTNET_POLICY_ASSET_STR).unwrap(), - Self::ElementsRegtest { policy_asset } => *policy_asset, - } - } - - pub fn genesis_block_hash(&self) -> elements::BlockHash { - match self { - Self::Liquid => *LIQUID_MAINNET_GENESIS, - Self::LiquidTestnet => *LIQUID_TESTNET_GENESIS, - Self::ElementsRegtest { .. } => *LIQUID_REGTEST_GENESIS, - } - } - - pub fn is_mainnet(&self) -> bool { - self == &Self::Liquid - } - - pub const fn address_params(&self) -> &'static elements::AddressParams { - match self { - Self::Liquid => &elements::AddressParams::LIQUID, - Self::LiquidTestnet => &elements::AddressParams::LIQUID_TESTNET, - Self::ElementsRegtest { .. } => &elements::AddressParams::ELEMENTS, - } - } -} +pub const LIQUID_TESTNET_TEST_ASSET_ID_STR: &str = "38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5"; diff --git a/crates/sdk/src/presets/simf/p2pk.simf b/crates/sdk/src/presets/simf/p2pk.simf index db4f27c..f6a75e6 100644 --- a/crates/sdk/src/presets/simf/p2pk.simf +++ b/crates/sdk/src/presets/simf/p2pk.simf @@ -1,3 +1,3 @@ fn main() { jet::bip_0340_verify((param::PUBLIC_KEY, jet::sig_all_hash()), witness::SIGNATURE) -} \ No newline at end of file +} diff --git a/crates/sdk/src/program/program.rs b/crates/sdk/src/program/program.rs index 6ccc521..da9c97c 100644 --- a/crates/sdk/src/program/program.rs +++ b/crates/sdk/src/program/program.rs @@ -16,7 +16,7 @@ use simplicityhl::tracker::{DefaultTracker, TrackerLogLevel}; use super::arguments::ArgumentsTrait; use super::error::ProgramError; -use crate::constants::SimplicityNetwork; +use crate::provider::SimplicityNetwork; pub trait ProgramTrait: DynClone { fn get_env( diff --git a/crates/sdk/src/provider/error.rs b/crates/sdk/src/provider/error.rs index 81c9011..40a994b 100644 --- a/crates/sdk/src/provider/error.rs +++ b/crates/sdk/src/provider/error.rs @@ -1,5 +1,10 @@ +use crate::provider::rpc::error::RpcError; + #[derive(Debug, thiserror::Error)] pub enum ProviderError { + #[error(transparent)] + Rpc(#[from] RpcError), + #[error("HTTP request failed: {0}")] Request(String), diff --git a/crates/sdk/src/provider/esplora.rs b/crates/sdk/src/provider/esplora.rs index e6f2b1e..e08becd 100644 --- a/crates/sdk/src/provider/esplora.rs +++ b/crates/sdk/src/provider/esplora.rs @@ -11,14 +11,12 @@ use simplicityhl::elements::{Address, OutPoint, Script, Transaction, TxOut, Txid use serde::Deserialize; -pub use simplex_provider::esplora::*; - use super::error::ProviderError; -use super::provider::ProviderTrait; +use super::provider::{DEFAULT_TIMEOUT_SECS, ProviderTrait}; -#[derive(Clone)] pub struct EsploraProvider { - esplora_url: String, + pub esplora_url: String, + pub timeout: Duration, } #[derive(Deserialize)] @@ -59,7 +57,10 @@ struct EsploraUtxo { impl EsploraProvider { pub fn new(url: String) -> Self { - Self { esplora_url: url } + Self { + esplora_url: url, + timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECS), + } } fn esplora_utxo_to_outpoint(&self, utxo: &EsploraUtxo) -> Result { @@ -98,8 +99,10 @@ impl ProviderTrait for EsploraProvider { fn broadcast_transaction(&self, tx: &Transaction) -> Result { let tx_hex = encode::serialize_hex(tx); let url = format!("{}/tx", self.esplora_url); + let timeout_secs = self.timeout.as_secs(); let response = minreq::post(&url) + .with_timeout(timeout_secs) .with_body(tx_hex) .send() .map_err(|e| ProviderError::Request(e.to_string()))?; @@ -119,10 +122,12 @@ impl ProviderTrait for EsploraProvider { } fn wait(&self, txid: &Txid) -> Result<(), ProviderError> { - let status_url = format!("{}/tx/{}/status", self.esplora_url, txid); + let url = format!("{}/tx/{}/status", self.esplora_url, txid); + let timeout_secs = self.timeout.as_secs(); for _ in 1..10 { - let response = minreq::get(&status_url) + let response = minreq::get(&url) + .with_timeout(timeout_secs) .send() .map_err(|e| ProviderError::Request(e.to_string()))?; @@ -145,7 +150,10 @@ impl ProviderTrait for EsploraProvider { fn fetch_transaction(&self, txid: &Txid) -> Result { let url = format!("{}/tx/{}/raw", self.esplora_url, txid); + let timeout_secs = self.timeout.as_secs(); + let response = minreq::get(&url) + .with_timeout(timeout_secs) .send() .map_err(|e| ProviderError::Request(e.to_string()))?; @@ -164,7 +172,10 @@ impl ProviderTrait for EsploraProvider { fn fetch_address_utxos(&self, address: &Address) -> Result, ProviderError> { let url = format!("{}/address/{}/utxo", self.esplora_url, address); + let timeout_secs = self.timeout.as_secs(); + let response = minreq::get(&url) + .with_timeout(timeout_secs) .send() .map_err(|e| ProviderError::Request(e.to_string()))?; @@ -190,7 +201,10 @@ impl ProviderTrait for EsploraProvider { let scripthash = hex::encode(hash_bytes); let url = format!("{}/scripthash/{}/utxo", self.esplora_url, scripthash); + let timeout_secs = self.timeout.as_secs(); + let response = minreq::get(&url) + .with_timeout(timeout_secs) .send() .map_err(|e| ProviderError::Request(e.to_string()))?; @@ -212,7 +226,10 @@ impl ProviderTrait for EsploraProvider { fn fetch_fee_estimates(&self) -> Result, ProviderError> { let url = format!("{}/fee-estimates", self.esplora_url); + let timeout_secs = self.timeout.as_secs(); + let response = minreq::get(&url) + .with_timeout(timeout_secs) .send() .map_err(|e| ProviderError::Request(e.to_string()))?; diff --git a/crates/sdk/src/provider/mod.rs b/crates/sdk/src/provider/mod.rs index f929382..916b68e 100644 --- a/crates/sdk/src/provider/mod.rs +++ b/crates/sdk/src/provider/mod.rs @@ -1,7 +1,12 @@ pub mod error; pub mod esplora; +pub mod network; pub mod provider; +pub(crate) mod rpc; +pub mod simplex; pub use error::ProviderError; pub use esplora::EsploraProvider; +pub use network::*; pub use provider::ProviderTrait; +pub use rpc::elements::ElementsRpc; diff --git a/crates/sdk/src/provider/network.rs b/crates/sdk/src/provider/network.rs new file mode 100644 index 0000000..c7e3cd3 --- /dev/null +++ b/crates/sdk/src/provider/network.rs @@ -0,0 +1,77 @@ + +use std::str::FromStr; + +use simplicityhl::simplicity::elements; +use simplicityhl::simplicity::hashes::{Hash, sha256}; + +use crate::constants::{LIQUID_DEFAULT_REGTEST_ASSET_STR, LIQUID_POLICY_ASSET_STR, LIQUID_TESTNET_POLICY_ASSET_STR}; + +pub static LIQUID_TESTNET_BITCOIN_ASSET: std::sync::LazyLock = std::sync::LazyLock::new(|| { + elements::AssetId::from_inner(sha256::Midstate([ + 0x49, 0x9a, 0x81, 0x85, 0x45, 0xf6, 0xba, 0xe3, 0x9f, 0xc0, 0x3b, 0x63, 0x7f, 0x2a, 0x4e, 0x1e, 0x64, 0xe5, + 0x90, 0xca, 0xc1, 0xbc, 0x3a, 0x6f, 0x6d, 0x71, 0xaa, 0x44, 0x43, 0x65, 0x4c, 0x14, + ])) +}); + +pub static LIQUID_MAINNET_GENESIS: std::sync::LazyLock = std::sync::LazyLock::new(|| { + elements::BlockHash::from_byte_array([ + 0x03, 0x60, 0x20, 0x8a, 0x88, 0x96, 0x92, 0x37, 0x2c, 0x8d, 0x68, 0xb0, 0x84, 0xa6, 0x2e, 0xfd, 0xf6, 0x0e, + 0xa1, 0xa3, 0x59, 0xa0, 0x4c, 0x94, 0xb2, 0x0d, 0x22, 0x36, 0x58, 0x27, 0x66, 0x14, + ]) +}); + +pub static LIQUID_TESTNET_GENESIS: std::sync::LazyLock = std::sync::LazyLock::new(|| { + elements::BlockHash::from_byte_array([ + 0xc1, 0xb1, 0x6a, 0xe2, 0x4f, 0x24, 0x23, 0xae, 0xa2, 0xea, 0x34, 0x55, 0x22, 0x92, 0x79, 0x3b, 0x5b, 0x5e, + 0x82, 0x99, 0x9a, 0x1e, 0xed, 0x81, 0xd5, 0x6a, 0xee, 0x52, 0x8e, 0xda, 0x71, 0xa7, + ]) +}); + +pub static LIQUID_REGTEST_GENESIS: std::sync::LazyLock = std::sync::LazyLock::new(|| { + elements::BlockHash::from_byte_array([ + 0x21, 0xca, 0xb1, 0xe5, 0xda, 0x47, 0x18, 0xea, 0x14, 0x0d, 0x97, 0x16, 0x93, 0x17, 0x02, 0x42, 0x2f, 0x0e, + 0x6a, 0xd9, 0x15, 0xc8, 0xd9, 0xb5, 0x83, 0xca, 0xc2, 0x70, 0x6b, 0x2a, 0x90, 0x00, + ]) +}); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SimplicityNetwork { + Liquid, + LiquidTestnet, + ElementsRegtest { policy_asset: elements::AssetId }, +} + +impl SimplicityNetwork { + pub fn default_regtest() -> Self { + let policy_asset = elements::AssetId::from_str(LIQUID_DEFAULT_REGTEST_ASSET_STR).unwrap(); + Self::ElementsRegtest { policy_asset } + } + + pub fn policy_asset(&self) -> elements::AssetId { + match self { + Self::Liquid => elements::AssetId::from_str(LIQUID_POLICY_ASSET_STR).unwrap(), + Self::LiquidTestnet => elements::AssetId::from_str(LIQUID_TESTNET_POLICY_ASSET_STR).unwrap(), + Self::ElementsRegtest { policy_asset } => *policy_asset, + } + } + + pub fn genesis_block_hash(&self) -> elements::BlockHash { + match self { + Self::Liquid => *LIQUID_MAINNET_GENESIS, + Self::LiquidTestnet => *LIQUID_TESTNET_GENESIS, + Self::ElementsRegtest { .. } => *LIQUID_REGTEST_GENESIS, + } + } + + pub fn is_mainnet(&self) -> bool { + self == &Self::Liquid + } + + pub const fn address_params(&self) -> &'static elements::AddressParams { + match self { + Self::Liquid => &elements::AddressParams::LIQUID, + Self::LiquidTestnet => &elements::AddressParams::LIQUID_TESTNET, + Self::ElementsRegtest { .. } => &elements::AddressParams::ELEMENTS, + } + } +} diff --git a/crates/sdk/src/provider/provider.rs b/crates/sdk/src/provider/provider.rs index ca0a92c..dcc79d1 100644 --- a/crates/sdk/src/provider/provider.rs +++ b/crates/sdk/src/provider/provider.rs @@ -3,7 +3,9 @@ use std::collections::HashMap; use simplicityhl::elements::{Address, OutPoint, Script, Transaction, TxOut, Txid}; use super::error::ProviderError; -use crate::constants::DEFAULT_FEE_RATE; + +pub const DEFAULT_FEE_RATE: f32 = 100.0; +pub const DEFAULT_TIMEOUT_SECS: u64 = 10; pub trait ProviderTrait { fn broadcast_transaction(&self, tx: &Transaction) -> Result; diff --git a/crates/sdk/src/provider/rpc/elements.rs b/crates/sdk/src/provider/rpc/elements.rs new file mode 100644 index 0000000..3b4dc91 --- /dev/null +++ b/crates/sdk/src/provider/rpc/elements.rs @@ -0,0 +1,129 @@ +use std::str::FromStr; + +use bitcoind::bitcoincore_rpc::{Auth, Client, RpcApi}; +use electrsd::bitcoind; + +use serde_json::Value; + +use simplicityhl::elements::{Address, AssetId, BlockHash, Txid}; + +use super::error::RpcError; + +use crate::utils::sat2btc; + +pub struct ElementsRpc { + inner: Client, + auth: Auth, + pub url: String, +} + +impl ElementsRpc { + pub fn new(url: String, auth: Auth) -> Result { + let inner = Client::new(url.as_str(), auth.clone())?; + inner.ping()?; + + Ok(Self { + inner: inner, + auth: auth, + url: url, + }) + } + + pub fn height(&self) -> Result { + const METHOD: &str = "getblockcount"; + + self.inner + .call::(METHOD, &[])? + .as_u64() + .ok_or_else(|| RpcError::ElementsRpcUnexpectedReturn(METHOD.into())) + } + + pub fn block_hash(&self, height: u64) -> Result { + const METHOD: &str = "getblockhash"; + + let raw: Value = self.inner.call(METHOD, &[height.into()])?; + + Ok(BlockHash::from_str(raw.as_str().unwrap())?) + } + + pub fn sendtoaddress(&self, address: &Address, satoshi: u64, asset: Option) -> Result { + const METHOD: &str = "sendtoaddress"; + + let btc = sat2btc(satoshi); + let r = match asset { + Some(asset) => self.inner.call::( + METHOD, + &[ + address.to_string().into(), + btc.into(), + "".into(), + "".into(), + false.into(), + false.into(), + 1.into(), + "UNSET".into(), + false.into(), + asset.to_string().into(), + ], + )?, + None => self + .inner + .call::(METHOD, &[address.to_string().into(), btc.into()])?, + }; + + Ok(Txid::from_str(r.as_str().unwrap()).unwrap()) + } + + pub fn rescanblockchain(&self, start: Option, stop: Option) -> Result<(), RpcError> { + const METHOD: &str = "rescanblockchain"; + + let mut args = Vec::with_capacity(2); + + if start.is_some() { + args.push(start.into()); + } + + if stop.is_some() { + args.push(stop.into()); + } + + self.inner.call::(METHOD, &args)?; + + Ok(()) + } + + pub fn getnewaddress(&self, label: &str) -> Result { + const METHOD: &str = "getnewaddress"; + + let addr: Value = self.inner.call(METHOD, &[label.into(), "bech32".to_string().into()])?; + + Ok(Address::from_str(addr.as_str().unwrap()).unwrap()) + } + + pub fn generate_blocks(&self, block_num: u32) -> Result<(), RpcError> { + const METHOD: &str = "generatetoaddress"; + + let address = self.getnewaddress("")?.to_string(); + self.inner.call::(METHOD, &[block_num.into(), address.into()])?; + + Ok(()) + } + + pub fn sweep_initialfreecoins(&self) -> Result<(), RpcError> { + const METHOD: &str = "sendtoaddress"; + + let address = self.getnewaddress("")?; + self.inner.call::( + METHOD, + &[ + address.to_string().into(), + "21".into(), + "".into(), + "".into(), + true.into(), + ], + )?; + + Ok(()) + } +} diff --git a/crates/sdk/src/provider/rpc/error.rs b/crates/sdk/src/provider/rpc/error.rs new file mode 100644 index 0000000..0d76c06 --- /dev/null +++ b/crates/sdk/src/provider/rpc/error.rs @@ -0,0 +1,11 @@ +#[derive(thiserror::Error, Debug)] +pub enum RpcError { + #[error(transparent)] + ElementsRpcError(#[from] electrsd::bitcoind::bitcoincore_rpc::Error), + + #[error("Elements RPC returned an unexpected value for call {0}")] + ElementsRpcUnexpectedReturn(String), + + #[error("Failed to decode hex value to array, {0}")] + BitcoinHashesHex(#[from] bitcoin_hashes::hex::HexToArrayError), +} diff --git a/crates/sdk/src/provider/rpc/mod.rs b/crates/sdk/src/provider/rpc/mod.rs new file mode 100644 index 0000000..9201cc8 --- /dev/null +++ b/crates/sdk/src/provider/rpc/mod.rs @@ -0,0 +1,2 @@ +pub mod elements; +pub mod error; diff --git a/crates/sdk/src/provider/simplex.rs b/crates/sdk/src/provider/simplex.rs new file mode 100644 index 0000000..97af6a9 --- /dev/null +++ b/crates/sdk/src/provider/simplex.rs @@ -0,0 +1,54 @@ +use std::collections::HashMap; + +use bitcoind::bitcoincore_rpc::Auth; +use electrsd::bitcoind; + +use simplicityhl::elements::{Address, OutPoint, Script, Transaction, TxOut, Txid}; + +use crate::provider::{ProviderError, ProviderTrait}; + +use super::{ElementsRpc, EsploraProvider}; + +pub struct SimplexProvider { + pub esplora: EsploraProvider, + pub elements: ElementsRpc, +} + +impl SimplexProvider { + pub fn new(esplora_url: String, elements_url: String, auth: Auth) -> Result { + Ok(Self { + esplora: EsploraProvider::new(esplora_url), + elements: ElementsRpc::new(elements_url, auth)?, + }) + } +} + +impl ProviderTrait for SimplexProvider { + fn broadcast_transaction(&self, tx: &Transaction) -> Result { + let txid = self.esplora.broadcast_transaction(tx)?; + + self.elements.generate_blocks(1)?; + + Ok(txid) + } + + fn wait(&self, txid: &Txid) -> Result<(), ProviderError> { + Ok(self.esplora.wait(txid)?) + } + + fn fetch_transaction(&self, txid: &Txid) -> Result { + Ok(self.esplora.fetch_transaction(txid)?) + } + + fn fetch_address_utxos(&self, address: &Address) -> Result, ProviderError> { + Ok(self.esplora.fetch_address_utxos(address)?) + } + + fn fetch_scripthash_utxos(&self, script: &Script) -> Result, ProviderError> { + Ok(self.esplora.fetch_scripthash_utxos(script)?) + } + + fn fetch_fee_estimates(&self) -> Result, ProviderError> { + Ok(self.esplora.fetch_fee_estimates()?) + } +} diff --git a/crates/sdk/src/signer/signer.rs b/crates/sdk/src/signer/signer.rs index a485dd8..fe0d317 100644 --- a/crates/sdk/src/signer/signer.rs +++ b/crates/sdk/src/signer/signer.rs @@ -29,14 +29,17 @@ use elements_miniscript::{ }; use super::error::SignerError; -use crate::constants::{MIN_FEE, PLACEHOLDER_FEE, SimplicityNetwork}; +use crate::constants::MIN_FEE; use crate::program::ProgramTrait; use crate::provider::ProviderTrait; +use crate::provider::SimplicityNetwork; use crate::transaction::FinalTransaction; use crate::transaction::PartialInput; use crate::transaction::PartialOutput; use crate::transaction::RequiredSignature; +pub const PLACEHOLDER_FEE: u64 = 1; + pub trait SignerTrait { fn sign_program( &self, @@ -53,14 +56,14 @@ pub trait SignerTrait { ) -> Result<(PublicKey, ecdsa::Signature), SignerError>; } -pub struct Signer { +pub struct Signer<'a> { xprv: Xpriv, - provider: Box, + provider: Box<&'a dyn ProviderTrait>, network: SimplicityNetwork, secp: Secp256k1, } -impl SignerTrait for Signer { +impl<'a> SignerTrait for Signer<'a> { fn sign_program( &self, pst: &PartiallySignedTransaction, @@ -105,10 +108,10 @@ enum Estimate { Failure(u64), } -impl Signer { +impl<'a> Signer<'a> { pub fn new( mnemonic: &str, - provider: Box, + provider: &'a impl ProviderTrait, network: SimplicityNetwork, ) -> Result { let secp = Secp256k1::new(); @@ -121,7 +124,7 @@ impl Signer { Ok(Self { xprv, - provider: provider, + provider: Box::new(provider), network: network, secp: secp, }) @@ -386,7 +389,7 @@ mod tests { let provider = EsploraProvider::new("https://blockstream.info/liquidtestnet/api".to_string()); let signer = Signer::new( "exist carry drive collect lend cereal occur much tiger just involve mean", - Box::new(provider.clone()), + &provider, SimplicityNetwork::LiquidTestnet, ) .unwrap(); diff --git a/crates/sdk/src/transaction/final_transaction.rs b/crates/sdk/src/transaction/final_transaction.rs index 9215a8d..968b94e 100644 --- a/crates/sdk/src/transaction/final_transaction.rs +++ b/crates/sdk/src/transaction/final_transaction.rs @@ -1,15 +1,20 @@ +use simplicityhl::elements::AssetId; use simplicityhl::elements::pset::PartiallySignedTransaction; -use crate::constants::{SimplicityNetwork, WITNESS_SCALE_FACTOR}; +use crate::provider::SimplicityNetwork; +use crate::utils::asset_entropy; use super::error::TransactionError; -use super::partial_input::{PartialInput, ProgramInput, RequiredSignature}; +use super::partial_input::{IssuanceInput, PartialInput, ProgramInput, RequiredSignature}; use super::partial_output::PartialOutput; +pub const WITNESS_SCALE_FACTOR: usize = 4; + #[derive(Clone)] pub struct FinalInput { pub partial_input: PartialInput, pub program_input: Option, + pub issuance_input: Option, pub required_sig: RequiredSignature, } @@ -29,7 +34,6 @@ impl FinalTransaction { } } - // TODO: require required_sig != Witness(String) pub fn add_input( &mut self, partial_input: PartialInput, @@ -47,12 +51,40 @@ impl FinalTransaction { self.inputs.push(FinalInput { partial_input: partial_input, program_input: None, + issuance_input: None, required_sig: required_sig, }); Ok(()) } + pub fn add_issuance_input( + &mut self, + partial_input: PartialInput, + issuance_input: IssuanceInput, + required_sig: RequiredSignature, + ) -> Result { + match required_sig { + RequiredSignature::Witness(_) => { + return Err(TransactionError::SignatureRequest( + "Requested signature is not NativeEcdsa or None".to_string(), + )); + } + _ => {} + } + + let asset_id = AssetId::from_entropy(asset_entropy(&partial_input.outpoint(), issuance_input.asset_entropy)); + + self.inputs.push(FinalInput { + partial_input: partial_input, + program_input: None, + issuance_input: Some(issuance_input), + required_sig: required_sig, + }); + + Ok(asset_id) + } + pub fn add_program_input( &mut self, partial_input: PartialInput, @@ -71,6 +103,7 @@ impl FinalTransaction { self.inputs.push(FinalInput { partial_input: partial_input, program_input: Some(program_input), + issuance_input: None, required_sig: required_sig, }); @@ -147,7 +180,19 @@ impl FinalTransaction { let mut pst = PartiallySignedTransaction::new_v2(); self.inputs.iter().for_each(|el| { - pst.add_input(el.partial_input.to_input()); + let mut input = el.partial_input.input(); + + // populate the input manually since `input.merge` is private + if el.issuance_input.is_some() { + let issue = el.issuance_input.clone().unwrap().input(); + + input.issuance_value_amount = issue.issuance_value_amount; + input.issuance_asset_entropy = issue.issuance_asset_entropy; + input.issuance_inflation_keys = issue.issuance_inflation_keys; + input.blinded_issuance = issue.blinded_issuance; + } + + pst.add_input(input); }); self.outputs.iter().for_each(|el| { diff --git a/crates/sdk/src/transaction/partial_input.rs b/crates/sdk/src/transaction/partial_input.rs index ef45e8d..f4e54a7 100644 --- a/crates/sdk/src/transaction/partial_input.rs +++ b/crates/sdk/src/transaction/partial_input.rs @@ -28,6 +28,12 @@ pub struct ProgramInput { pub witness: Box, } +#[derive(Clone)] +pub struct IssuanceInput { + pub issuance_amount: u64, + pub asset_entropy: [u8; 32], +} + impl PartialInput { pub fn new(outpoint: OutPoint, txout: TxOut) -> Self { let amount = match txout.value { @@ -49,7 +55,14 @@ impl PartialInput { } } - pub fn to_input(&self) -> Input { + pub fn outpoint(&self) -> OutPoint { + OutPoint { + txid: self.witness_txid.clone(), + vout: self.witness_output_index, + } + } + + pub fn input(&self) -> Input { let mut input = Input::default(); input.previous_txid = self.witness_txid.clone(); @@ -71,3 +84,23 @@ impl ProgramInput { } } } + +impl IssuanceInput { + pub fn new(issuance_amount: u64, asset_entropy: [u8; 32]) -> Self { + Self { + issuance_amount: issuance_amount, + asset_entropy: asset_entropy, + } + } + + pub fn input(&self) -> Input { + let mut input = Input::default(); + + input.issuance_value_amount = Some(self.issuance_amount); + input.issuance_asset_entropy = Some(self.asset_entropy); + input.issuance_inflation_keys = None; + input.blinded_issuance = Some(0x00); + + input + } +} diff --git a/crates/sdk/src/utils.rs b/crates/sdk/src/utils.rs index d1577c8..0175f08 100644 --- a/crates/sdk/src/utils.rs +++ b/crates/sdk/src/utils.rs @@ -1,5 +1,9 @@ +use simplicityhl::simplicity::bitcoin; use simplicityhl::simplicity::bitcoin::secp256k1; +use simplicityhl::elements::{AssetId, ContractHash, OutPoint}; +use simplicityhl::simplicity::hashes::{Hash, sha256}; + pub fn tr_unspendable_key() -> secp256k1::XOnlyPublicKey { secp256k1::XOnlyPublicKey::from_slice(&[ 0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e, 0x07, 0x8a, @@ -7,3 +11,13 @@ pub fn tr_unspendable_key() -> secp256k1::XOnlyPublicKey { ]) .expect("key should be valid") } + +pub fn asset_entropy(outpoint: &OutPoint, entropy: [u8; 32]) -> sha256::Midstate { + let contract_hash = ContractHash::from_byte_array(entropy); + AssetId::generate_asset_entropy(*outpoint, contract_hash) +} + +pub fn sat2btc(sat: u64) -> String { + let amount = bitcoin::Amount::from_sat(sat); + amount.to_string_in(bitcoin::amount::Denomination::Bitcoin) +} diff --git a/crates/simplex/Cargo.toml b/crates/simplex/Cargo.toml index f7ac2c7..2ef6f72 100644 --- a/crates/simplex/Cargo.toml +++ b/crates/simplex/Cargo.toml @@ -23,7 +23,6 @@ sdk = ["dep:simplex-sdk"] [dependencies] simplex-macros = { workspace = true, features = [], optional = true } simplex-test = { workspace = true } -simplex-provider = { workspace = true } simplex-sdk = { workspace = true, optional = true } bincode = { workspace = true, optional = true } diff --git a/crates/simplex/tests/simplex_test.rs b/crates/simplex/tests/simplex_test.rs index fb2608e..72a51e3 100644 --- a/crates/simplex/tests/simplex_test.rs +++ b/crates/simplex/tests/simplex_test.rs @@ -1,160 +1,160 @@ -use simplex_provider::elements_rpc::{AddressType, ElementsRpcClient}; -use simplex_test::{DEFAULT_SAT_AMOUNT_FAUCET, ElementsDConf, TestContext}; -use simplicityhl::elements::Address; -use simplicityhl::elements::bitcoin::secp256k1; -use simplicityhl::elements::secp256k1_zkp::Keypair; - -use simplex::simplex_sdk::constants::SimplicityNetwork; -use simplex::simplex_sdk::presets::{P2PK, P2PKArguments}; -use simplex::simplex_sdk::utils::tr_unspendable_key; - -#[ignore] -#[simplex::simplex_macros::test(default_rpc)] -fn test_execution(x: TestContext) { - assert!(true) -} - -#[ignore] -#[simplex::simplex_macros::test] -fn test_execution3(x: TestContext) { - assert!(true) -} - -#[ignore] -#[test] -fn test_execution2() { - use ::simplex::tracing; - use simplex_test::TestContextBuilder; - use std::path::PathBuf; - - fn test_execution2(x: TestContext) { - assert!(true); - } - - let test_context = match std::env::var("SIMPLEX_TEST_ENV") { - Err(e) => { - tracing::trace!( - "Test 'test_in_custom_folder_custom_333' connected with simplex is disabled, run `simplex test` in order to test it, err: '{e}'" - ); - panic!("Failed to run this test, required to use `simplex test`"); - } - Ok(path) => { - let path = PathBuf::from(path); - let test_context = TestContextBuilder::FromConfigPath(path).build().unwrap(); - tracing::trace!("Running 'test_in_custom_folder_custom_333' with simplex configuration"); - test_context - } - }; - println!("fn name: {}, \n ident: {}", "test_execution2", "#ident"); - println!("input: {}, \n AttributeArgs: {}", "#input", "#args"); - - test_execution2(test_context) -} - -#[test] -fn test_invocation_tx_tracking() -> anyhow::Result<()> { - use simplex_test::{ConfigOption, TestClientProvider}; - - fn test_invocation_tx_tracking( - rpc: TestClientProvider, - user1_addr: Address, - user2_addr: Address, - ) -> anyhow::Result<()> { - // user input code - { - let network = SimplicityNetwork::default_regtest(); - let keypair = Keypair::from_seckey_slice(&secp256k1::SECP256K1, &[1; 32])?; - - let arguments = P2PKArguments { - public_key: keypair.x_only_public_key().0.serialize(), - }; - - let p2pk_program = P2PK::new(tr_unspendable_key(), arguments); - let p2pk = p2pk_program.get_program().get_tr_address(network).unwrap(); - - dbg!(p2pk.to_string()); - - // simplex runtime - // - test provider - // - fields from config - // - - // p2tr - - dbg!(ElementsRpcClient::validateaddress(rpc.as_ref(), &p2pk.to_string())?); - - // broadcast, fetch fee transaction - - let result = ElementsRpcClient::sendtoaddress( - rpc.as_ref(), - &p2pk, - DEFAULT_SAT_AMOUNT_FAUCET, - Some(network.policy_asset()), - )?; - - ElementsRpcClient::generate_blocks(rpc.as_ref(), 5)?; - - dbg!(ElementsRpcClient::listunspent( - rpc.as_ref(), - None, - None, - Some(vec![p2pk.to_string()]), - None, - None, - )?,); - - dbg!(ElementsRpcClient::scantxoutset( - rpc.as_ref(), - "start", - Some(vec![format!("addr({})", p2pk)]), - )?,); - - Ok(()) - } - } - - let network = SimplicityNetwork::default_regtest(); - let rpc = TestClientProvider::init( - ConfigOption::DefaultRegtest, - ElementsDConf::obtain_default_elementsd_path(), - ) - .unwrap(); - { - ElementsRpcClient::generate_blocks(rpc.as_ref(), 1).unwrap(); - ElementsRpcClient::rescanblockchain(rpc.as_ref(), None, None).unwrap(); - ElementsRpcClient::sweep_initialfreecoins(rpc.as_ref()).unwrap(); - ElementsRpcClient::generate_blocks(rpc.as_ref(), 100).unwrap(); - } - // TODO: remove berkeley - // addresstype - bech32 - - // - - let user1_addr = ElementsRpcClient::getnewaddress(rpc.as_ref(), "", AddressType::default()).unwrap(); - let user2_addr = ElementsRpcClient::getnewaddress(rpc.as_ref(), "", AddressType::default()).unwrap(); - ElementsRpcClient::sendtoaddress( - rpc.as_ref(), - &user1_addr, - DEFAULT_SAT_AMOUNT_FAUCET, - Some(network.policy_asset()), - ) - .unwrap(); - - ElementsRpcClient::sendtoaddress( - rpc.as_ref(), - &user2_addr, - DEFAULT_SAT_AMOUNT_FAUCET, - Some(network.policy_asset()), - ) - .unwrap(); - - ElementsRpcClient::generate_blocks(rpc.as_ref(), 3).unwrap(); - dbg!(ElementsRpcClient::listunspent( - rpc.as_ref(), - None, - None, - Some(vec![user1_addr.to_string(), user2_addr.to_string()]), - None, - None, - )?,); - test_invocation_tx_tracking(rpc, user1_addr, user2_addr) -} +// use simplex_provider::elements_rpc::{AddressType, ElementsRpcClient}; +// use simplex_test::{DEFAULT_SAT_AMOUNT_FAUCET, ElementsDConf, TestContext}; +// use simplicityhl::elements::Address; +// use simplicityhl::elements::bitcoin::secp256k1; +// use simplicityhl::elements::secp256k1_zkp::Keypair; + +// use simplex::simplex_sdk::constants::SimplicityNetwork; +// use simplex::simplex_sdk::presets::{P2PK, P2PKArguments}; +// use simplex::simplex_sdk::utils::tr_unspendable_key; + +// #[ignore] +// #[simplex::simplex_macros::test(default_rpc)] +// fn test_execution(x: TestContext) { +// assert!(true) +// } + +// #[ignore] +// #[simplex::simplex_macros::test] +// fn test_execution3(x: TestContext) { +// assert!(true) +// } + +// #[ignore] +// #[test] +// fn test_execution2() { +// use ::simplex::tracing; +// use simplex_test::TestContextBuilder; +// use std::path::PathBuf; + +// fn test_execution2(x: TestContext) { +// assert!(true); +// } + +// let test_context = match std::env::var("SIMPLEX_TEST_ENV") { +// Err(e) => { +// tracing::trace!( +// "Test 'test_in_custom_folder_custom_333' connected with simplex is disabled, run `simplex test` in order to test it, err: '{e}'" +// ); +// panic!("Failed to run this test, required to use `simplex test`"); +// } +// Ok(path) => { +// let path = PathBuf::from(path); +// let test_context = TestContextBuilder::FromConfigPath(path).build().unwrap(); +// tracing::trace!("Running 'test_in_custom_folder_custom_333' with simplex configuration"); +// test_context +// } +// }; +// println!("fn name: {}, \n ident: {}", "test_execution2", "#ident"); +// println!("input: {}, \n AttributeArgs: {}", "#input", "#args"); + +// test_execution2(test_context) +// } + +// #[test] +// fn test_invocation_tx_tracking() -> anyhow::Result<()> { +// use simplex_test::{ConfigOption, TestClientProvider}; + +// fn test_invocation_tx_tracking( +// rpc: TestClientProvider, +// user1_addr: Address, +// user2_addr: Address, +// ) -> anyhow::Result<()> { +// // user input code +// { +// let network = SimplicityNetwork::default_regtest(); +// let keypair = Keypair::from_seckey_slice(&secp256k1::SECP256K1, &[1; 32])?; + +// let arguments = P2PKArguments { +// public_key: keypair.x_only_public_key().0.serialize(), +// }; + +// let p2pk_program = P2PK::new(tr_unspendable_key(), arguments); +// let p2pk = p2pk_program.get_program().get_tr_address(network).unwrap(); + +// dbg!(p2pk.to_string()); + +// // simplex runtime +// // - test provider +// // - fields from config +// // - +// // p2tr + +// dbg!(ElementsRpcClient::validateaddress(rpc.as_ref(), &p2pk.to_string())?); + +// // broadcast, fetch fee transaction + +// let result = ElementsRpcClient::sendtoaddress( +// rpc.as_ref(), +// &p2pk, +// DEFAULT_SAT_AMOUNT_FAUCET, +// Some(network.policy_asset()), +// )?; + +// ElementsRpcClient::generate_blocks(rpc.as_ref(), 5)?; + +// dbg!(ElementsRpcClient::listunspent( +// rpc.as_ref(), +// None, +// None, +// Some(vec![p2pk.to_string()]), +// None, +// None, +// )?,); + +// dbg!(ElementsRpcClient::scantxoutset( +// rpc.as_ref(), +// "start", +// Some(vec![format!("addr({})", p2pk)]), +// )?,); + +// Ok(()) +// } +// } + +// let network = SimplicityNetwork::default_regtest(); +// let rpc = TestClientProvider::init( +// ConfigOption::DefaultRegtest, +// ElementsDConf::obtain_default_elementsd_path(), +// ) +// .unwrap(); +// { +// ElementsRpcClient::generate_blocks(rpc.as_ref(), 1).unwrap(); +// ElementsRpcClient::rescanblockchain(rpc.as_ref(), None, None).unwrap(); +// ElementsRpcClient::sweep_initialfreecoins(rpc.as_ref()).unwrap(); +// ElementsRpcClient::generate_blocks(rpc.as_ref(), 100).unwrap(); +// } +// // TODO: remove berkeley +// // addresstype - bech32 + +// // + +// let user1_addr = ElementsRpcClient::getnewaddress(rpc.as_ref(), "", AddressType::default()).unwrap(); +// let user2_addr = ElementsRpcClient::getnewaddress(rpc.as_ref(), "", AddressType::default()).unwrap(); +// ElementsRpcClient::sendtoaddress( +// rpc.as_ref(), +// &user1_addr, +// DEFAULT_SAT_AMOUNT_FAUCET, +// Some(network.policy_asset()), +// ) +// .unwrap(); + +// ElementsRpcClient::sendtoaddress( +// rpc.as_ref(), +// &user2_addr, +// DEFAULT_SAT_AMOUNT_FAUCET, +// Some(network.policy_asset()), +// ) +// .unwrap(); + +// ElementsRpcClient::generate_blocks(rpc.as_ref(), 3).unwrap(); +// dbg!(ElementsRpcClient::listunspent( +// rpc.as_ref(), +// None, +// None, +// Some(vec![user1_addr.to_string(), user2_addr.to_string()]), +// None, +// None, +// )?,); +// test_invocation_tx_tracking(rpc, user1_addr, user2_addr) +// } diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index 655691c..d93322b 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -7,13 +7,11 @@ edition.workspace = true [lints] workspace = true - [dependencies] -simplex-provider = { workspace = true } simplex-sdk = { workspace = true } thiserror = { workspace = true } simplicityhl = { workspace = true } electrsd = { workspace = true } serde = { workspace = true } -toml = { workspace = true } \ No newline at end of file +toml = { workspace = true } diff --git a/crates/test/src/common.rs b/crates/test/src/common.rs deleted file mode 100644 index 0cd6630..0000000 --- a/crates/test/src/common.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub const DEFAULT_SAT_AMOUNT_FAUCET: u64 = 100000; - -pub trait ElementsdParams { - fn get_bin_args(&self) -> Vec; -} - -pub struct DefaultElementsdParams; - -impl ElementsdParams for DefaultElementsdParams { - fn get_bin_args(&self) -> Vec { - vec![ - "-fallbackfee=0.0001".to_string(), - "-dustrelayfee=0.00000001".to_string(), - "-acceptdiscountct=1".to_string(), - "-rest".to_string(), - "-evbparams=simplicity:-1:::".to_string(), // Enable Simplicity from block 0 - "-minrelaytxfee=0".to_string(), // test tx with no fees/asset fees - "-blockmintxfee=0".to_string(), // test tx with no fees/asset fees - "-chain=liquidregtest".to_string(), - "-txindex=1".to_string(), - "-validatepegin=0".to_string(), - "-initialfreecoins=2100000000000000".to_string(), - "-listen=1".to_string(), - "-txindex=1".to_string(), - // "-disablewallet=0".to_string(), - ] - } -} diff --git a/crates/test/src/config.rs b/crates/test/src/config.rs new file mode 100644 index 0000000..2730675 --- /dev/null +++ b/crates/test/src/config.rs @@ -0,0 +1,50 @@ +use std::fs; +use std::fs::OpenOptions; +use std::io::Read; +use std::io::Write; +use std::path::Path; + +use serde::{Deserialize, Serialize}; + +use super::error::TestError; + +pub const TEST_ENV_NAME: &str = "SIMPLEX_TEST_ENV"; + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct TestConfig { + #[serde(default)] + pub esplora: Option, + #[serde(default)] + pub rpc: Option, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct RpcConfig { + url: String, + username: String, + password: String, +} + +impl TestConfig { + pub fn to_file(&self, path: &impl AsRef) -> Result<(), TestError> { + if let Some(parent_dir) = path.as_ref().parent() { + fs::create_dir_all(parent_dir)?; + } + + let mut file = OpenOptions::new().create(true).write(true).open(&path)?; + + file.write(toml::to_string_pretty(&self).unwrap().as_bytes())?; + file.flush()?; + + Ok(()) + } + + pub fn from_file(path: impl AsRef) -> Result { + let mut content = String::new(); + let mut file = OpenOptions::new().read(true).open(path)?; + + file.read_to_string(&mut content)?; + + Ok(toml::from_str(&content)?) + } +} diff --git a/crates/test/src/context.rs b/crates/test/src/context.rs new file mode 100644 index 0000000..fb30056 --- /dev/null +++ b/crates/test/src/context.rs @@ -0,0 +1,75 @@ +// use crate::TestError; +// pub use config::*; +// use electrsd::bitcoind::bitcoincore_rpc::Auth; +// pub use rpc_provider::*; +// use std::path::PathBuf; + +// pub struct TestContext { +// config: ElementsDConf, +// rpc: TestRpcProvider, +// } + +// pub enum TestContextBuilder { +// Default, +// FromConfigPath(PathBuf), +// } + +// impl TestContextBuilder { +// pub fn build(self) -> Result { +// let context = match self { +// Self::Default => { +// let elementsd_path = ElementsDConf::obtain_default_elementsd_path(); +// let rpc = TestRpcProvider::init(ConfigOption::DefaultRegtest, &elementsd_path)?; +// TestContext { +// config: ElementsDConf { +// elemendsd_path: elementsd_path, +// rpc_creds: RpcCreds::None, +// }, +// rpc, +// } +// } +// Self::FromConfigPath(path) => { +// let config: ElementsDConf = ElementsDConf::from_file(&path)?; +// match &config.rpc_creds { +// RpcCreds::Auth { +// url, +// username, +// password, +// } => { +// let rpc = TestRpcProvider::init( +// ConfigOption::CustomRpcUrlRegtest { +// url: url.clone(), +// auth: Auth::UserPass(username.clone(), password.clone()), +// }, +// &config.elemendsd_path, +// )?; +// TestContext { config, rpc } +// } +// RpcCreds::None => { +// let rpc = TestRpcProvider::init(ConfigOption::DefaultRegtest, &config.elemendsd_path)?; +// TestContext { config, rpc } +// } +// } +// } +// }; +// Ok(context) +// } +// } + +// impl TestContext { +// pub fn get_config(&self) -> &ElementsDConf { +// &self.config +// } + +// pub fn get_rpc_provider(&self) -> &TestRpcProvider { +// &self.rpc +// } + +// pub fn default_rpc_setup(&self) -> Result<(), TestError> { +// self.rpc.generate_blocks(1)?; +// self.rpc.rescanblockchain(None, None)?; +// self.rpc.sweep_initialfreecoins()?; +// self.rpc.generate_blocks(100)?; +// Ok(()) +// } +// } diff --git a/crates/test/src/error.rs b/crates/test/src/error.rs index 9cd42e8..2bb4ade 100644 --- a/crates/test/src/error.rs +++ b/crates/test/src/error.rs @@ -1,28 +1,7 @@ use std::io; -use electrsd::electrum_client::bitcoin::hex::HexToArrayError; - -use simplex_provider::ExplorerError; - #[derive(thiserror::Error, Debug)] pub enum TestError { - #[error("Explorer error occurred: {0}")] - Explorer(#[from] ExplorerError), - - #[error("Unhealthy rpc connection, error: {0}")] - UnhealthyRpc(ExplorerError), - - #[error("Node failed to start, error: {0}")] - NodeFailedToStart(String), - - /// Errors when converting hex strings to byte arrays. - #[error("Hex to array error: '{0}'")] - HexToArray(#[from] HexToArrayError), - - /// Errors when failed to decode transaction. - #[error("Failed to decode transaction: '{0}'")] - TransactionDecode(String), - /// Errors when io error occurred. #[error("Occurred io error: '{0}'")] Io(#[from] io::Error), diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index 4ad8493..a2bc69c 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -1,7 +1,5 @@ -mod common; -mod error; -mod testing; +pub mod config; +pub mod context; +pub mod error; -pub use common::*; -pub use error::*; -pub use testing::*; +pub use config::{RpcConfig, TestConfig, TEST_ENV_NAME}; diff --git a/crates/test/src/testing/config.rs b/crates/test/src/testing/config.rs deleted file mode 100644 index 3ea2615..0000000 --- a/crates/test/src/testing/config.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::TestError; -use electrsd::bitcoind::bitcoincore_rpc::jsonrpc::serde::{Deserialize, Serialize}; -use std::fs::OpenOptions; -use std::io::Read; -use std::path::{Path, PathBuf}; - -pub const TEST_ENV_NAME: &str = "SIMPLEX_TEST_ENV"; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ElementsDConf { - pub elemendsd_path: PathBuf, - pub rpc_creds: RpcCreds, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub enum RpcCreds { - Auth { - url: String, - username: String, - password: String, - }, - #[default] - None, -} - -impl ElementsDConf { - pub fn from_file(path: impl AsRef) -> Result { - let mut content = String::new(); - let mut file = OpenOptions::new().read(true).open(path)?; - file.read_to_string(&mut content)?; - Ok(toml::from_str(&content)?) - } - - pub fn obtain_default_elementsd_path() -> PathBuf { - // TODO: change binary into installed one in $PATH dir - const ELEMENTSD_BIN_PATH: &str = "../../assets/elementsd"; - const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); - - Path::new(MANIFEST_DIR).join(ELEMENTSD_BIN_PATH) - } -} diff --git a/crates/test/src/testing/mod.rs b/crates/test/src/testing/mod.rs deleted file mode 100644 index 55afece..0000000 --- a/crates/test/src/testing/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -mod config; -mod rpc_provider; - -use crate::TestError; -pub use config::*; -use electrsd::bitcoind::bitcoincore_rpc::Auth; -pub use rpc_provider::*; -use std::path::PathBuf; - -pub struct TestContext { - config: ElementsDConf, - rpc: TestRpcProvider, -} - -pub enum TestContextBuilder { - Default, - FromConfigPath(PathBuf), -} - -impl TestContextBuilder { - pub fn build(self) -> Result { - let context = match self { - Self::Default => { - let elementsd_path = ElementsDConf::obtain_default_elementsd_path(); - let rpc = TestRpcProvider::init(ConfigOption::DefaultRegtest, &elementsd_path)?; - TestContext { - config: ElementsDConf { - elemendsd_path: elementsd_path, - rpc_creds: RpcCreds::None, - }, - rpc, - } - } - Self::FromConfigPath(path) => { - let config: ElementsDConf = ElementsDConf::from_file(&path)?; - match &config.rpc_creds { - RpcCreds::Auth { - url, - username, - password, - } => { - let rpc = TestRpcProvider::init( - ConfigOption::CustomRpcUrlRegtest { - url: url.clone(), - auth: Auth::UserPass(username.clone(), password.clone()), - }, - &config.elemendsd_path, - )?; - TestContext { config, rpc } - } - RpcCreds::None => { - let rpc = TestRpcProvider::init(ConfigOption::DefaultRegtest, &config.elemendsd_path)?; - TestContext { config, rpc } - } - } - } - }; - Ok(context) - } -} - -impl TestContext { - pub fn get_config(&self) -> &ElementsDConf { - &self.config - } - - pub fn get_rpc_provider(&self) -> &TestRpcProvider { - &self.rpc - } - - pub fn default_rpc_setup(&self) -> Result<(), TestError> { - self.rpc.generate_blocks(1)?; - self.rpc.rescanblockchain(None, None)?; - self.rpc.sweep_initialfreecoins()?; - self.rpc.generate_blocks(100)?; - Ok(()) - } -} diff --git a/crates/test/src/testing/rpc_provider.rs b/crates/test/src/testing/rpc_provider.rs deleted file mode 100644 index afd387e..0000000 --- a/crates/test/src/testing/rpc_provider.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::collections::HashMap; -use std::path::Path; -use std::str::FromStr; - -use bitcoind::bitcoincore_rpc::bitcoin; - -use electrsd::bitcoind; -use electrsd::bitcoind::bitcoincore_rpc::jsonrpc::serde_json::Value; -use electrsd::bitcoind::bitcoincore_rpc::{Auth, Client}; -use electrsd::bitcoind::{BitcoinD, Conf}; - -use simplicityhl::elements::Transaction; -use simplicityhl::elements::hex::ToHex; -use simplicityhl::elements::{Address, AssetId, BlockHash, OutPoint, Script, TxOut, Txid}; - -pub use simplex_provider::elements_rpc::*; - -use simplex_sdk::constants::SimplicityNetwork; -use simplex_sdk::provider::{ProviderError, ProviderTrait}; - -use crate::{ElementsdParams, TestError, common}; - -pub enum TestClientProvider { - ConfiguredNode { node: BitcoinD, network: SimplicityNetwork }, - CustomRpc(ElementsRpcClient), -} - -pub enum ConfigOption<'a> { - DefaultRegtest, - CustomConfRegtest { conf: Conf<'a> }, - CustomRpcUrlRegtest { url: String, auth: Auth }, -} - -impl TestClientProvider { - pub fn init(init_option: ConfigOption, elementsd_path: impl AsRef) -> Result { - let rpc = match init_option { - ConfigOption::DefaultRegtest => { - let node = Self::create_default_node(elementsd_path); - let network = SimplicityNetwork::default_regtest(); - Self::ConfiguredNode { node, network } - } - ConfigOption::CustomConfRegtest { conf } => { - let node = Self::create_node(conf, elementsd_path)?; - let network = SimplicityNetwork::default_regtest(); - Self::ConfiguredNode { node, network } - } - ConfigOption::CustomRpcUrlRegtest { auth, url: rpc_url } => { - Self::CustomRpc(ElementsRpcClient::new(&rpc_url, auth)?) - } - }; - - if let Err(e) = ElementsRpcClient::blockchain_info(rpc.as_ref()) { - return Err(TestError::UnhealthyRpc(e)); - } - Ok(rpc) - } - - fn create_default_node(bin_path: impl AsRef) -> BitcoinD { - let mut conf = Conf::default(); - let bin_args = common::DefaultElementsdParams {}.get_bin_args(); - - conf.args = bin_args.iter().map(|x| x.as_ref()).collect::>(); - conf.network = "liquidregtest"; - conf.p2p = bitcoind::P2P::Yes; - - BitcoinD::with_conf(bin_path.as_ref(), &conf).unwrap() - } - - pub fn create_default_node_with_stdin(bin_path: impl AsRef) -> BitcoinD { - let mut conf = Conf::default(); - let bin_args = common::DefaultElementsdParams {}.get_bin_args(); - - conf.args = bin_args.iter().map(|x| x.as_ref()).collect::>(); - conf.view_stdout = true; - conf.attempts = 2; - conf.network = "liquidregtest"; - conf.p2p = bitcoind::P2P::Yes; - - BitcoinD::with_conf(bin_path.as_ref(), &conf).unwrap() - } - - fn create_node(conf: Conf, bin_path: impl AsRef) -> Result { - BitcoinD::with_conf(bin_path.as_ref(), &conf).map_err(|e| TestError::NodeFailedToStart(e.to_string())) - } - - pub fn client(&self) -> &Client { - match self { - TestClientProvider::ConfiguredNode { node, .. } => &node.client, - TestClientProvider::CustomRpc(x) => x.client(), - } - } -} - -impl AsRef for TestClientProvider { - fn as_ref(&self) -> &Client { - self.client() - } -} - -pub struct TestRpcProvider { - provider: TestClientProvider, -} - -impl ProviderTrait for TestRpcProvider { - fn broadcast_transaction(&self, tx: &Transaction) -> Result { - use simplicityhl::simplicity::elements::encode; - let tx_hex = encode::serialize_hex(tx); - self.sendrawtransaction(&tx_hex) - .map_err(|e| ProviderError::Request(e.to_string())) - } - - fn wait(&self, txid: &Txid) -> Result<(), ProviderError> { - todo!() - } - - fn fetch_fee_estimates(&self) -> Result, ProviderError> { - // Todo: search for appropriate endpoint - let mut map = HashMap::new(); - map.insert("".to_string(), 0.1); - Ok(map) - } - - fn fetch_address_utxos(&self, address: &Address) -> Result, ProviderError> { - todo!() - } - - fn fetch_scripthash_utxos(&self, script: &Script) -> Result, ProviderError> { - todo!() - } - - fn fetch_transaction(&self, txid: &Txid) -> Result { - self.gettransaction(&txid) - .map_err(|e| ProviderError::Request(e.to_string())) - } -} - -impl TestRpcProvider { - pub fn init(init_option: ConfigOption, bin_path: impl AsRef) -> Result { - Ok(Self { - provider: TestClientProvider::init(init_option, bin_path)?, - }) - } - - pub fn gettransaction(&self, txid: &Txid) -> Result { - use simplicityhl::elements::encode; - - let client = self.provider.client(); - let res = ElementsRpcClient::getrawtransaction_hex(client, &txid.to_hex())?; - let tx: Transaction = - encode::deserialize(res.as_bytes()).map_err(|e| TestError::TransactionDecode(e.to_string()))?; - Ok(tx) - } - - pub fn height(&self) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::height(client)?) - } - - pub fn blockchain_info(&self) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::blockchain_info(client)?) - } - - pub fn sendtoaddress(&self, address: &Address, satoshi: u64, asset: Option) -> Result { - Ok(ElementsRpcClient::sendtoaddress( - self.provider.client(), - address, - satoshi, - asset, - )?) - } - pub fn rescanblockchain(&self, start: Option, stop: Option) -> Result<(), TestError> { - let client = self.provider.client(); - Ok(ElementsRpcClient::rescanblockchain(client, start, stop)?) - } - - pub fn getnewaddress(&self, label: &str, kind: AddressType) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::getnewaddress(client, label, kind)?) - } - - pub fn generate_blocks(&self, block_num: u32) -> Result<(), TestError> { - let client = self.provider.client(); - Ok(ElementsRpcClient::generate_blocks(client, block_num)?) - } - - pub fn sweep_initialfreecoins(&self) -> Result<(), TestError> { - let client = self.provider.client(); - Ok(ElementsRpcClient::sweep_initialfreecoins(client)?) - } - - pub fn issueasset(&self, satoshi: u64) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::issueasset(client, satoshi)?) - } - - pub fn genesis_block_hash(&self) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::genesis_block_hash(client)?) - } - - pub fn block_hash(&self, height: u64) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::block_hash(client, height)?) - } - - pub fn getpeginaddress(&self) -> Result<(bitcoin::Address, String), TestError> { - let client = self.provider.client(); - Ok(ElementsRpcClient::getpeginaddress(client)?) - } - - pub fn raw_createpsbt(&self, inputs: Value, outputs: Value) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::raw_createpsbt(client, inputs, outputs)?) - } - - pub fn expected_next(&self, base64: &str) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::expected_next(client, base64)?) - } - - pub fn walletprocesspsbt(&self, psbt: &str) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::walletprocesspsbt(client, psbt)?) - } - - pub fn finalizepsbt(&self, psbt: &str) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::finalizepsbt(client, psbt)?) - } - - pub fn sendrawtransaction(&self, tx: &str) -> Result { - let client = self.provider.client(); - let res = ElementsRpcClient::sendrawtransaction(client, tx)?; - Ok(Txid::from_str(&res.txid)?) - } - - pub fn testmempoolaccept(&self, tx: &str) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::testmempoolaccept(client, tx)?) - } - - pub fn create_wallet(&self, wallet_name: Option) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::create_wallet(client, wallet_name)?) - } - - pub fn getbalance(&self, conf: Option) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::getbalance(client, conf)?) - } - - pub fn listunspent( - &self, - min_conf: Option, - max_conf: Option, - addresses: Option>, - include_unsafe: Option, - query_options: Option, - ) -> Result, TestError> { - let client = self.provider.client(); - Ok(ElementsRpcClient::listunspent( - client, - min_conf, - max_conf, - addresses, - include_unsafe, - query_options, - )?) - } - pub fn importaddress( - &self, - address: &str, - label: Option<&str>, - rescan: Option, - p2sh: Option, - ) -> Result<(), TestError> { - let client = self.provider.client(); - Ok(ElementsRpcClient::importaddress(client, address, label, rescan, p2sh)?) - } - pub fn validateaddress(&self, address: &str) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::validateaddress(client, address)?) - } - - pub fn scantxoutset( - &self, - action: &str, - scanobjects: Option>, - ) -> Result { - let client = self.provider.client(); - Ok(ElementsRpcClient::scantxoutset(client, action, scanobjects)?) - } -} diff --git a/examples/basic/Simplex.toml b/examples/basic/Simplex.toml index 3e97780..30229db 100644 --- a/examples/basic/Simplex.toml +++ b/examples/basic/Simplex.toml @@ -1,4 +1,4 @@ -network = "liquidtestnet" +network = "elementsregtest" [build] simf_files = ["*.simf"] diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index 7c29088..094eaa2 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -2,8 +2,8 @@ mod artifacts; use simplicityhl::elements::{Script, Txid}; -use simplex::simplex_sdk::constants::{DUMMY_SIGNATURE, SimplicityNetwork}; -use simplex::simplex_sdk::provider::{EsploraProvider, ProviderTrait}; +use simplex::simplex_sdk::constants::DUMMY_SIGNATURE; +use simplex::simplex_sdk::provider::{EsploraProvider, ProviderTrait, SimplicityNetwork}; use simplex::simplex_sdk::signer::Signer; use simplex::simplex_sdk::transaction::{ FinalTransaction, PartialInput, PartialOutput, ProgramInput, RequiredSignature, @@ -80,7 +80,7 @@ fn main() -> anyhow::Result<()> { let provider = EsploraProvider::new(ESPLORA_URL.to_string()); let signer = Signer::new( "exist carry drive collect lend cereal occur much tiger just involve mean", - Box::new(provider.clone()), + provider.clone(), SimplicityNetwork::LiquidTestnet, )?; @@ -97,5 +97,6 @@ fn main() -> anyhow::Result<()> { println!("Confirmed"); println!("OK"); + Ok(()) }