diff --git a/Cargo.lock b/Cargo.lock index f067eb8..7af8928 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,6 +127,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.8.8" @@ -138,10 +160,10 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "itoa", "matchit", @@ -154,7 +176,7 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-tungstenite 0.28.0", "tower", @@ -171,12 +193,12 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -343,9 +365,6 @@ dependencies = [ "getrandom 0.3.4", "lightning 0.3.0+git", "lightning-invoice 0.34.0+git", - "reqwest 0.11.27", - "serde", - "serde_json", ] [[package]] @@ -358,6 +377,9 @@ dependencies = [ "getrandom 0.3.4", "lightning 0.3.0+git", "lightning-invoice 0.34.0+git", + "reqwest 0.13.1", + "serde", + "serde_json", ] [[package]] @@ -381,12 +403,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.11.0" @@ -427,9 +443,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -508,17 +532,36 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -644,6 +687,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.15.0" @@ -660,22 +709,13 @@ dependencies = [ "byteorder", "libc", "log", - "rustls 0.23.37", + "rustls", "serde", "serde_json", "webpki-roots 0.25.4", "winapi", ] -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -758,6 +798,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.32" @@ -896,25 +942,6 @@ dependencies = [ "wasip3", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.14" @@ -926,7 +953,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http", "indexmap", "slab", "tokio", @@ -1025,17 +1052,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.4.0" @@ -1046,17 +1062,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -1064,7 +1069,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http", ] [[package]] @@ -1075,8 +1080,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -1092,30 +1097,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.8.1" @@ -1126,9 +1107,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.14", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", @@ -1139,33 +1120,19 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.4.0", - "hyper 1.8.1", + "http", + "hyper", "hyper-util", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower-service", "webpki-roots 1.0.6", ] @@ -1180,14 +1147,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.8.1", + "http", + "http-body", + "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2", "tokio", "tower-service", "tracing", @@ -1374,6 +1341,60 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.92" @@ -1438,7 +1459,7 @@ dependencies = [ "rand 0.9.2", "reqwest 0.12.28", "rusqlite", - "rustls 0.23.37", + "rustls", "serde", "serde_json", "tokio", @@ -1480,7 +1501,7 @@ dependencies = [ "rand 0.9.2", "reqwest 0.12.28", "rusqlite", - "rustls 0.23.37", + "rustls", "serde", "serde_json", "tokio", @@ -1879,7 +1900,7 @@ dependencies = [ "axum", "base64 0.22.1", "bech32", - "bitcoin-payment-instructions 0.6.0 (git+https://github.com/moneydevkit/bitcoin-payment-instructions?rev=6796e87525d6c564e1332354a808730e2ba2ebf8)", + "bitcoin-payment-instructions 0.6.0 (git+https://github.com/moneydevkit/bitcoin-payment-instructions?rev=bdcef061488bcc619142010a2a69cd49462d8843)", "chrono", "clap", "corepc-node", @@ -1890,7 +1911,7 @@ dependencies = [ "ldk-node 0.7.0 (git+https://github.com/moneydevkit/ldk-node?rev=e5fcce065fa97c522de0f8fec87beb9e7e541456)", "ldk-node 0.7.0 (git+https://github.com/moneydevkit/ldk-node?rev=f13fcead7e02ef4b77489a83854f204de11e902b)", "log", - "reqwest 0.12.28", + "reqwest 0.13.1", "rusqlite", "serde", "serde_json", @@ -1996,6 +2017,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2202,8 +2229,8 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.37", - "socket2 0.6.3", + "rustls", + "socket2", "thiserror 2.0.18", "tokio", "tracing", @@ -2216,13 +2243,14 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -2240,7 +2268,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2", "tracing", "windows-sys 0.60.2", ] @@ -2331,7 +2359,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -2365,75 +2393,73 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", - "encoding_rs", + "futures-channel", "futures-core", "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-rustls 0.24.2", - "ipnet", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile", + "quinn", + "rustls", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", + "sync_wrapper", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.4", - "winreg", + "webpki-roots 1.0.6", ] [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" dependencies = [ "base64 0.22.1", "bytes", - "futures-channel", "futures-core", - "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.37", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -2464,7 +2490,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ - "bitflags 2.11.0", + "bitflags", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -2484,7 +2510,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2497,47 +2523,39 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.10", + "rustls-webpki", "subtle", "zeroize", ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "rustls-native-certs" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "base64 0.21.7", + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", ] [[package]] @@ -2551,21 +2569,39 @@ dependencies = [ ] [[package]] -name = "rustls-webpki" -version = "0.101.7" +name = "rustls-platform-verifier" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ - "ring", - "untrusted", + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2584,21 +2620,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] -name = "scopeguard" -version = "1.2.0" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] -name = "sct" -version = "0.7.1" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "ring", - "untrusted", + "windows-sys 0.61.2", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "secp256k1" version = "0.29.1" @@ -2620,6 +2664,29 @@ dependencies = [ "cc", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -2751,16 +2818,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.3" @@ -2811,12 +2868,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -2837,27 +2888,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tempfile" version = "3.27.0" @@ -2948,7 +2978,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -2964,23 +2994,13 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "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.37", + "rustls", "tokio", ] @@ -3083,7 +3103,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -3096,11 +3116,11 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "iri-string", "pin-project-lite", "tower", @@ -3154,7 +3174,7 @@ checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", "rand 0.9.2", @@ -3171,7 +3191,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", "rand 0.9.2", @@ -3326,6 +3346,16 @@ dependencies = [ "url", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3442,7 +3472,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags", "hashbrown 0.15.5", "indexmap", "semver", @@ -3468,6 +3498,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.25.4" @@ -3520,6 +3559,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3585,6 +3633,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3630,6 +3687,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -3678,6 +3750,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3696,6 +3774,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3714,6 +3798,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3744,6 +3834,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3762,6 +3858,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3780,6 +3882,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3798,6 +3906,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3825,16 +3939,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wiremock" version = "0.6.5" @@ -3845,9 +3949,9 @@ dependencies = [ "base64 0.22.1", "deadpool", "futures", - "http 1.4.0", + "http", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "log", "once_cell", @@ -3916,7 +4020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags", "indexmap", "log", "serde", diff --git a/Cargo.toml b/Cargo.toml index 103b1fe..5e0308e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ ldk-node = { git = "https://github.com/moneydevkit/ldk-node", rev = "f13fcead7e0 # Pinned to the same git rev as ldk-node's transitive pull to avoid duplicate # crate compilation. Verify with `cargo tree -d | grep bitcoin-payment-instructions`. -bitcoin-payment-instructions = { git = "https://github.com/moneydevkit/bitcoin-payment-instructions", rev = "6796e87525d6c564e1332354a808730e2ba2ebf8", default-features = false, features = ["http"] } +bitcoin-payment-instructions = { git = "https://github.com/moneydevkit/bitcoin-payment-instructions", rev = "bdcef061488bcc619142010a2a69cd49462d8843", default-features = false, features = ["http"] } axum = { version = "0.8", features = ["json", "ws"] } utoipa = { version = "5.4", features = ["axum_extras"] } @@ -30,7 +30,7 @@ tokio = { version = "1", features = ["full"] } tokio-util = "0.7" serde = { version = "1", features = ["derive"] } serde_json = "1" -reqwest = { version = "0.12", features = ["json", "rustls-tls", "socks"], default-features = false } +reqwest = { version = "0.13", features = ["json", "form", "rustls", "webpki-roots", "socks"], default-features = false } rusqlite = { version = "0.31.0", features = ["bundled"] } hex = { package = "hex-conservative", version = "0.2.1" } clap = { version = "4", features = ["derive"] } diff --git a/src/daemon/api/balance.rs b/src/daemon/api/balance.rs index 154379f..65cd8f1 100644 --- a/src/daemon/api/balance.rs +++ b/src/daemon/api/balance.rs @@ -35,7 +35,11 @@ pub async fn handle_get_balance( .map(|ch| ch.outbound_capacity_msat / 1000) .sum(); - let max_withdrawable_sat = client.max_sendable(None).ok().map(|e| e.amount_msat / 1000); + let max_withdrawable_sat = client + .max_sendable(None) + .await + .ok() + .map(|e| e.amount_msat / 1000); Ok(Json(GetBalanceResponse { balance_sat: lightning_sat, diff --git a/src/daemon/api/pay_any.rs b/src/daemon/api/pay_any.rs index 6f4caeb..99b5320 100644 --- a/src/daemon/api/pay_any.rs +++ b/src/daemon/api/pay_any.rs @@ -62,7 +62,7 @@ pub async fn handle_pay(state: AppState, req: &PayRequest) -> Result; pub struct MdkClient { node: Arc, api: Arc, + http_client: Client, lsp_pubkey: PublicKey, splice_cfg: SpliceConfig, max_sendable_cfg: MaxSendableConfig, @@ -96,6 +98,7 @@ impl MdkClient { Ok(Self { node, api, + http_client, lsp_pubkey, splice_cfg, max_sendable_cfg, @@ -144,15 +147,25 @@ impl MdkClient { self.lsp_pubkey } + /// Shared `reqwest::Client` honouring `socks_proxy`. Hand to + /// any HTTP-using crate (e.g. `HTTPHrnResolver::with_client`) + /// rather than building a fresh `reqwest::Client` that would + /// bypass the proxy. + pub fn http_client(&self) -> &Client { + &self.http_client + } + /// Best-effort estimate of the largest amount that can flow out /// over Lightning right now, with routing-fee headroom subtracted. /// Recomputed from `node.list_channels()` on every call so the /// result reflects in-flight HTLCs and reserve as of *now*. /// /// `dest = None` returns a buffer-based estimate; `Some(_)` - /// drives `Node::find_route` and subtracts the real fees. See + /// drives `Node::find_route` and subtracts the real fees. An + /// LNURL-pay destination triggers a callback fetch to obtain a + /// concrete BOLT11 invoice before routing. See /// [`crate::mdk::max_sendable`] for the full dispatch table. - pub fn max_sendable( + pub async fn max_sendable( &self, dest: Option<&PaymentInstructions>, ) -> Result { @@ -162,13 +175,19 @@ impl MdkClient { .iter() .map(ChannelSnapshot::from) .collect(); + // `with_client` (not `new`) so LNURL fetches go through the + // configured SOCKS proxy. A default `HTTPHrnResolver::new()` + // would build its own client and bypass the proxy, leaking IP. + let resolver = HTTPHrnResolver::with_client(self.http_client.clone()); max_sendable::compute_estimate( dest, &snaps, &self.lsp_pubkey, &self.max_sendable_cfg, + &resolver, |rp| self.node.find_route(rp).map_err(|e| format!("{e}")), ) + .await } /// Splice `amount_sats` of confirmed on-chain funds into the diff --git a/src/mdk/max_sendable.rs b/src/mdk/max_sendable.rs index 6e89b69..8fde3ba 100644 --- a/src/mdk/max_sendable.rs +++ b/src/mdk/max_sendable.rs @@ -2,33 +2,43 @@ //! out of mdk's LSP channel(s), with routing fees subtracted. //! //! Entry point: [`compute_estimate`]. The caller collects channel -//! state and provides a `find_route` closure; this module owns the -//! `dest` dispatch and the choice between buffer and route-based -//! estimators. +//! state and provides a `find_route` closure plus an `HrnResolver`; +//! this module owns the `dest` dispatch and the choice between +//! buffer and route-based estimators. //! //! # Coverage //! -//! | Destination | Behaviour | -//! |------------------------------|----------------------------| -//! | None | buffer | -//! | BOLT11, amount set by payee | `Err(FixedAmount)` | -//! | BOLT11, zero-amount | route-fee estimate | -//! | BOLT12 offer | buffer (TODO) | -//! | LNURL-pay | buffer (TODO) | -//! | HRN (BIP 353 / LN address) | buffer (TODO) | -//! | Onchain only | `Err(NoLightningMethod)` | -//! | `find_route` fails | `Err(RoutingFailure(msg))` | +//! | Destination | Behaviour | +//! |----------------------------------------|---------------------------------| +//! | None | buffer | +//! | BOLT11, amount set by payee | `Err(FixedAmount)` | +//! | BOLT11, zero-amount | route-fee estimate | +//! | BOLT12 offer | buffer (TODO) | +//! | LNURL-pay, balance >= min_value | route-fee estimate | +//! | LNURL-pay, balance < min_value | `Err(BelowLnurlMin)` | +//! | HRN (BIP 353) | dispatches as the resolved BIP | +//! | | 321 methods (typically BOLT12) | +//! | HRN (LN address) -> LNURL-pay | as LNURL-pay above | +//! | Onchain only | `Err(NoLightningMethod)` | +//! | `find_route` fails | `Err(RoutingFailure(msg))` | +//! | LNURL fetch/validation fails | `Err(LnurlResolutionFailed)` | //! -//! TODO rows fall back to the buffer rather than overstate. BOLT12 -//! needs a `from_bolt12_invoice` route once the invoice fetch lands -//! upstream. LNURL-pay and HRN destinations need to be resolved into -//! a concrete BOLT11/BOLT12 invoice before this module can route -//! against them; resolution will move into this module shortly. +//! BOLT12 still falls back to the buffer until `from_bolt12_invoice` +//! lands upstream. HRNs are resolved at `PaymentInstructions::parse` +//! time: BIP 353 names resolve to a BIP 321 `bitcoin:` URI carrying +//! whatever reusable methods the recipient publishes (most commonly +//! a BOLT12 offer, sometimes BOLT11 or on-chain fallbacks), and +//! LN-Address names look like LNURL-pay. Whichever method +//! `pick_strategy` reaches first wins per the iteration order of +//! `methods()`. use std::time::Instant; +use bitcoin_payment_instructions::amount::Amount as InstructionAmount; +use bitcoin_payment_instructions::hrn_resolution::HrnResolver; use bitcoin_payment_instructions::{ - PaymentInstructions, PaymentMethod, PossiblyResolvedPaymentMethod, + ConfigurableAmountPaymentInstructions, PaymentInstructions, PaymentMethod, + PossiblyResolvedPaymentMethod, }; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::lightning_invoice::Bolt11Invoice as LdkBolt11Invoice; @@ -97,6 +107,15 @@ pub enum MaxSendableError { /// `Node::find_route` failed. Lossy on purpose: the caller's /// only useful action is to retry or surface "no route". RoutingFailure(String), + /// LNURL-pay callback fetch, validation, or BOLT11 round-trip + /// failed. Wraps the underlying message verbatim. + LnurlResolutionFailed(String), + /// Current outbound balance is below the LNURL-pay endpoint's + /// `min_value`, so no payment is possible. Distinct from + /// `Ok(amount_msat = 0)` (dust against the fee buffer) so the + /// caller can render a recipient-specific message such as + /// "minimum send is N sats". + BelowLnurlMin { min_msat: u64, balance_msat: u64 }, } /// Minimal projection of `ldk_node::ChannelDetails` carrying only @@ -120,17 +139,21 @@ impl From<&ChannelDetails> for ChannelSnapshot { /// Top-level entry point. Picks an [`EstimationStrategy`] for /// `dest` and folds the result into a [`MaxSendableEstimate`]. -/// `find_route` is the only effect; everything else is pure -/// dispatch. See the module-level coverage table. -pub(crate) fn compute_estimate( +/// `find_route` and `resolver` are the only effects; everything +/// else is pure dispatch. `resolver` is invoked only when `dest` +/// carries an unresolved LNURL-pay endpoint. See the module-level +/// coverage table. +pub(crate) async fn compute_estimate( dest: Option<&PaymentInstructions>, channels: &[ChannelSnapshot], lsp_pubkey: &PublicKey, cfg: &MaxSendableConfig, + resolver: &R, find_route: F, ) -> Result where F: FnOnce(RouteParameters) -> Result, + R: HrnResolver, { let balance_msat = sum_outbound_balance(channels, lsp_pubkey)?; match dest { @@ -144,24 +167,37 @@ where .unwrap_or(0), }), Some(PaymentInstructions::ConfigurableAmount(inst)) => { - match pick_strategy(inst.methods())? { + match pick_strategy(inst, balance_msat, resolver).await? { EstimationStrategy::Buffer => Ok(subtract_fee_buffer(balance_msat, cfg)), EstimationStrategy::FromRoute(payment_params) => { - let route_params = RouteParameters::from_payment_params_and_value( - payment_params, - balance_msat, - ); - // TODO: Post channel consolidation, consider setting this to one to not use MPP - // route_params.payment_params.max_path_count = 1; - let route = - find_route(route_params).map_err(MaxSendableError::RoutingFailure)?; - Ok(estimate_from_route(balance_msat, &route, cfg)) + estimate_via_payment_params(balance_msat, payment_params, cfg, find_route) } } } } } +/// Build `RouteParameters` from `payment_params` and `balance_msat`, +/// call `find_route`, then fold the resulting `Route` into a +/// `MaxSendableEstimate`. Extracted so the upcoming LNURL-pay branch +/// of [`compute_estimate`] can reuse the same routing tail after it +/// fetches the BOLT11 invoice. +fn estimate_via_payment_params( + balance_msat: u64, + payment_params: PaymentParameters, + cfg: &MaxSendableConfig, + find_route: F, +) -> Result +where + F: FnOnce(RouteParameters) -> Result, +{ + let route_params = RouteParameters::from_payment_params_and_value(payment_params, balance_msat); + // TODO: Post channel consolidation, consider setting this to one to not use MPP + // route_params.payment_params.max_path_count = 1; + let route = find_route(route_params).map_err(MaxSendableError::RoutingFailure)?; + Ok(estimate_from_route(balance_msat, &route, cfg)) +} + /// Subtract the configured fee buffer from a known outbound balance. fn subtract_fee_buffer(balance_msat: u64, cfg: &MaxSendableConfig) -> MaxSendableEstimate { // u128 intermediate dodges overflow at the percentage step. @@ -228,32 +264,35 @@ enum EstimationStrategy { } /// Pick the first Lightning method and decide how to price it. -/// On-chain and unresolved LNURL methods are skipped; an empty -/// result yields `NoLightningMethod`. +/// `inst.methods()` yields resolved methods before LNURL, so a +/// destination carrying both a concrete BOLT11 and an LNURL +/// fallback prefers the BOLT11. On-chain methods are skipped; an +/// empty result yields `NoLightningMethod`. /// -/// BOLT11 round-trips through bech32: bitcoin-payment-instructions -/// pulls upstream rust-lightning's `Bolt11Invoice`, ldk-node pulls -/// the moneydevkit fork — distinct types in the dep graph, -/// identical wire format. -fn pick_strategy<'a, I>(methods: I) -> Result -where - I: IntoIterator>, -{ - for method in methods { +/// LNURL-pay calls `set_amount` for `min(balance, max_value)` to +/// fetch the actual invoice the payer would receive at the largest +/// plausible amount, then prices it route-based. Sub-`min_value` +/// balances short-circuit to `Err(BelowLnurlMin)`. +async fn pick_strategy( + inst: &ConfigurableAmountPaymentInstructions, + balance_msat: u64, + resolver: &R, +) -> Result { + for method in inst.methods() { match method { PossiblyResolvedPaymentMethod::Resolved(PaymentMethod::LightningBolt11(inv)) => { - return Ok(match inv.to_string().parse::() { - Ok(ldk_inv) => EstimationStrategy::FromRoute( - PaymentParameters::from_bolt11_invoice(&ldk_inv), - ), - Err(_) => EstimationStrategy::Buffer, - }); + return Ok(bolt11_to_strategy(&inv.to_string())); } PossiblyResolvedPaymentMethod::Resolved(PaymentMethod::LightningBolt12(_)) => { return Ok(EstimationStrategy::Buffer); } - PossiblyResolvedPaymentMethod::LNURLPay { .. } => { - return Ok(EstimationStrategy::Buffer); + PossiblyResolvedPaymentMethod::LNURLPay { + min_value, + max_value, + .. + } => { + return resolve_lnurl_strategy(inst, balance_msat, min_value, max_value, resolver) + .await; } _ => continue, } @@ -261,9 +300,63 @@ where Err(MaxSendableError::NoLightningMethod) } +/// Round-trip a BOLT11 invoice string into ldk-node's `Bolt11Invoice` +/// type and wrap it in `PaymentParameters`. +fn bolt11_to_strategy(bolt11_str: &str) -> EstimationStrategy { + match bolt11_str.parse::() { + Ok(ldk_inv) => { + EstimationStrategy::FromRoute(PaymentParameters::from_bolt11_invoice(&ldk_inv)) + } + Err(_) => EstimationStrategy::Buffer, + } +} + +/// Fetch a concrete BOLT11 invoice for an LNURL-pay endpoint at +/// `min(balance, max_value)`, then price it route-based. +async fn resolve_lnurl_strategy( + inst: &ConfigurableAmountPaymentInstructions, + balance_msat: u64, + min_value: InstructionAmount, + max_value: InstructionAmount, + resolver: &R, +) -> Result { + let min_msat = min_value.milli_sats(); + if balance_msat < min_msat { + return Err(MaxSendableError::BelowLnurlMin { + min_msat, + balance_msat, + }); + } + let target_msat = balance_msat.min(max_value.milli_sats()); + let amount = InstructionAmount::from_milli_sats(target_msat).map_err(|_| { + MaxSendableError::LnurlResolutionFailed(format!( + "target amount {target_msat} msat exceeds Amount bounds" + )) + })?; + let fixed = inst + .clone() + .set_amount(amount, resolver) + .await + .map_err(|e| MaxSendableError::LnurlResolutionFailed(e.to_string()))?; + let bolt11 = fixed + .methods() + .iter() + .find_map(|m| match m { + PaymentMethod::LightningBolt11(inv) => Some(inv), + _ => None, + }) + .ok_or_else(|| { + MaxSendableError::LnurlResolutionFailed( + "LNURL resolution yielded no BOLT11 invoice".into(), + ) + })?; + Ok(bolt11_to_strategy(&bolt11.to_string())) +} + #[cfg(test)] mod tests { use super::*; + use bitcoin_payment_instructions::hrn_resolution::DummyHrnResolver; use ldk_node::lightning::routing::router::{Path, RouteHop}; use ldk_node::lightning::types::features::{ChannelFeatures, NodeFeatures}; use std::str::FromStr; @@ -288,14 +381,17 @@ mod tests { /// Run the public `compute_estimate` with `dest = None`. The /// closure panics if invoked — the None path must never route. - fn buffer_estimate( + /// Resolver is `DummyHrnResolver` because LNURL is unreachable + /// with `dest = None`. + async fn buffer_estimate( chans: &[ChannelSnapshot], lsp: &PublicKey, cfg: &MaxSendableConfig, ) -> Result { - compute_estimate(None, chans, lsp, cfg, |_| { + compute_estimate(None, chans, lsp, cfg, &DummyHrnResolver, |_| { panic!("None dest must not invoke find_route") }) + .await } /// Build a `Route` whose paths carry the given per-hop fee_msat @@ -324,83 +420,93 @@ mod tests { } } - #[test] - fn no_usable_channel_when_empty() { - let res = buffer_estimate(&[], &lsp(), &MaxSendableConfig::default()); + #[tokio::test] + async fn no_usable_channel_when_empty() { + let res = buffer_estimate(&[], &lsp(), &MaxSendableConfig::default()).await; assert!(matches!(res, Err(MaxSendableError::NoUsableChannel))); } - #[test] - fn no_usable_channel_when_only_other_counterparty() { + #[tokio::test] + async fn no_usable_channel_when_only_other_counterparty() { let lsp = lsp(); let chans = [snap(other_peer(), true, 100_000_000)]; - let res = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()); + let res = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()).await; assert!(matches!(res, Err(MaxSendableError::NoUsableChannel))); } - #[test] - fn no_usable_channel_when_lsp_channel_unusable() { + #[tokio::test] + async fn no_usable_channel_when_lsp_channel_unusable() { // Mid-open/splice — distinct from "balance is zero". let lsp = lsp(); let chans = [snap(lsp, false, 100_000_000)]; - let res = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()); + let res = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()).await; assert!(matches!(res, Err(MaxSendableError::NoUsableChannel))); } - #[test] - fn dust_balance_below_floor_returns_zero() { + #[tokio::test] + async fn dust_balance_below_floor_returns_zero() { // 5 sats < 10-sat floor → buffer wins, amount saturates to 0. let lsp = lsp(); let chans = [snap(lsp, true, 5_000)]; - let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()).unwrap(); + let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()) + .await + .unwrap(); assert_eq!(est.amount_msat, 0); assert_eq!(est.fee_budget_msat, 10_000); assert_eq!(est.balance_at_compute_msat, 5_000); } - #[test] - fn balance_exactly_equals_buffer_returns_zero() { + #[tokio::test] + async fn balance_exactly_equals_buffer_returns_zero() { let lsp = lsp(); let chans = [snap(lsp, true, 10_000)]; - let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()).unwrap(); + let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()) + .await + .unwrap(); assert_eq!(est.amount_msat, 0); assert_eq!(est.fee_budget_msat, 10_000); } - #[test] - fn normal_case_percentage_buffer_dominates() { + #[tokio::test] + async fn normal_case_percentage_buffer_dominates() { // 100k sats × 1% = 1000 sats > 10-sat floor → percentage wins. let lsp = lsp(); let chans = [snap(lsp, true, 100_000_000)]; - let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()).unwrap(); + let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()) + .await + .unwrap(); assert_eq!(est.fee_budget_msat, 1_000_000); assert_eq!(est.amount_msat, 99_000_000); assert_eq!(est.balance_at_compute_msat, 100_000_000); } - #[test] - fn normal_case_floor_buffer_dominates() { + #[tokio::test] + async fn normal_case_floor_buffer_dominates() { // 500 sats × 1% = 5 sats < 10-sat floor → floor wins. let lsp = lsp(); let chans = [snap(lsp, true, 500_000)]; - let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()).unwrap(); + let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()) + .await + .unwrap(); assert_eq!(est.fee_budget_msat, 10_000); assert_eq!(est.amount_msat, 490_000); } - #[test] - fn two_usable_lsp_channels_sum() { + #[tokio::test] + async fn two_usable_lsp_channels_sum() { // No single-channel assumption: two usable LSP channels sum. let lsp = lsp(); let chans = [snap(lsp, true, 50_000_000), snap(lsp, true, 30_000_000)]; - let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()).unwrap(); + let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()) + .await + .unwrap(); assert_eq!(est.balance_at_compute_msat, 80_000_000); assert_eq!(est.fee_budget_msat, 800_000); assert_eq!(est.amount_msat, 79_200_000); } - #[test] - fn mixed_channels_only_usable_lsp_contributes() { + #[tokio::test] + async fn mixed_channels_only_usable_lsp_contributes() { // Non-LSP and unusable-LSP entries are filtered out. let lsp = lsp(); let other = other_peer(); @@ -409,14 +515,16 @@ mod tests { snap(other, true, 50_000_000), snap(lsp, false, 100_000_000), ]; - let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()).unwrap(); + let est = buffer_estimate(&chans, &lsp, &MaxSendableConfig::default()) + .await + .unwrap(); assert_eq!(est.balance_at_compute_msat, 10_000_000); assert_eq!(est.fee_budget_msat, 100_000); assert_eq!(est.amount_msat, 9_900_000); } - #[test] - fn overrides_take_effect() { + #[tokio::test] + async fn overrides_take_effect() { let lsp = lsp(); let chans = [snap(lsp, true, 1_000_000_000)]; let cfg = MaxSendableConfig { @@ -424,7 +532,7 @@ mod tests { fee_buffer_floor_sats: 50, ..MaxSendableConfig::default() }; - let est = buffer_estimate(&chans, &lsp, &cfg).unwrap(); + let est = buffer_estimate(&chans, &lsp, &cfg).await.unwrap(); assert_eq!(est.fee_budget_msat, 20_000_000); assert_eq!(est.amount_msat, 980_000_000); }