From: Mehmet Efe Umit Date: Wed, 19 Nov 2025 06:22:44 +0000 (-0800) Subject: feat: add payjoin send support X-Git-Url: http://internal-gitweb-vhost/script/%22https:/database/struct.CommandStringError.html?a=commitdiff_plain;h=0112c924adf186b8b62d77d8dc0b2837426ea4af;p=bdk-cli feat: add payjoin send support temp: move the send_payjoin out of the commit --- diff --git a/Cargo.lock b/Cargo.lock index 4ff8e29..5e615ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,52 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", + "rand_core 0.6.4", +] + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures 0.2.17", + "opaque-debug", +] + +[[package]] +name = "aes-gcm" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" +dependencies = [ + "aead 0.4.3", + "aes", + "cipher 0.3.0", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.12" @@ -192,12 +238,15 @@ dependencies = [ "dirs", "env_logger", "log", + "payjoin", + "reqwest", "serde_json", "shlex", - "thiserror", + "thiserror 2.0.12", "tokio", "tracing", "tracing-subscriber", + "url", ] [[package]] @@ -278,7 +327,7 @@ dependencies = [ "ciborium", "redb", "tempfile", - "thiserror", + "thiserror 2.0.12", ] [[package]] @@ -291,7 +340,7 @@ dependencies = [ "bip39", "bitcoin", "miniscript", - "rand_core", + "rand_core 0.6.4", "serde", "serde_json", ] @@ -302,6 +351,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +[[package]] +name = "bhttp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16fc24bc615b9fd63148f59b218ea58a444b55762f8845da910e23aca686398b" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "bindgen" version = "0.69.5" @@ -319,7 +377,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn", "which", @@ -346,7 +404,7 @@ dependencies = [ "bitcoin", "bitcoin_hashes 0.15.0", "chacha20-poly1305", - "rand", + "rand 0.8.5", "tokio", ] @@ -389,6 +447,25 @@ dependencies = [ "bitcoin", ] +[[package]] +name = "bitcoin-hpke" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37a54c486727c1d1ae9cc28dcf78b6e6ba20dcb88e8c892f1437d9ce215dc8c" +dependencies = [ + "aead 0.5.2", + "chacha20poly1305 0.10.1", + "digest 0.10.7", + "generic-array", + "hkdf 0.12.4", + "hmac 0.12.1", + "rand_core 0.6.4", + "secp256k1", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "bitcoin-internals" version = "0.2.0" @@ -425,6 +502,29 @@ dependencies = [ "bitcoin-internals 0.4.0", ] +[[package]] +name = "bitcoin-ohttp" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87a803a4b54e44635206b53329c78c0029d0c70926288ac2f07f4bb1267546cb" +dependencies = [ + "aead 0.4.3", + "aes-gcm", + "bitcoin-hpke", + "byteorder", + "chacha20poly1305 0.8.0", + "hex", + "hkdf 0.11.0", + "lazy_static", + "log", + "rand 0.8.5", + "serde", + "serde_derive", + "sha2 0.9.9", + "thiserror 1.0.69", + "toml", +] + [[package]] name = "bitcoin-units" version = "0.1.2" @@ -466,6 +566,16 @@ dependencies = [ "hex-conservative 0.3.0", ] +[[package]] +name = "bitcoin_uri" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0a228e083d1702f83389b0ac71eb70078dc8d7fcbb6cde864d1cbca145f5cc" +dependencies = [ + "bitcoin", + "percent-encoding-rfc3986", +] + [[package]] name = "bitcoincore-rpc" version = "0.19.0" @@ -496,6 +606,24 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -540,12 +668,67 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures 0.1.5", + "zeroize", +] + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + [[package]] name = "chacha20-poly1305" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b4b0fc281743d80256607bd65e8beedc42cb0787ea119c85b81b4c0eab85e5f" +[[package]] +name = "chacha20poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" +dependencies = [ + "aead 0.4.3", + "chacha20 0.7.1", + "cipher 0.3.0", + "poly1305 0.7.2", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead 0.5.2", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305 0.8.0", + "zeroize", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -573,6 +756,26 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -678,12 +881,51 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "csv" version = "1.3.1" @@ -705,6 +947,35 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" +dependencies = [ + "cipher 0.3.0", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + [[package]] name = "dirs" version = "6.0.0" @@ -762,7 +1033,7 @@ dependencies = [ "rustls 0.23.31", "serde", "serde_json", - "webpki-roots", + "webpki-roots 0.25.4", "winapi", ] @@ -957,6 +1228,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -964,8 +1245,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -975,9 +1258,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", ] [[package]] @@ -1027,6 +1322,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex-conservative" version = "0.1.2" @@ -1057,6 +1358,44 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "home" version = "0.5.11" @@ -1125,6 +1464,23 @@ dependencies = [ "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.31", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.4", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1272,6 +1628,15 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "io-uring" version = "0.7.9" @@ -1459,6 +1824,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "memchr" version = "2.7.5" @@ -1503,7 +1874,7 @@ dependencies = [ "rustls-webpki 0.101.7", "serde", "serde_json", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -1574,6 +1945,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.73" @@ -1647,12 +2024,37 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "payjoin" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e20b76ae28f1420a918e8051681fc9669ed7273e542e515baa329be78c3255a" +dependencies = [ + "bhttp", + "bitcoin", + "bitcoin-hpke", + "bitcoin-ohttp", + "bitcoin_uri", + "http", + "reqwest", + "serde", + "serde_json", + "tracing", + "url", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "percent-encoding-rfc3986" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3637c05577168127568a64e9dc5a6887da720efef07b3d9472d45f63ab191166" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1671,6 +2073,40 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.4.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.5.1", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.4.0", +] + [[package]] name = "portable-atomic" version = "1.11.1" @@ -1723,6 +2159,61 @@ dependencies = [ "unicode-ident", ] +[[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 2.1.1", + "rustls 0.23.31", + "socket2", + "thiserror 2.0.12", + "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.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash 2.1.1", + "rustls 0.23.31", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "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.40" @@ -1745,8 +2236,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "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.3", ] [[package]] @@ -1756,7 +2257,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "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.3", ] [[package]] @@ -1768,6 +2279,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "redb" version = "2.6.0" @@ -1794,7 +2314,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror", + "thiserror 2.0.12", ] [[package]] @@ -1828,9 +2348,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", @@ -1839,6 +2359,7 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", @@ -1846,6 +2367,8 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.31", "rustls-pki-types", "serde", "serde_json", @@ -1853,6 +2376,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -1860,6 +2384,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 1.0.4", ] [[package]] @@ -1902,6 +2427,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[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" @@ -1949,6 +2480,7 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki 0.103.4", "subtle", @@ -1961,6 +2493,7 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ + "web-time", "zeroize", ] @@ -2030,7 +2563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "bitcoin_hashes 0.14.0", - "rand", + "rand 0.8.5", "secp256k1-sys", "serde", ] @@ -2111,6 +2644,30 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2228,13 +2785,33 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2323,6 +2900,25 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.31", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower" version = "0.5.2" @@ -2431,6 +3027,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -2452,6 +3054,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +[[package]] +name = "universal-hash" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -2467,6 +3089,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -2604,12 +3227,31 @@ dependencies = [ "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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -2900,6 +3542,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index d5767f3..366ee5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ serde_json = "1.0" thiserror = "2.0.11" tokio = { version = "1", features = ["full"] } cli-table = "0.5.0" +tracing = "0.1.41" +tracing-subscriber = "0.3.20" # Optional dependencies bdk_bitcoind_rpc = { version = "0.21.0", features = ["std"], optional = true } @@ -29,8 +31,9 @@ bdk_esplora = { version = "0.22.1", features = ["async-https", "tokio"], optiona bdk_kyoto = { version = "0.15.1", optional = true } bdk_redb = { version = "0.1.0", optional = true } shlex = { version = "1.3.0", optional = true } -tracing = "0.1.41" -tracing-subscriber = "0.3.20" +payjoin = { version = "1.0.0-rc.1", features = ["v1", "v2", "io", "_test-utils"], optional = true} +reqwest = { version = "0.12.23", default-features = false, optional = true } +url = { version = "2.5.4", optional = true } [features] default = ["repl", "sqlite"] @@ -43,10 +46,13 @@ sqlite = ["bdk_wallet/rusqlite"] redb = ["bdk_redb"] # Available blockchain client options -cbf = ["bdk_kyoto"] -electrum = ["bdk_electrum"] -esplora = ["bdk_esplora"] -rpc = ["bdk_bitcoind_rpc"] +cbf = ["bdk_kyoto", "_payjoin-dependencies"] +electrum = ["bdk_electrum", "_payjoin-dependencies"] +esplora = ["bdk_esplora", "_payjoin-dependencies"] +rpc = ["bdk_bitcoind_rpc", "_payjoin-dependencies"] + +# Internal features +_payjoin-dependencies = ["payjoin", "reqwest", "url"] # Use this to consensus verify transactions at sync time verify = [] diff --git a/src/commands.rs b/src/commands.rs index d3f2d98..e730c0f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -430,6 +430,24 @@ pub enum OnlineWalletSubCommand { )] tx: Option, }, + /// Sends an original PSBT to a BIP 21 URI and broadcasts the returned Payjoin PSBT. + SendPayjoin { + /// BIP 21 URI for the Payjoin. + #[arg(env = "PAYJOIN_URI", long = "uri", required = true)] + uri: String, + /// URL of the Payjoin OHTTP relay. Can be repeated multiple times to attempt the + /// operation with multiple relays for redundancy. + #[arg(env = "PAYJOIN_OHTTP_RELAY", long = "ohttp_relay", required = true)] + ohttp_relay: Vec, + /// Fee rate to use in sat/vbyte. + #[arg( + env = "PAYJOIN_SENDER_FEE_RATE", + short = 'f', + long = "fee_rate", + required = true + )] + fee_rate: u64, + }, } /// Subcommands for Key operations. diff --git a/src/error.rs b/src/error.rs index 1b8b5b4..064a928 100644 --- a/src/error.rs +++ b/src/error.rs @@ -103,6 +103,15 @@ pub enum BDKCliError { #[cfg(feature = "cbf")] #[error("BDK-Kyoto update error: {0}")] KyotoUpdateError(#[from] bdk_kyoto::UpdateError), + + #[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "rpc", + feature = "cbf", + ))] + #[error("Reqwest error: {0}")] + ReqwestError(#[from] reqwest::Error), } impl From for BDKCliError { diff --git a/src/handlers.rs b/src/handlers.rs index b863e25..d9b214c 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -58,7 +58,14 @@ use std::convert::TryFrom; #[cfg(any(feature = "repl", feature = "electrum", feature = "esplora"))] use std::io::Write; use std::str::FromStr; -#[cfg(any(feature = "redb", feature = "compiler"))] +#[cfg(any( + feature = "redb", + feature = "compiler", + feature = "electrum", + feature = "esplora", + feature = "cbf", + feature = "rpc" +))] use std::sync::Arc; #[cfg(any( feature = "electrum", @@ -68,7 +75,9 @@ use std::sync::Arc; ))] use { crate::commands::OnlineWalletSubCommand::*, + crate::payjoin::{PayjoinManager, ohttp::RelayManager}, bdk_wallet::bitcoin::{Transaction, consensus::Decodable, hex::FromHex}, + std::sync::Mutex, }; #[cfg(feature = "esplora")] use {crate::utils::BlockchainClient::Esplora, bdk_esplora::EsploraAsyncExt}; @@ -706,6 +715,17 @@ pub(crate) async fn handle_online_wallet_subcommand( let txid = broadcast_transaction(client, tx).await?; Ok(serde_json::to_string_pretty(&json!({ "txid": txid }))?) } + SendPayjoin { + uri, + ohttp_relay, + fee_rate, + } => { + let relay_manager = Arc::new(Mutex::new(RelayManager::new())); + let mut payjoin_manager = PayjoinManager::new(wallet, relay_manager); + return payjoin_manager + .send_payjoin(uri, fee_rate, ohttp_relay, client) + .await; + } } } diff --git a/src/main.rs b/src/main.rs index c69aecc..81190bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,13 @@ mod commands; mod error; mod handlers; +#[cfg(any( + feature = "electrum", + feature = "esplora", + feature = "cbf", + feature = "rpc" +))] +mod payjoin; #[cfg(any(feature = "sqlite", feature = "redb"))] mod persister; mod utils; diff --git a/src/payjoin/mod.rs b/src/payjoin/mod.rs new file mode 100644 index 0000000..2f36224 --- /dev/null +++ b/src/payjoin/mod.rs @@ -0,0 +1,274 @@ +use crate::error::BDKCliError as Error; +use crate::handlers::{broadcast_transaction, sync_wallet}; +use crate::utils::BlockchainClient; +use bdk_wallet::{ + SignOptions, Wallet, + bitcoin::{FeeRate, Psbt, Txid, consensus::encode::serialize_hex}, +}; +use payjoin::bitcoin::TxIn; +use payjoin::persist::{OptionalTransitionOutcome, SessionPersister}; +use payjoin::receive::InputPair; +use payjoin::receive::v2::{ + HasReplyableError, Initialized, MaybeInputsOwned, MaybeInputsSeen, Monitor, OutputsUnknown, + PayjoinProposal, ProvisionalProposal, ReceiveSession, Receiver, + SessionEvent as ReceiverSessionEvent, UncheckedOriginalPayload, WantsFeeRange, WantsInputs, + WantsOutputs, +}; +use payjoin::send::v2::{ + PollingForProposal, SendSession, Sender, SessionEvent as SenderSessionEvent, + SessionOutcome as SenderSessionOutcome, WithReplyKey, +}; +use payjoin::{ImplementationError, UriExt}; +use serde_json::{json, to_string_pretty}; +use std::sync::{Arc, Mutex}; + +use crate::payjoin::ohttp::{RelayManager, fetch_ohttp_keys}; + +pub mod ohttp; + +/// Implements all of the functions required to go through the Payjoin receive and send processes. +/// +/// TODO: At the time of writing, this struct is written to make a Persister implementation easier +/// but the persister is not implemented yet! For instance [`PayjoinManager::proceed_sender_session`] and +/// [`PayjoinManager::proceed_receiver_session`] are designed such that the manager can enable +/// resuming ongoing payjoins are well. So... this is a TODO for implementing persister. +pub(crate) struct PayjoinManager<'a> { + wallet: &'a mut Wallet, + relay_manager: Arc>, +} + +impl<'a> PayjoinManager<'a> { + pub fn new(wallet: &'a mut Wallet, relay_manager: Arc>) -> Self { + Self { + wallet, + relay_manager, + } + } + + + pub async fn send_payjoin( + &mut self, + uri: String, + fee_rate: u64, + ohttp_relays: Vec, + blockchain_client: BlockchainClient, + ) -> Result { + let uri = payjoin::Uri::try_from(uri) + .map_err(|e| Error::Generic(format!("Failed parsing to Payjoin URI: {}", e)))?; + let uri = uri.require_network(self.wallet.network()).map_err(|e| { + Error::Generic(format!("Failed setting the right network for the URI: {e}")) + })?; + let uri = uri + .check_pj_supported() + .map_err(|e| Error::Generic(format!("URI does not support Payjoin: {}", e)))?; + + let sats = uri + .amount + .ok_or_else(|| Error::Generic("Amount is not specified in the URI.".to_string()))?; + + let fee_rate = FeeRate::from_sat_per_vb(fee_rate).expect("Provided fee rate is not valid."); + + // Build and sign the original PSBT which pays to the receiver. + let mut original_psbt = { + let mut tx_builder = self.wallet.build_tx(); + tx_builder + .add_recipient(uri.address.script_pubkey(), sats) + .fee_rate(fee_rate); + + tx_builder.finish().map_err(|e| { + Error::Generic(format!( + "Error occurred when building original Payjoin transaction: {e}" + )) + })? + }; + if !self + .wallet + .sign(&mut original_psbt, SignOptions::default())? + { + return Err(Error::Generic( + "Failed to sign and finalize the original PSBT.".to_string(), + )); + } + + let txid = match uri.extras.pj_param() { + payjoin::PjParam::V1(_) => { + let (req, ctx) = payjoin::send::v1::SenderBuilder::new(original_psbt.clone(), uri) + .build_recommended(fee_rate) + .map_err(|e| { + Error::Generic(format!("Failed to build a Payjoin v1 sender: {e}")) + })? + .create_v1_post_request(); + + let response = self + .send_payjoin_post_request(req) + .await + .map_err(|e| Error::Generic(format!("Failed to send request: {e}")))?; + + let psbt = ctx + .process_response(&response.bytes().await?) + .map_err(|e| Error::Generic(format!("Failed to send a Payjoin v1: {e}")))?; + + self.process_payjoin_proposal(psbt, blockchain_client) + .await? + } + payjoin::PjParam::V2(_) => { + let ohttp_relays: Vec = ohttp_relays + .into_iter() + .map(|s| url::Url::parse(&s)) + .collect::>() + .map_err(|e| { + Error::Generic(format!("Failed to parse one or more OHTTP URLs: {e}")) + })?; + + if ohttp_relays.is_empty() { + return Err(Error::Generic( + "At least one valid OHTTP relay must be provided.".into(), + )); + } + + // TODO: Implement proper persister. + let persister = + payjoin::persist::NoopSessionPersister::::default(); + + let sender = payjoin::send::v2::SenderBuilder::new(original_psbt.clone(), uri) + .build_recommended(fee_rate) + .map_err(|e| { + Error::Generic(format!("Failed to build a Payjoin v2 sender: {e}")) + })? + .save(&persister) + .map_err(|e| { + Error::Generic(format!( + "Failed to save the Payjoin v2 sender in the persister: {e}" + )) + })?; + + let selected_relay = + fetch_ohttp_keys(ohttp_relays, &sender.endpoint(), self.relay_manager.clone()) + .await? + .relay_url; + + self.proceed_sender_session( + SendSession::WithReplyKey(sender), + &persister, + selected_relay.to_string(), + blockchain_client, + ) + .await? + } + _ => { + unimplemented!("Payjoin version not recognized."); + } + }; + + Ok(to_string_pretty(&json!({ "txid": txid }))?) + } + async fn proceed_sender_session( + &self, + session: SendSession, + persister: &impl SessionPersister, + relay: impl payjoin::IntoUrl, + blockchain_client: BlockchainClient, + ) -> Result { + match session { + SendSession::WithReplyKey(context) => { + self.post_original_proposal(context, relay, persister, blockchain_client) + .await + } + SendSession::PollingForProposal(context) => { + self.get_proposed_payjoin_proposal(context, relay, persister, blockchain_client) + .await + } + SendSession::Closed(SenderSessionOutcome::Success(psbt)) => { + self.process_payjoin_proposal(psbt, blockchain_client).await + } + _ => Err(Error::Generic("Unexpected SendSession state!".to_string())), + } + } + + async fn post_original_proposal( + &self, + sender: Sender, + relay: impl payjoin::IntoUrl, + persister: &impl SessionPersister, + blockchain_client: BlockchainClient, + ) -> Result { + let (req, ctx) = sender.create_v2_post_request(relay.as_str()).map_err(|e| { + Error::Generic(format!( + "Failed to create a post request for a Payjoin send: {e}" + )) + })?; + let response = self.send_payjoin_post_request(req).await?; + let sender = sender + .process_response(&response.bytes().await?, ctx) + .save(persister) + .map_err(|e| { + Error::Generic(format!("Failed to persist the Payjoin send after successfully sending original proposal: {e}")) + })?; + self.get_proposed_payjoin_proposal(sender, relay, persister, blockchain_client) + .await + } + + async fn get_proposed_payjoin_proposal( + &self, + sender: Sender, + relay: impl payjoin::IntoUrl, + persister: &impl SessionPersister, + blockchain_client: BlockchainClient, + ) -> Result { + let mut sender = sender.clone(); + loop { + let (req, ctx) = sender.create_poll_request(relay.as_str()).map_err(|e| { + Error::Generic(format!( + "Failed to create a poll request during a Payjoin send: {e}" + )) + })?; + let response = self.send_payjoin_post_request(req).await?; + let processed_response = sender + .process_response(&response.bytes().await?, ctx) + .save(persister); + match processed_response { + Ok(OptionalTransitionOutcome::Progress(psbt)) => { + println!("Proposal received. Processing..."); + return self.process_payjoin_proposal(psbt, blockchain_client).await; + } + Ok(OptionalTransitionOutcome::Stasis(current_state)) => { + println!("No response yet. Continuing polling..."); + sender = current_state; + continue; + } + Err(e) => { + break Err(Error::Generic(format!( + "Error occurred when polling for Payjoin v2 proposal: {e}" + ))); + } + } + } + } + + async fn process_payjoin_proposal( + &self, + mut psbt: Psbt, + blockchain_client: BlockchainClient, + ) -> Result { + if !self.wallet.sign(&mut psbt, SignOptions::default())? { + return Err(Error::Generic( + "Failed to sign and finalize the Payjoin proposal PSBT.".to_string(), + )); + } + + broadcast_transaction(blockchain_client, psbt.extract_tx_fee_rate_limit()?).await + } + + async fn send_payjoin_post_request( + &self, + req: payjoin::Request, + ) -> reqwest::Result { + let client = reqwest::Client::new(); + client + .post(req.url) + .header("Content-Type", req.content_type) + .body(req.body) + .send() + .await + } +} diff --git a/src/payjoin/ohttp.rs b/src/payjoin/ohttp.rs new file mode 100644 index 0000000..1cc0935 --- /dev/null +++ b/src/payjoin/ohttp.rs @@ -0,0 +1,110 @@ +use crate::error::BDKCliError as Error; +use std::sync::{Arc, Mutex}; + +#[derive(Debug, Clone)] +pub(crate) struct RelayManager { + selected_relay: Option, + failed_relays: Vec, +} + +impl RelayManager { + pub fn new() -> Self { + RelayManager { + selected_relay: None, + failed_relays: Vec::new(), + } + } + + pub fn set_selected_relay(&mut self, relay: url::Url) { + self.selected_relay = Some(relay); + } + + pub fn get_selected_relay(&self) -> Option { + self.selected_relay.clone() + } + + pub fn add_failed_relay(&mut self, relay: url::Url) { + self.failed_relays.push(relay); + } + + pub fn get_failed_relays(&self) -> Vec { + self.failed_relays.clone() + } +} + +pub(crate) struct ValidatedOhttpKeys { + pub(crate) ohttp_keys: payjoin::OhttpKeys, + pub(crate) relay_url: url::Url, +} + +pub(crate) async fn fetch_ohttp_keys( + relays: Vec, + payjoin_directory: impl payjoin::IntoUrl, + relay_manager: Arc>, +) -> Result { + use payjoin::bitcoin::secp256k1::rand::prelude::SliceRandom; + + loop { + let failed_relays = relay_manager + .lock() + .expect("Lock should not be poisoned") + .get_failed_relays(); + + let remaining_relays: Vec<_> = relays + .iter() + .filter(|r| !failed_relays.contains(r)) + .cloned() + .collect(); + + if remaining_relays.is_empty() { + return Err(Error::Generic( + "No valid OHTTP relays available".to_string(), + )); + } + + let selected_relay = + match remaining_relays.choose(&mut payjoin::bitcoin::key::rand::thread_rng()) { + Some(relay) => relay.clone(), + None => { + return Err(Error::Generic( + "Failed to select from remaining relays".to_string(), + )); + } + }; + + relay_manager + .lock() + .expect("Lock should not be poisoned") + .set_selected_relay(selected_relay.clone()); + + let ohttp_keys = + payjoin::io::fetch_ohttp_keys(selected_relay.as_str(), payjoin_directory.as_str()) + .await; + + match ohttp_keys { + Ok(keys) => { + return Ok(ValidatedOhttpKeys { + ohttp_keys: keys, + relay_url: selected_relay, + }); + } + Err(payjoin::io::Error::UnexpectedStatusCode(e)) => { + return Err(Error::Generic(format!( + "Unexpected error occurred when fetching OHTTP keys: {}", + e + ))); + } + Err(e) => { + tracing::debug!( + "Failed to connect to OHTTP relay: {}, {}", + selected_relay, + e + ); + relay_manager + .lock() + .expect("Lock should not be poisoned") + .add_failed_relay(selected_relay); + } + } + } +}