mirror of
https://github.com/daywalker90/cln-nip47.git
synced 2026-08-13 12:33:43 +02:00
add support for holdinvoices
This commit is contained in:
parent
140d295eec
commit
c52cc5f162
18 changed files with 2192 additions and 207 deletions
|
|
@ -1,5 +1,11 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
- holdinvoice methods: ``make_hold_invoice``, ``cancel_hold_invoice``, ``settle_hold_invoice``
|
||||
- holdinvoice notification: ``hold_invoice_accepted``
|
||||
|
||||
## [0.1.9] 2026-04-23
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
476
Cargo.lock
generated
476
Cargo.lock
generated
|
|
@ -44,6 +44,17 @@ version = "0.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-utility"
|
||||
version = "0.3.1"
|
||||
|
|
@ -81,6 +92,12 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef49f5882e4b6afaac09ad239a4f8c70a24b8f2b0897edb1f706008efd109cf4"
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "base58ck"
|
||||
version = "0.1.0"
|
||||
|
|
@ -282,10 +299,14 @@ dependencies = [
|
|||
"log-panics",
|
||||
"nostr-sdk",
|
||||
"parking_lot",
|
||||
"prost",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tonic",
|
||||
"tonic-prost",
|
||||
"tonic-prost-build",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
|
@ -386,12 +407,40 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
|
|
@ -555,6 +604,25 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
|
|
@ -566,9 +634,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
|
|
@ -616,12 +684,96 @@ dependencies = [
|
|||
"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 = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-timeout"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
|
||||
dependencies = [
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.1.1"
|
||||
|
|
@ -737,7 +889,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
|
@ -764,6 +916,15 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
|
|
@ -772,9 +933,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.94"
|
||||
version = "0.3.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
|
|
@ -800,6 +961,12 @@ version = "0.2.186"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.2"
|
||||
|
|
@ -862,6 +1029,12 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
||||
|
||||
[[package]]
|
||||
name = "negentropy"
|
||||
version = "0.5.0"
|
||||
|
|
@ -1017,6 +1190,37 @@ version = "2.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
|
|
@ -1071,6 +1275,79 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-build"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"itertools",
|
||||
"log",
|
||||
"multimap",
|
||||
"petgraph",
|
||||
"prettyplease",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"pulldown-cmark",
|
||||
"pulldown-cmark-to-cmark",
|
||||
"regex",
|
||||
"syn",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7"
|
||||
dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark-to-cmark"
|
||||
version = "22.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90"
|
||||
dependencies = [
|
||||
"pulldown-cmark",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
|
|
@ -1105,9 +1382,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
|
|
@ -1203,12 +1480,26 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
|
|
@ -1422,6 +1713,12 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
|
|
@ -1433,6 +1730,19 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
|
|
@ -1595,6 +1905,105 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"bytes",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-timeout",
|
||||
"hyper-util",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"socket2",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-stream",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-build"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734"
|
||||
dependencies = [
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-prost"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost",
|
||||
"tonic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-prost-build"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a"
|
||||
dependencies = [
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"prost-build",
|
||||
"prost-types",
|
||||
"quote",
|
||||
"syn",
|
||||
"tempfile",
|
||||
"tonic-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
|
@ -1657,6 +2066,12 @@ 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 = "tungstenite"
|
||||
version = "0.26.2"
|
||||
|
|
@ -1668,7 +2083,7 @@ dependencies = [
|
|||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
"rand 0.9.4",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
|
|
@ -1682,6 +2097,12 @@ version = "1.20.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
|
|
@ -1767,6 +2188,15 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[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"
|
||||
|
|
@ -1793,9 +2223,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.117"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
|
|
@ -1806,9 +2236,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.67"
|
||||
version = "0.4.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e"
|
||||
checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
|
@ -1816,9 +2246,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.117"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
|
@ -1826,9 +2256,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.117"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
|
|
@ -1839,9 +2269,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.117"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
|
@ -1882,9 +2312,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.94"
|
||||
version = "0.3.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a"
|
||||
checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
|
@ -1896,14 +2326,14 @@ version = "0.26.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||
dependencies = [
|
||||
"webpki-roots 1.0.6",
|
||||
"webpki-roots 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
|
||||
checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
|
|
|||
11
Cargo.toml
11
Cargo.toml
|
|
@ -27,6 +27,17 @@ hex = "0.4"
|
|||
|
||||
regex = "1"
|
||||
|
||||
tonic = { version = "0.14", default-features = false, features = [
|
||||
"codegen",
|
||||
"transport",
|
||||
"tls-ring",
|
||||
] }
|
||||
prost = "0.14"
|
||||
tonic-prost = "0.14"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-prost-build = "0.14"
|
||||
|
||||
|
||||
[profile.optimized]
|
||||
inherits = "release"
|
||||
|
|
|
|||
|
|
@ -127,6 +127,9 @@ For a private relay you can for example use [nostr-rs-relay](https://github.com/
|
|||
* list all NWC configurations or just the one with ``label``
|
||||
* ***label***: optional. The label the NWC was created with
|
||||
|
||||
## Holdinvoice support
|
||||
For methods or notifications related to holdinvoices you need v0.3.2+ of [hold](https://github.com/BoltzExchange/hold) with enabled grpc (which is the default just make sure the port is free)
|
||||
|
||||
## Supported NWC methods
|
||||
* ``pay_invoice``
|
||||
* ``multi_pay_invoice``
|
||||
|
|
@ -137,10 +140,14 @@ For a private relay you can for example use [nostr-rs-relay](https://github.com/
|
|||
* ``list_transactions``
|
||||
* ``get_balance``
|
||||
* ``get_info`` (no ``block_hash``)
|
||||
* ``make_hold_invoice`` (requires [Holdinvoice support](#holdinvoice_support))
|
||||
* ``cancel_hold_invoice`` (requires [Holdinvoice support](#holdinvoice_support))
|
||||
* ``settle_hold_invoice`` (requires [Holdinvoice support](#holdinvoice_support))
|
||||
|
||||
## Supported NWC notifications
|
||||
* ``payment_received``
|
||||
* ``payment_sent``
|
||||
* ``hold_invoice_accepted`` (requires [Holdinvoice support](#holdinvoice_support))
|
||||
|
||||
## Supported content encryption:
|
||||
* [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md)
|
||||
|
|
|
|||
6
build.rs
Normal file
6
build.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
fn main() {
|
||||
tonic_prost_build::configure()
|
||||
.protoc_arg("--experimental_allow_proto3_optional")
|
||||
.compile_protos(&["protos/hold.proto"], &["protos"])
|
||||
.unwrap_or_else(|e| panic!("Could not build protos: {e}"));
|
||||
}
|
||||
185
protos/hold.proto
Normal file
185
protos/hold.proto
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package hold;
|
||||
|
||||
service Hold {
|
||||
rpc GetInfo (GetInfoRequest) returns (GetInfoResponse);
|
||||
|
||||
rpc Invoice (InvoiceRequest) returns (InvoiceResponse) {}
|
||||
rpc Inject (InjectRequest) returns (InjectResponse) {}
|
||||
|
||||
rpc List (ListRequest) returns (ListResponse) {}
|
||||
|
||||
rpc Settle (SettleRequest) returns (SettleResponse) {}
|
||||
rpc Cancel (CancelRequest) returns (CancelResponse) {}
|
||||
|
||||
// Cleans cancelled invoices
|
||||
rpc Clean (CleanRequest) returns (CleanResponse) {}
|
||||
|
||||
rpc Track (TrackRequest) returns (stream TrackResponse) {}
|
||||
rpc TrackAll (TrackAllRequest) returns (stream TrackAllResponse) {}
|
||||
|
||||
rpc OnionMessages (stream OnionMessageResponse) returns (stream OnionMessage) {}
|
||||
}
|
||||
|
||||
enum HookAction {
|
||||
Continue = 0;
|
||||
Resolve = 1;
|
||||
}
|
||||
|
||||
message GetInfoRequest {}
|
||||
message GetInfoResponse {
|
||||
string version = 1;
|
||||
}
|
||||
|
||||
message Hop {
|
||||
bytes public_key = 1;
|
||||
uint64 short_channel_id = 2;
|
||||
uint64 base_fee = 3;
|
||||
uint64 ppm_fee = 4;
|
||||
uint64 cltv_expiry_delta = 5;
|
||||
}
|
||||
|
||||
message RoutingHint {
|
||||
repeated Hop hops = 1;
|
||||
}
|
||||
|
||||
message InvoiceRequest {
|
||||
bytes payment_hash = 1;
|
||||
uint64 amount_msat = 2;
|
||||
|
||||
oneof description {
|
||||
string memo = 3;
|
||||
bytes hash = 4;
|
||||
}
|
||||
|
||||
optional uint64 expiry = 5;
|
||||
optional uint64 min_final_cltv_expiry = 6;
|
||||
repeated RoutingHint routing_hints = 7;
|
||||
}
|
||||
message InvoiceResponse {
|
||||
string bolt11 = 1;
|
||||
}
|
||||
|
||||
message InjectRequest {
|
||||
string invoice = 1;
|
||||
optional uint64 min_cltv_expiry = 2;
|
||||
}
|
||||
message InjectResponse {}
|
||||
|
||||
message ListRequest {
|
||||
message Pagination {
|
||||
// Inclusive
|
||||
int64 index_start = 1;
|
||||
uint64 limit = 2;
|
||||
}
|
||||
|
||||
oneof constraint {
|
||||
bytes payment_hash = 1;
|
||||
Pagination pagination = 2;
|
||||
}
|
||||
}
|
||||
|
||||
enum InvoiceState {
|
||||
UNPAID = 0;
|
||||
ACCEPTED = 1;
|
||||
PAID = 2;
|
||||
CANCELLED = 3;
|
||||
}
|
||||
|
||||
message Htlc {
|
||||
int64 id = 1;
|
||||
InvoiceState state = 2;
|
||||
string scid = 3;
|
||||
uint64 channel_id = 4;
|
||||
uint64 msat = 5;
|
||||
uint64 created_at = 6;
|
||||
optional uint64 cltv_expiry = 7;
|
||||
}
|
||||
|
||||
message Invoice {
|
||||
int64 id = 1;
|
||||
bytes payment_hash = 2;
|
||||
optional bytes preimage = 3;
|
||||
string invoice = 4;
|
||||
InvoiceState state = 5;
|
||||
uint64 created_at = 6;
|
||||
optional uint64 settled_at = 8;
|
||||
|
||||
repeated Htlc htlcs = 7;
|
||||
|
||||
optional uint64 min_cltv_expiry = 9;
|
||||
}
|
||||
|
||||
message ListResponse {
|
||||
repeated Invoice invoices = 1;
|
||||
}
|
||||
|
||||
message SettleRequest {
|
||||
bytes payment_preimage = 1;
|
||||
}
|
||||
message SettleResponse {}
|
||||
|
||||
message CancelRequest {
|
||||
bytes payment_hash = 1;
|
||||
}
|
||||
message CancelResponse {}
|
||||
|
||||
message CleanRequest {
|
||||
// Clean everything older than age seconds
|
||||
optional uint64 age = 1;
|
||||
}
|
||||
message CleanResponse {
|
||||
uint64 cleaned = 1;
|
||||
}
|
||||
|
||||
message TrackRequest {
|
||||
bytes payment_hash = 1;
|
||||
}
|
||||
|
||||
message TrackResponse {
|
||||
InvoiceState state = 1;
|
||||
}
|
||||
|
||||
message TrackAllRequest {
|
||||
repeated bytes payment_hashes = 1;
|
||||
}
|
||||
|
||||
message TrackAllResponse {
|
||||
bytes payment_hash = 1;
|
||||
string bolt11 = 2;
|
||||
InvoiceState state = 3;
|
||||
}
|
||||
|
||||
message OnionMessage {
|
||||
message ReplyBlindedPath {
|
||||
message Hop {
|
||||
optional bytes blinded_node_id = 1;
|
||||
optional bytes encrypted_recipient_data = 2;
|
||||
}
|
||||
|
||||
optional bytes first_node_id = 1;
|
||||
optional string first_scid = 2;
|
||||
optional uint64 first_scid_dir = 3;
|
||||
optional bytes first_path_key = 4;
|
||||
repeated Hop hops = 5;
|
||||
}
|
||||
|
||||
message UnknownField {
|
||||
uint64 number = 1;
|
||||
bytes value = 2;
|
||||
}
|
||||
|
||||
uint64 id = 1;
|
||||
optional bytes pathsecret = 2;
|
||||
optional ReplyBlindedPath reply_blindedpath = 3;
|
||||
optional bytes invoice_request = 4;
|
||||
optional bytes invoice = 5;
|
||||
optional bytes invoice_error = 6;
|
||||
repeated UnknownField unknown_fields = 7;
|
||||
}
|
||||
|
||||
message OnionMessageResponse {
|
||||
uint64 id = 1;
|
||||
HookAction action = 2;
|
||||
}
|
||||
124
src/main.rs
124
src/main.rs
|
|
@ -1,4 +1,8 @@
|
|||
use std::{path::Path, time::Duration};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use cln_plugin::{
|
||||
|
|
@ -12,10 +16,17 @@ use nwc::run_nwc;
|
|||
use nwc_notifications::{payment_received_handler, payment_sent_handler};
|
||||
use parse::read_startup_options;
|
||||
use rpc::{nwc_budget, nwc_create, nwc_list, nwc_revoke};
|
||||
use serde_json::json;
|
||||
use structs::PluginState;
|
||||
use tokio::time;
|
||||
use tonic::transport::{Certificate, ClientTlsConfig, Endpoint, Identity};
|
||||
use util::{load_nwc_store, update_nwc_store};
|
||||
|
||||
use crate::{
|
||||
hold::{hold_client::HoldClient, InvoiceState, ListRequest},
|
||||
nwc_notifications::holdinvoice_accepted_handler,
|
||||
};
|
||||
|
||||
mod nwc;
|
||||
mod nwc_balance;
|
||||
mod nwc_hold;
|
||||
|
|
@ -32,6 +43,9 @@ mod tasks;
|
|||
mod util;
|
||||
|
||||
pub const STARTUP_DELAY: u64 = 1;
|
||||
pub mod hold {
|
||||
tonic::include_proto!("hold");
|
||||
}
|
||||
|
||||
const OPT_RELAYS: StringArrayConfigOption = ConfigOption::new_str_arr_no_default(
|
||||
"nip47-relays",
|
||||
|
|
@ -56,10 +70,17 @@ pub const WALLET_PAY_METHODS: [nip47::Method; 4] = [
|
|||
nip47::Method::PayKeysend,
|
||||
nip47::Method::MultiPayKeysend,
|
||||
];
|
||||
pub const WALLET_HOLD_METHODS: [nip47::Method; 3] = [
|
||||
nip47::Method::MakeHoldInvoice,
|
||||
nip47::Method::CancelHoldInvoice,
|
||||
nip47::Method::SettleHoldInvoice,
|
||||
];
|
||||
pub const WALLET_NOTIFICATIONS: [nip47::NotificationType; 2] = [
|
||||
nip47::NotificationType::PaymentReceived,
|
||||
nip47::NotificationType::PaymentSent,
|
||||
];
|
||||
pub const WALLET_HOLD_NOTIFICATIONS: [nip47::NotificationType; 1] =
|
||||
[nip47::NotificationType::HoldInvoiceAccepted];
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
|
|
@ -109,6 +130,16 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
};
|
||||
let plugin = confplugin.start(state).await?;
|
||||
|
||||
match check_hold_support(plugin.clone()).await {
|
||||
Ok(()) => {
|
||||
log::info!("Hold support activated, loading pending invoices...");
|
||||
if let Err(e) = load_pending_hold_invoices(plugin.clone()).await {
|
||||
log::error!("Error loading pending hold invoices: {e}");
|
||||
}
|
||||
}
|
||||
Err(e) => log::info!("Hold support not activated: {e}"),
|
||||
}
|
||||
|
||||
{
|
||||
let mut rpc = plugin.state().rpc_lock.lock().await;
|
||||
|
||||
|
|
@ -167,3 +198,94 @@ async fn load_nwcs(plugin: Plugin<PluginState>, rpc: &mut ClnRpc) -> Result<(),
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_pending_hold_invoices(plugin: Plugin<PluginState>) -> Result<(), anyhow::Error> {
|
||||
let mut hold_client = plugin.state().hold_client.lock().clone().unwrap();
|
||||
let invoices_request = ListRequest { constraint: None };
|
||||
let invoices = hold_client
|
||||
.list(invoices_request)
|
||||
.await?
|
||||
.into_inner()
|
||||
.invoices;
|
||||
|
||||
for invoice in invoices {
|
||||
if invoice.state() == InvoiceState::Accepted || invoice.state() == InvoiceState::Unpaid {
|
||||
log::debug!(
|
||||
"Starting holdinvoice accepted handler for {}",
|
||||
hex::encode(&invoice.payment_hash)
|
||||
);
|
||||
tokio::spawn(holdinvoice_accepted_handler(
|
||||
plugin.clone(),
|
||||
invoice.payment_hash,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_hold_support(plugin: Plugin<PluginState>) -> Result<(), anyhow::Error> {
|
||||
let mut rpc = plugin.state().rpc_lock.lock().await;
|
||||
let hold_grpc_host_response: serde_json::Value = rpc
|
||||
.call_raw("listconfigs", &json!({"config": "hold-grpc-host"}))
|
||||
.await?;
|
||||
|
||||
let Some(hold_grpc_host_configs) = hold_grpc_host_response.get("configs") else {
|
||||
return Err(anyhow!("Unsopprted listconfigs response!"));
|
||||
};
|
||||
let Some(hold_grpc_host_config) = hold_grpc_host_configs.get("hold-grpc-host") else {
|
||||
return Err(anyhow!("hold-grpc-host config not found"));
|
||||
};
|
||||
let Some(hold_grpc_host_value) = hold_grpc_host_config.get("value_str") else {
|
||||
return Err(anyhow!("hold-grpc-host config not a string"));
|
||||
};
|
||||
let Some(hold_grpc_host) = hold_grpc_host_value.as_str() else {
|
||||
return Err(anyhow!("hold-grpc-host config not convertable to string"));
|
||||
};
|
||||
|
||||
let hold_grpc_port_response: serde_json::Value = rpc
|
||||
.call_raw("listconfigs", &json!({"config": "hold-grpc-port"}))
|
||||
.await?;
|
||||
let Some(hold_grpc_port_configs) = hold_grpc_port_response.get("configs") else {
|
||||
return Err(anyhow!("Unsopprted listconfigs response!"));
|
||||
};
|
||||
let Some(hold_grpc_port_config) = hold_grpc_port_configs.get("hold-grpc-port") else {
|
||||
return Err(anyhow!("hold-grpc-port config not found"));
|
||||
};
|
||||
let Some(hold_grpc_port_value) = hold_grpc_port_config.get("value_int") else {
|
||||
return Err(anyhow!("hold-grpc-port config not a number"));
|
||||
};
|
||||
let hold_grpc_port = if let Some(hgh) = hold_grpc_port_value.as_u64() {
|
||||
u16::try_from(hgh)?
|
||||
} else {
|
||||
return Err(anyhow!("hold-grpc-port config not convertable to integer"));
|
||||
};
|
||||
|
||||
let cert_dir = PathBuf::from_str(&plugin.configuration().lightning_dir)?.join("hold");
|
||||
|
||||
log::debug!(
|
||||
"Searching {} for hold plugin certs",
|
||||
cert_dir.to_str().unwrap()
|
||||
);
|
||||
|
||||
let ca_cert = tokio::fs::read(cert_dir.join("ca.pem")).await?;
|
||||
let client_cert = tokio::fs::read(cert_dir.join("client.pem")).await?;
|
||||
let client_key = tokio::fs::read(cert_dir.join("client-key.pem")).await?;
|
||||
|
||||
let identity = Identity::from_pem(client_cert, client_key);
|
||||
|
||||
let ca = Certificate::from_pem(ca_cert);
|
||||
|
||||
let tls_config = ClientTlsConfig::new()
|
||||
.ca_certificate(ca)
|
||||
.identity(identity)
|
||||
.domain_name("hold");
|
||||
|
||||
let hold_channel = Endpoint::from_shared(format!("https://{hold_grpc_host}:{hold_grpc_port}"))?
|
||||
.tls_config(tls_config)?
|
||||
.keep_alive_while_idle(true)
|
||||
.connect_lazy();
|
||||
*plugin.state().hold_client.lock() = Some(HoldClient::new(hold_channel));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
36
src/nwc.rs
36
src/nwc.rs
|
|
@ -55,29 +55,29 @@ pub async fn run_nwc(
|
|||
);
|
||||
let client_pubkey = Keys::new(nwc_store.uri.secret.clone()).public_key();
|
||||
|
||||
let client = Client::new(wallet_keys.clone());
|
||||
let nostr_client = Client::new(wallet_keys.clone());
|
||||
|
||||
log::debug!("relay_count:{}", nwc_store.uri.relays.len());
|
||||
|
||||
for relay in &nwc_store.uri.relays {
|
||||
log::debug!("Adding relay: {relay}");
|
||||
client.add_relay(relay).await?;
|
||||
nostr_client.add_relay(relay).await?;
|
||||
}
|
||||
|
||||
if nwc_store.interval_config.is_some() {
|
||||
start_nwc_budget_job(&plugin, label.clone());
|
||||
}
|
||||
|
||||
let client_clone = client.clone();
|
||||
let nostr_client_clone = nostr_client.clone();
|
||||
let plugin_clone = plugin.clone();
|
||||
let label_clone = label.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
client_clone.connect().await;
|
||||
client_clone
|
||||
nostr_client_clone.connect().await;
|
||||
nostr_client_clone
|
||||
.wait_for_connection(Duration::from_secs(30))
|
||||
.await;
|
||||
let relays = client_clone.relays().await;
|
||||
let relays = nostr_client_clone.relays().await;
|
||||
if relays.is_empty() {
|
||||
log::info!("No more relays left, we probably shut down. Exiting...");
|
||||
break;
|
||||
|
|
@ -98,14 +98,14 @@ pub async fn run_nwc(
|
|||
|
||||
if let Err(e) = send_nwc_info_event(
|
||||
plugin_clone.clone(),
|
||||
client_clone.clone(),
|
||||
nostr_client_clone.clone(),
|
||||
method_capabilities.clone(),
|
||||
wallet_keys.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
client_clone.disconnect().await;
|
||||
nostr_client_clone.disconnect().await;
|
||||
time::sleep(Duration::from_secs(5)).await;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -115,15 +115,15 @@ pub async fn run_nwc(
|
|||
.author(client_pubkey)
|
||||
.since(Timestamp::now() - STARTUP_DELAY - 1);
|
||||
|
||||
if let Err(e) = client_clone.subscribe(filter, None).await {
|
||||
if let Err(e) = nostr_client_clone.subscribe(filter, None).await {
|
||||
log::warn!("Could not subscribe to nwc events! {e}");
|
||||
client_clone.disconnect().await;
|
||||
nostr_client_clone.disconnect().await;
|
||||
time::sleep(Duration::from_secs(5)).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
let client_clone_handler = client_clone.clone();
|
||||
match client_clone
|
||||
let client_clone_handler = nostr_client_clone.clone();
|
||||
match nostr_client_clone
|
||||
.handle_notifications(|notification| {
|
||||
let client_clone_handler = client_clone_handler.clone();
|
||||
let plugin_clone = plugin_clone.clone();
|
||||
|
|
@ -152,7 +152,7 @@ pub async fn run_nwc(
|
|||
let mut locked_handles = plugin.state().handles.lock().await;
|
||||
locked_handles.insert(
|
||||
label.clone(),
|
||||
(client, Keys::new(nwc_store.uri.secret).public_key()),
|
||||
(nostr_client, Keys::new(nwc_store.uri.secret).public_key()),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -227,7 +227,7 @@ pub fn stop_nwc_budget_job(plugin: &Plugin<PluginState>, label: &String) {
|
|||
|
||||
async fn nwc_request_handler(
|
||||
notification: RelayPoolNotification,
|
||||
client: client::Client,
|
||||
nostr_client: client::Client,
|
||||
plugin: Plugin<PluginState>,
|
||||
label: String,
|
||||
wallet_keys: Keys,
|
||||
|
|
@ -281,13 +281,13 @@ async fn nwc_request_handler(
|
|||
nip47::RequestParams::GetBalance => get_balance_response(plugin.clone(), &label).await,
|
||||
nip47::RequestParams::GetInfo => get_info_response(plugin.clone(), &label).await,
|
||||
nip47::RequestParams::MakeHoldInvoice(make_hold_invoice_request) => {
|
||||
make_hold_invoice_response(plugin.clone(), make_hold_invoice_request, &label).await
|
||||
make_hold_invoice_response(plugin.clone(), make_hold_invoice_request).await
|
||||
}
|
||||
nip47::RequestParams::CancelHoldInvoice(cancel_hold_invoice_request) => {
|
||||
cancel_hold_invoice_response(plugin.clone(), cancel_hold_invoice_request, &label).await
|
||||
cancel_hold_invoice_response(plugin.clone(), cancel_hold_invoice_request).await
|
||||
}
|
||||
nip47::RequestParams::SettleHoldInvoice(settle_hold_invoice_request) => {
|
||||
settle_hold_invoice_response(plugin.clone(), settle_hold_invoice_request, &label).await
|
||||
settle_hold_invoice_response(plugin.clone(), settle_hold_invoice_request).await
|
||||
}
|
||||
};
|
||||
for (response, id) in responses {
|
||||
|
|
@ -309,7 +309,7 @@ async fn nwc_request_handler(
|
|||
}
|
||||
};
|
||||
|
||||
let send_result = match client.send_event(&response_event).await {
|
||||
let send_result = match nostr_client.send_event(&response_event).await {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
log::warn!("Error sending response event! {e}");
|
||||
|
|
|
|||
274
src/nwc_hold.rs
274
src/nwc_hold.rs
|
|
@ -1,58 +1,242 @@
|
|||
use cln_plugin::Plugin;
|
||||
use nostr_sdk::nips::nip47;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::structs::PluginState;
|
||||
use cln_plugin::Plugin;
|
||||
use cln_rpc::primitives::Sha256;
|
||||
use nostr_sdk::{nips::nip47, Timestamp};
|
||||
|
||||
use crate::{
|
||||
hold::{invoice_request::Description, CancelRequest, InvoiceRequest, SettleRequest},
|
||||
nwc_notifications::holdinvoice_accepted_handler,
|
||||
structs::PluginState,
|
||||
};
|
||||
|
||||
pub async fn make_hold_invoice_response(
|
||||
_plugin: Plugin<PluginState>,
|
||||
_params: nip47::MakeHoldInvoiceRequest,
|
||||
_label: &str,
|
||||
plugin: Plugin<PluginState>,
|
||||
params: nip47::MakeHoldInvoiceRequest,
|
||||
) -> Vec<(nip47::Response, Option<String>)> {
|
||||
vec![(
|
||||
nip47::Response {
|
||||
result_type: nip47::Method::MakeHoldInvoice,
|
||||
error: Some(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::NotImplemented,
|
||||
message: "Not implemented".to_owned(),
|
||||
}),
|
||||
result: None,
|
||||
},
|
||||
None,
|
||||
)]
|
||||
vec![match make_hold_invoice(plugin, params).await {
|
||||
Ok(o) => (
|
||||
nip47::Response {
|
||||
result_type: nip47::Method::MakeHoldInvoice,
|
||||
error: None,
|
||||
result: Some(nip47::ResponseResult::MakeHoldInvoice(o)),
|
||||
},
|
||||
None,
|
||||
),
|
||||
Err(e) => (
|
||||
nip47::Response {
|
||||
result_type: nip47::Method::MakeHoldInvoice,
|
||||
error: Some(e),
|
||||
result: None,
|
||||
},
|
||||
None,
|
||||
),
|
||||
}]
|
||||
}
|
||||
|
||||
async fn make_hold_invoice(
|
||||
plugin: Plugin<PluginState>,
|
||||
params: nip47::MakeHoldInvoiceRequest,
|
||||
) -> Result<nip47::MakeHoldInvoiceResponse, nip47::NIP47Error> {
|
||||
let Some(mut hold_client) = plugin.state().hold_client.lock().clone() else {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::NotImplemented,
|
||||
message: "No hold plugin found".to_owned(),
|
||||
});
|
||||
};
|
||||
|
||||
let description: Option<Description> = if let Some(d_hash) = ¶ms.description_hash {
|
||||
if let Some(description) = ¶ms.description {
|
||||
let my_description_hash = Sha256::const_hash(description.as_bytes());
|
||||
let description_hash = Sha256::from_str(d_hash).map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
if my_description_hash != description_hash {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "description_hash not matching description".to_owned(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let desc_hash_bytes = match hex::decode(d_hash) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Could not convert description hash to bytes".to_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
Some(Description::Hash(desc_hash_bytes))
|
||||
} else {
|
||||
params
|
||||
.description
|
||||
.as_ref()
|
||||
.map(|desc| Description::Memo(desc.clone()))
|
||||
};
|
||||
|
||||
let payment_hash = match hex::decode(¶ms.payment_hash) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Invalid payment hash".to_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let expiry = params.expiry.unwrap_or(60 * 60);
|
||||
|
||||
let holdinvoice_request = InvoiceRequest {
|
||||
payment_hash: payment_hash.clone(),
|
||||
amount_msat: params.amount,
|
||||
expiry: Some(expiry),
|
||||
min_final_cltv_expiry: params.cltv_expiry_delta.map(u64::from),
|
||||
routing_hints: Vec::new(),
|
||||
description,
|
||||
};
|
||||
|
||||
let holdinvoice = hold_client
|
||||
.invoice(holdinvoice_request)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: format!("Error creating hold invoice: {e}"),
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
let response = nip47::MakeHoldInvoiceResponse {
|
||||
invoice: Some(holdinvoice.bolt11),
|
||||
transaction_type: nip47::TransactionType::Incoming,
|
||||
description: params.description,
|
||||
description_hash: params.description_hash,
|
||||
amount: params.amount,
|
||||
created_at: Timestamp::now(),
|
||||
expires_at: Timestamp::now() + expiry,
|
||||
metadata: None,
|
||||
payment_hash: params.payment_hash,
|
||||
};
|
||||
|
||||
tokio::spawn(holdinvoice_accepted_handler(plugin, payment_hash));
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn cancel_hold_invoice_response(
|
||||
_plugin: Plugin<PluginState>,
|
||||
_params: nip47::CancelHoldInvoiceRequest,
|
||||
_label: &str,
|
||||
plugin: Plugin<PluginState>,
|
||||
params: nip47::CancelHoldInvoiceRequest,
|
||||
) -> Vec<(nip47::Response, Option<String>)> {
|
||||
vec![(
|
||||
nip47::Response {
|
||||
result_type: nip47::Method::MakeHoldInvoice,
|
||||
error: Some(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::NotImplemented,
|
||||
message: "Not implemented".to_owned(),
|
||||
}),
|
||||
result: None,
|
||||
},
|
||||
None,
|
||||
)]
|
||||
vec![match cancel_hold_invoice(plugin, params).await {
|
||||
Ok(o) => (
|
||||
nip47::Response {
|
||||
result_type: nip47::Method::CancelHoldInvoice,
|
||||
error: None,
|
||||
result: Some(nip47::ResponseResult::CancelHoldInvoice(o)),
|
||||
},
|
||||
None,
|
||||
),
|
||||
Err(e) => (
|
||||
nip47::Response {
|
||||
result_type: nip47::Method::CancelHoldInvoice,
|
||||
error: Some(e),
|
||||
result: None,
|
||||
},
|
||||
None,
|
||||
),
|
||||
}]
|
||||
}
|
||||
|
||||
async fn cancel_hold_invoice(
|
||||
plugin: Plugin<PluginState>,
|
||||
params: nip47::CancelHoldInvoiceRequest,
|
||||
) -> Result<nip47::CancelHoldInvoiceResponse, nip47::NIP47Error> {
|
||||
let Some(mut hold_client) = plugin.state().hold_client.lock().clone() else {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::NotImplemented,
|
||||
message: "No hold plugin found".to_owned(),
|
||||
});
|
||||
};
|
||||
|
||||
let payment_hash = match hex::decode(¶ms.payment_hash) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Invalid payment hash".to_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let hold_cancel_request = CancelRequest { payment_hash };
|
||||
|
||||
let hold_cancel_response = hold_client.cancel(hold_cancel_request).await;
|
||||
|
||||
match hold_cancel_response {
|
||||
Ok(_o) => Ok(nip47::CancelHoldInvoiceResponse {}),
|
||||
Err(e) => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn settle_hold_invoice_response(
|
||||
_plugin: Plugin<PluginState>,
|
||||
_params: nip47::SettleHoldInvoiceRequest,
|
||||
_label: &str,
|
||||
plugin: Plugin<PluginState>,
|
||||
params: nip47::SettleHoldInvoiceRequest,
|
||||
) -> Vec<(nip47::Response, Option<String>)> {
|
||||
vec![(
|
||||
nip47::Response {
|
||||
result_type: nip47::Method::MakeHoldInvoice,
|
||||
error: Some(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::NotImplemented,
|
||||
message: "Not implemented".to_owned(),
|
||||
}),
|
||||
result: None,
|
||||
},
|
||||
None,
|
||||
)]
|
||||
vec![match settle_hold_invoice(plugin, params).await {
|
||||
Ok(o) => (
|
||||
nip47::Response {
|
||||
result_type: nip47::Method::SettleHoldInvoice,
|
||||
error: None,
|
||||
result: Some(nip47::ResponseResult::SettleHoldInvoice(o)),
|
||||
},
|
||||
None,
|
||||
),
|
||||
Err(e) => (
|
||||
nip47::Response {
|
||||
result_type: nip47::Method::SettleHoldInvoice,
|
||||
error: Some(e),
|
||||
result: None,
|
||||
},
|
||||
None,
|
||||
),
|
||||
}]
|
||||
}
|
||||
|
||||
async fn settle_hold_invoice(
|
||||
plugin: Plugin<PluginState>,
|
||||
params: nip47::SettleHoldInvoiceRequest,
|
||||
) -> Result<nip47::SettleHoldInvoiceResponse, nip47::NIP47Error> {
|
||||
let Some(mut hold_client) = plugin.state().hold_client.lock().clone() else {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::NotImplemented,
|
||||
message: "No hold plugin found".to_owned(),
|
||||
});
|
||||
};
|
||||
|
||||
let preimage = match hex::decode(¶ms.preimage) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Invalid preimage".to_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let hold_settle_request = SettleRequest {
|
||||
payment_preimage: preimage,
|
||||
};
|
||||
|
||||
let hold_settle_response = hold_client.settle(hold_settle_request).await;
|
||||
|
||||
match hold_settle_response {
|
||||
Ok(_o) => Ok(nip47::SettleHoldInvoiceResponse {}),
|
||||
Err(e) => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use cln_rpc::{
|
|||
model::{
|
||||
requests::{DecodeRequest, ListinvoicesRequest, ListpaysRequest},
|
||||
responses::{
|
||||
DecodeType,
|
||||
ListinvoicesInvoices,
|
||||
ListinvoicesInvoicesStatus,
|
||||
ListpaysPays,
|
||||
|
|
@ -16,7 +17,10 @@ use cln_rpc::{
|
|||
};
|
||||
use nostr_sdk::{nips::nip47, Timestamp};
|
||||
|
||||
use crate::structs::{PluginState, NOT_INV_ERR};
|
||||
use crate::{
|
||||
hold::{self, list_request::Constraint, ListRequest},
|
||||
structs::{PluginState, NOT_INV_ERR},
|
||||
};
|
||||
|
||||
pub async fn lookup_invoice_response(
|
||||
plugin: Plugin<PluginState>,
|
||||
|
|
@ -58,7 +62,7 @@ async fn lookup_invoice(
|
|||
let invoice = if params.payment_hash.is_some() && params.invoice.is_some() {
|
||||
None
|
||||
} else {
|
||||
params.invoice
|
||||
params.invoice.clone()
|
||||
};
|
||||
|
||||
let invoices = rpc
|
||||
|
|
@ -81,47 +85,234 @@ async fn lookup_invoice(
|
|||
if invoices.len() == 1 {
|
||||
let invoice_response = invoices.into_iter().next().unwrap();
|
||||
|
||||
make_lookup_response_from_listinvoices(&mut rpc, invoice_response).await
|
||||
return make_lookup_response_from_listinvoices(&mut rpc, invoice_response).await;
|
||||
}
|
||||
|
||||
let payment_hash_hash = if let Some(hash) = ¶ms.payment_hash {
|
||||
if let Ok(res) = Sha256::from_str(hash) {
|
||||
Some(res)
|
||||
} else {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: "Could not convert payment hash".to_owned(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let payment_hash_hash = if let Some(hash) = params.payment_hash {
|
||||
if let Ok(res) = Sha256::from_str(&hash) {
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let pays = rpc
|
||||
.call_typed(&ListpaysRequest {
|
||||
bolt11: invoice,
|
||||
index: None,
|
||||
limit: None,
|
||||
payment_hash: payment_hash_hash,
|
||||
start: None,
|
||||
status: None,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?
|
||||
.pays;
|
||||
|
||||
if pays.len() == 1 {
|
||||
let list_pay = pays.into_iter().next().unwrap();
|
||||
|
||||
return make_lookup_response_from_listpays(&mut rpc, list_pay).await;
|
||||
}
|
||||
|
||||
let holdinvoice_support = plugin.state().hold_client.lock().is_some();
|
||||
|
||||
if holdinvoice_support {
|
||||
let mut hold_client = plugin.state().hold_client.lock().clone().unwrap();
|
||||
|
||||
let (hold_invoice, invoice_decoded) = if let Some(ph) = ¶ms.payment_hash {
|
||||
let payment_hash_hash = match hex::decode(ph) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Invalid payment hash".to_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let list_request = ListRequest {
|
||||
constraint: Some(Constraint::PaymentHash(payment_hash_hash)),
|
||||
};
|
||||
let hold_lookup = hold_client
|
||||
.list(list_request)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: format!("Could not fetch hold invoice: {e}"),
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
if hold_lookup.invoices.len() != 1 {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: "Could not convert payment hash".to_owned(),
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Transaction not found".to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
let hold_invoice = hold_lookup.invoices.into_iter().next().unwrap();
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: hold_invoice.invoice.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
|
||||
(hold_invoice, invoice_decoded)
|
||||
} else {
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: params.invoice.unwrap(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
let ph = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.invoice_payment_hash.unwrap(),
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.payment_hash.unwrap().to_string(),
|
||||
_ => todo!(),
|
||||
};
|
||||
let payment_hash_hash = match hex::decode(&ph) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Invalid payment hash in invoice".to_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let list_request = ListRequest {
|
||||
constraint: Some(Constraint::PaymentHash(payment_hash_hash)),
|
||||
};
|
||||
|
||||
let hold_lookup = hold_client
|
||||
.list(list_request)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: format!("Could not fetch hold invoice: {e}"),
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
if hold_lookup.invoices.len() != 1 {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Transaction not found".to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
let hold_invoice = hold_lookup.invoices.into_iter().next().unwrap();
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: hold_invoice.invoice.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
(hold_invoice, invoice_decoded)
|
||||
};
|
||||
let not_invoice_err = Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: NOT_INV_ERR.to_owned(),
|
||||
});
|
||||
|
||||
let description = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.offer_description,
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.description,
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
let description_hash = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => None,
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.description_hash.map(|h| h.to_string()),
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let created_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => {
|
||||
Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap())
|
||||
}
|
||||
DecodeType::BOLT11_INVOICE => Timestamp::from_secs(invoice_decoded.created_at.unwrap()),
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let amount = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.invoice_amount_msat.unwrap().msat(),
|
||||
DecodeType::BOLT11_INVOICE => {
|
||||
if let Some(amt) = invoice_decoded.amount_msat {
|
||||
amt.msat()
|
||||
} else {
|
||||
// amount: `any` but have to put a value...
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let expires_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded
|
||||
.invoice_relative_expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(u64::from(e_at))),
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded
|
||||
.expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(e_at)),
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let state = match hold_invoice.state() {
|
||||
hold::InvoiceState::Unpaid => nip47::TransactionState::Pending,
|
||||
hold::InvoiceState::Accepted => nip47::TransactionState::Pending, // TODO ACCEPTED STATE
|
||||
hold::InvoiceState::Paid => nip47::TransactionState::Settled,
|
||||
hold::InvoiceState::Cancelled => nip47::TransactionState::Expired,
|
||||
};
|
||||
|
||||
let settled_at = if hold_invoice.settled_at() != 0 {
|
||||
Some(Timestamp::from_secs(hold_invoice.settled_at()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let pays = rpc
|
||||
.call_typed(&ListpaysRequest {
|
||||
bolt11: invoice,
|
||||
index: None,
|
||||
limit: None,
|
||||
payment_hash: payment_hash_hash,
|
||||
start: None,
|
||||
status: None,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?
|
||||
.pays;
|
||||
let preimage = if hold_invoice.state() == hold::InvoiceState::Paid {
|
||||
Some(hex::encode(hold_invoice.preimage()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if pays.len() != 1 {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::NotFound,
|
||||
message: "Transaction not found".to_owned(),
|
||||
});
|
||||
}
|
||||
let list_pay = pays.into_iter().next().unwrap();
|
||||
|
||||
make_lookup_response_from_listpays(&mut rpc, list_pay).await
|
||||
return Ok(nip47::LookupInvoiceResponse {
|
||||
transaction_type: Some(nip47::TransactionType::Incoming),
|
||||
invoice: Some(hold_invoice.invoice.clone()),
|
||||
description,
|
||||
description_hash,
|
||||
preimage,
|
||||
payment_hash: hex::encode(hold_invoice.payment_hash),
|
||||
amount,
|
||||
fees_paid: 0,
|
||||
created_at,
|
||||
expires_at,
|
||||
settled_at,
|
||||
metadata: None,
|
||||
state: Some(state),
|
||||
});
|
||||
}
|
||||
|
||||
Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::NotFound,
|
||||
message: "Transaction not found".to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn list_transactions_response(
|
||||
|
|
@ -193,6 +384,118 @@ async fn list_transactions(
|
|||
Err(_e) => (),
|
||||
}
|
||||
}
|
||||
|
||||
let holdinvoice_support = plugin.state().hold_client.lock().is_some();
|
||||
|
||||
if holdinvoice_support {
|
||||
let mut hold_client = plugin.state().hold_client.lock().clone().unwrap();
|
||||
|
||||
let lookup_request = ListRequest { constraint: None };
|
||||
|
||||
let hold_lookup = hold_client
|
||||
.list(lookup_request)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: format!("Could not fetch hold invoices: {e}"),
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
for hold_invoice in hold_lookup.invoices {
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: hold_invoice.invoice.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
|
||||
let description = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.offer_description,
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.description,
|
||||
_ => continue,
|
||||
};
|
||||
let description_hash = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => None,
|
||||
DecodeType::BOLT11_INVOICE => {
|
||||
invoice_decoded.description_hash.map(|h| h.to_string())
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let created_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => {
|
||||
Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap())
|
||||
}
|
||||
DecodeType::BOLT11_INVOICE => {
|
||||
Timestamp::from_secs(invoice_decoded.created_at.unwrap())
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let amount = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => {
|
||||
invoice_decoded.invoice_amount_msat.unwrap().msat()
|
||||
}
|
||||
DecodeType::BOLT11_INVOICE => {
|
||||
if let Some(amt) = invoice_decoded.amount_msat {
|
||||
amt.msat()
|
||||
} else {
|
||||
// amount: `any` but have to put a value...
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let expires_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded
|
||||
.invoice_relative_expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(u64::from(e_at))),
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded
|
||||
.expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(e_at)),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let state = match hold_invoice.state() {
|
||||
hold::InvoiceState::Unpaid => nip47::TransactionState::Pending,
|
||||
hold::InvoiceState::Accepted => nip47::TransactionState::Pending, // TODO: ACCEPTED STATE
|
||||
hold::InvoiceState::Paid => nip47::TransactionState::Settled,
|
||||
hold::InvoiceState::Cancelled => nip47::TransactionState::Expired,
|
||||
};
|
||||
|
||||
let settled_at = if hold_invoice.settled_at() != 0 {
|
||||
Some(Timestamp::from_secs(hold_invoice.settled_at()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let preimage = if hold_invoice.state() == hold::InvoiceState::Paid {
|
||||
Some(hex::encode(hold_invoice.preimage()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
transactions.push(nip47::LookupInvoiceResponse {
|
||||
transaction_type: Some(nip47::TransactionType::Incoming),
|
||||
invoice: Some(hold_invoice.invoice.clone()),
|
||||
description,
|
||||
description_hash,
|
||||
preimage,
|
||||
payment_hash: hex::encode(hold_invoice.payment_hash),
|
||||
amount,
|
||||
fees_paid: 0,
|
||||
created_at,
|
||||
expires_at,
|
||||
settled_at,
|
||||
metadata: None,
|
||||
state: Some(state),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if query_payments {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::anyhow;
|
|||
use cln_plugin::Plugin;
|
||||
use cln_rpc::{
|
||||
model::{
|
||||
requests::{DecodeRequest, ListinvoicesRequest, ListpaysRequest},
|
||||
requests::{DecodeRequest, ListinvoicesRequest, ListpaysRequest, ListpeerchannelsRequest},
|
||||
responses::{
|
||||
DecodeResponse,
|
||||
ListinvoicesInvoices,
|
||||
|
|
@ -24,6 +24,7 @@ use nostr_sdk::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
hold::{list_request::Constraint, InvoiceState, ListRequest, TrackRequest},
|
||||
structs::{PluginState, NOT_INV_ERR},
|
||||
OPT_NOTIFICATIONS,
|
||||
};
|
||||
|
|
@ -380,3 +381,129 @@ async fn send_notification(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn holdinvoice_accepted_handler(
|
||||
plugin: Plugin<PluginState>,
|
||||
payment_hash: Vec<u8>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let mut hold_client = plugin.state().hold_client.lock().clone().unwrap();
|
||||
|
||||
let track_request = TrackRequest {
|
||||
payment_hash: payment_hash.clone(),
|
||||
};
|
||||
let mut track_stream = hold_client.track(track_request).await?.into_inner();
|
||||
|
||||
while let Some(response) = track_stream.message().await? {
|
||||
log::debug!("Invoice status: {}", response.state().as_str_name());
|
||||
if response.state() == InvoiceState::Accepted {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let list_request = ListRequest {
|
||||
constraint: Some(Constraint::PaymentHash(payment_hash.clone())),
|
||||
};
|
||||
|
||||
let hold_lookup = hold_client.list(list_request).await?.into_inner();
|
||||
|
||||
if hold_lookup.invoices.len() != 1 {
|
||||
return Err(anyhow!("hold plugin did not return exactly one invoice"));
|
||||
}
|
||||
|
||||
let hold_invoice = hold_lookup.invoices.first().unwrap();
|
||||
|
||||
let mut rpc = plugin.state().rpc_lock.lock().await;
|
||||
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: hold_invoice.invoice.clone(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let amount = match invoice_decoded.item_type {
|
||||
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
|
||||
invoice_decoded.invoice_amount_msat.unwrap().msat()
|
||||
}
|
||||
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
|
||||
if let Some(amt) = invoice_decoded.amount_msat {
|
||||
amt.msat()
|
||||
} else {
|
||||
// amount: `any` but have to put a value...
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => return Err(anyhow!("hold plugin did not return an invoice string")),
|
||||
};
|
||||
|
||||
let created_at = match invoice_decoded.item_type {
|
||||
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
|
||||
Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap())
|
||||
}
|
||||
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
|
||||
Timestamp::from_secs(invoice_decoded.created_at.unwrap())
|
||||
}
|
||||
_ => return Err(anyhow!("hold plugin did not return an invoice string")),
|
||||
};
|
||||
|
||||
let expires_at = match invoice_decoded.item_type {
|
||||
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
|
||||
created_at
|
||||
+ Timestamp::from_secs(u64::from(invoice_decoded.invoice_relative_expiry.unwrap()))
|
||||
}
|
||||
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
|
||||
created_at + Timestamp::from_secs(invoice_decoded.expiry.unwrap())
|
||||
}
|
||||
_ => return Err(anyhow!("hold plugin did not return an invoice string")),
|
||||
};
|
||||
|
||||
let list_peer_channels = rpc
|
||||
.call_typed(&ListpeerchannelsRequest {
|
||||
id: None,
|
||||
short_channel_id: None,
|
||||
})
|
||||
.await?
|
||||
.channels;
|
||||
|
||||
let payment_hash_hash = Sha256::from_str(&hex::encode(payment_hash))?;
|
||||
|
||||
let mut lowest_htlc_expiry = 0;
|
||||
|
||||
for peer in list_peer_channels {
|
||||
if let Some(htlcs) = peer.htlcs {
|
||||
for htlc in htlcs {
|
||||
if htlc.payment_hash != payment_hash_hash {
|
||||
continue;
|
||||
}
|
||||
if htlc.expiry < lowest_htlc_expiry {
|
||||
lowest_htlc_expiry = htlc.expiry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let clients = plugin.state().handles.lock().await;
|
||||
|
||||
let content = nip47::Notification {
|
||||
notification_type: nip47::NotificationType::HoldInvoiceAccepted,
|
||||
notification: nip47::NotificationResult::HoldInvoiceAccepted(
|
||||
nip47::HoldInvoiceAcceptedNotification {
|
||||
transaction_type: nip47::TransactionType::Incoming,
|
||||
invoice: hold_invoice.invoice.clone(),
|
||||
description: None,
|
||||
description_hash: None,
|
||||
payment_hash: hex::encode(&hold_invoice.payment_hash),
|
||||
amount,
|
||||
created_at,
|
||||
expires_at,
|
||||
settle_deadline: lowest_htlc_expiry,
|
||||
metadata: None,
|
||||
},
|
||||
),
|
||||
};
|
||||
let notification = serde_json::to_string(&content).unwrap();
|
||||
|
||||
for (client, client_pubkey) in clients.values() {
|
||||
send_notification(¬ification, client, client_pubkey).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ use nostr_sdk::{client, nips::nip47, nostr};
|
|||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::oneshot;
|
||||
use tonic::transport::Channel;
|
||||
|
||||
use crate::hold::hold_client::HoldClient;
|
||||
|
||||
pub const NOT_INV_ERR: &str = "Not an invoice or invalid invoice";
|
||||
|
||||
|
|
@ -14,6 +17,7 @@ pub struct PluginState {
|
|||
pub handles: Arc<tokio::sync::Mutex<HashMap<String, (client::Client, nostr::PublicKey)>>>,
|
||||
pub rpc_lock: Arc<tokio::sync::Mutex<ClnRpc>>,
|
||||
pub budget_jobs: Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>,
|
||||
pub hold_client: Arc<Mutex<Option<HoldClient<Channel>>>>,
|
||||
}
|
||||
impl PluginState {
|
||||
pub async fn new(path: PathBuf) -> Result<PluginState, anyhow::Error> {
|
||||
|
|
@ -22,6 +26,7 @@ impl PluginState {
|
|||
handles: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
|
||||
rpc_lock: Arc::new(tokio::sync::Mutex::new(ClnRpc::new(path).await?)),
|
||||
budget_jobs: Arc::new(Mutex::new(HashMap::new())),
|
||||
hold_client: Arc::new(Mutex::new(None)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
33
src/util.rs
33
src/util.rs
|
|
@ -10,6 +10,8 @@ use crate::{
|
|||
structs::{NwcStore, PluginState},
|
||||
OPT_NOTIFICATIONS,
|
||||
PLUGIN_NAME,
|
||||
WALLET_HOLD_METHODS,
|
||||
WALLET_HOLD_NOTIFICATIONS,
|
||||
WALLET_NOTIFICATIONS,
|
||||
WALLET_PAY_METHODS,
|
||||
WALLET_READ_METHODS,
|
||||
|
|
@ -124,11 +126,22 @@ pub fn at_or_above_version(my_version: &str, min_version: &str) -> Result<bool,
|
|||
}
|
||||
|
||||
pub fn build_capabilities(is_read_only: bool, plugin: &Plugin<PluginState>) -> (String, String) {
|
||||
let holdinvoice_support = plugin.state().hold_client.lock().is_some();
|
||||
|
||||
let mut methods = WALLET_READ_METHODS.map(|m| m.to_string()).join(" ");
|
||||
if !is_read_only {
|
||||
methods.push(' ');
|
||||
methods.push_str(WALLET_PAY_METHODS.map(|m| m.to_string()).join(" ").as_str());
|
||||
}
|
||||
if holdinvoice_support {
|
||||
methods.push(' ');
|
||||
methods.push_str(
|
||||
WALLET_HOLD_METHODS
|
||||
.map(|m| m.to_string())
|
||||
.join(" ")
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut notifications = String::new();
|
||||
if plugin.option(&OPT_NOTIFICATIONS).unwrap() {
|
||||
|
|
@ -138,23 +151,41 @@ pub fn build_capabilities(is_read_only: bool, plugin: &Plugin<PluginState>) -> (
|
|||
.join(" ")
|
||||
.as_str(),
|
||||
);
|
||||
if holdinvoice_support {
|
||||
notifications.push(' ');
|
||||
notifications.push_str(
|
||||
WALLET_HOLD_NOTIFICATIONS
|
||||
.map(|m| m.to_string())
|
||||
.join(" ")
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(methods, notifications)
|
||||
}
|
||||
|
||||
pub fn build_methods_vec(is_read_only: bool, _plugin: &Plugin<PluginState>) -> Vec<nip47::Method> {
|
||||
pub fn build_methods_vec(is_read_only: bool, plugin: &Plugin<PluginState>) -> Vec<nip47::Method> {
|
||||
let holdinvoice_support = plugin.state().hold_client.lock().is_some();
|
||||
let mut methods = WALLET_READ_METHODS.to_vec();
|
||||
if !is_read_only {
|
||||
methods.extend_from_slice(&WALLET_PAY_METHODS);
|
||||
}
|
||||
if holdinvoice_support {
|
||||
methods.extend_from_slice(&WALLET_HOLD_METHODS);
|
||||
}
|
||||
methods
|
||||
}
|
||||
|
||||
pub fn build_notifications_vec(plugin: &Plugin<PluginState>) -> Vec<String> {
|
||||
let holdinvoice_support = plugin.state().hold_client.lock().is_some();
|
||||
|
||||
let mut notifications = Vec::new();
|
||||
if plugin.option(&OPT_NOTIFICATIONS).unwrap() {
|
||||
notifications.extend_from_slice(&WALLET_NOTIFICATIONS.map(|m| m.to_string()));
|
||||
if holdinvoice_support {
|
||||
notifications.extend_from_slice(&WALLET_HOLD_NOTIFICATIONS.map(|m| m.to_string()));
|
||||
}
|
||||
}
|
||||
notifications
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ reject_future_seconds = 1800
|
|||
[limits]
|
||||
limit_scrapers = false
|
||||
[network]
|
||||
address = "0.0.0.0"
|
||||
address = "127.0.0.1"
|
||||
|
|
|
|||
|
|
@ -81,3 +81,17 @@ if [ -d "$proto_path" ]; then
|
|||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
hold_url="https://github.com/BoltzExchange/hold/releases/download/v0.3.3/hold-linux-amd64.tar.gz"
|
||||
|
||||
if ! curl -L "$hold_url" -o "$script_dir/hold-linux-amd64.tar.gz"; then
|
||||
echo "Error downloading the file from $hold_url" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! tar -xzvf "$script_dir/hold-linux-amd64.tar.gz" -C "$script_dir"; then
|
||||
echo "Error extracting the contents of hold-linux-amd64.tar.gz" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv "$script_dir/build/hold-linux-amd64" "$script_dir/hold"
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ import json
|
|||
import logging
|
||||
import time
|
||||
import asyncio
|
||||
import secrets
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Awaitable, Callable, Union
|
||||
|
||||
import pytest
|
||||
from pyln.testing.fixtures import * # noqa: F403
|
||||
from pyln.testing.utils import RpcError, wait_for, TIMEOUT
|
||||
from util import generate_random_label, get_plugin # noqa: F401
|
||||
from util import generate_random_label, get_plugin, get_hold # noqa: F401
|
||||
|
||||
from nostr_sdk import (
|
||||
Alphabet,
|
||||
|
|
@ -75,7 +77,10 @@ async def fetch_event_responses(
|
|||
) -> tuple[list[Event], Any]:
|
||||
events = []
|
||||
response_filter = Filter().kind(Kind(event_kind)).pubkey(client_pubkey)
|
||||
await client.subscribe(response_filter)
|
||||
|
||||
id = uuid.uuid4().hex
|
||||
LOGGER.info(f"Subscribing with id {id} to {response_filter}")
|
||||
await client.subscribe_with_id(id, response_filter)
|
||||
|
||||
handler = NotificationHandler(events, stop_after)
|
||||
task = asyncio.create_task(client.handle_notifications(handler))
|
||||
|
|
@ -1454,3 +1459,551 @@ async def test_budget_command(nostr_relay, node_factory, get_plugin): # noqa: F
|
|||
info_event.tags().find(TagKind.UNKNOWN("notifications")).content()
|
||||
== "payment_received payment_sent"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hold_invoice(
|
||||
node_factory,
|
||||
executor,
|
||||
get_plugin, # noqa: F811
|
||||
get_hold, # noqa: F811
|
||||
nostr_relay,
|
||||
):
|
||||
url = nostr_relay
|
||||
l1, l2 = node_factory.line_graph(
|
||||
2,
|
||||
wait_for_announce=True,
|
||||
opts=[
|
||||
{
|
||||
"log-level": "debug",
|
||||
"may_reconnect": True,
|
||||
},
|
||||
{
|
||||
"log-level": "debug",
|
||||
"plugin": get_plugin,
|
||||
"important-plugin": get_hold,
|
||||
"nip47-relays": url,
|
||||
"may_reconnect": True,
|
||||
},
|
||||
],
|
||||
)
|
||||
uri_res = l2.rpc.call("nip47-create", ["test1", 3010])
|
||||
uri_str = uri_res["uri"]
|
||||
client_pubkey = PublicKey.parse(uri_res["clientkey_public"])
|
||||
LOGGER.info(uri_str)
|
||||
uri = NostrWalletConnectUri.parse(uri_str)
|
||||
|
||||
nwc = Nwc(uri)
|
||||
|
||||
preimage = secrets.token_hex(32)
|
||||
payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
LOGGER.info(f"preimage: {preimage}")
|
||||
LOGGER.info(f"payment_hash: {payment_hash}")
|
||||
content = {
|
||||
"method": "make_hold_invoice",
|
||||
"params": {
|
||||
"amount": 5000,
|
||||
"payment_hash": payment_hash,
|
||||
},
|
||||
}
|
||||
content = json.dumps(content)
|
||||
signer = NostrSigner.keys(Keys(uri.secret()))
|
||||
encrypted_content = await signer.nip04_encrypt(uri.public_key(), content)
|
||||
event = (
|
||||
await EventBuilder(Kind(23194), encrypted_content)
|
||||
.tags([Tag.public_key(uri.public_key())])
|
||||
.sign(signer)
|
||||
)
|
||||
client = Client(signer)
|
||||
relay_url = RelayUrl.parse(url)
|
||||
await client.add_relay(relay_url)
|
||||
await client.connect()
|
||||
|
||||
await fetch_info_event(client, uri)
|
||||
|
||||
(responses1, _res) = await fetch_event_responses(
|
||||
client, client_pubkey, 23195, client.send_event(event), 1
|
||||
)
|
||||
error_events = []
|
||||
success_events = []
|
||||
for event in responses1:
|
||||
LOGGER.info(event)
|
||||
content = await signer.nip04_decrypt(uri.public_key(), event.content())
|
||||
content = json.loads(content)
|
||||
LOGGER.info(content)
|
||||
if "result" in content and content["result"] is not None:
|
||||
success_events.append(content)
|
||||
if "error" in content and content["error"] is not None:
|
||||
error_events.append(content)
|
||||
|
||||
assert len(success_events) == 1
|
||||
assert len(error_events) == 0
|
||||
|
||||
assert success_events[0]["result_type"] == "make_hold_invoice"
|
||||
assert success_events[0]["result"]["payment_hash"] == payment_hash
|
||||
assert success_events[0]["result"]["type"] == "incoming"
|
||||
assert success_events[0]["result"]["invoice"] is not None
|
||||
invoice1 = success_events[0]["result"]["invoice"]
|
||||
assert "description" not in success_events[0]["result"]
|
||||
assert "description_hash" not in success_events[0]["result"]
|
||||
assert success_events[0]["result"]["amount"] == 5000
|
||||
invoice1_created_at = pytest.approx(int(time.time()), abs=1)
|
||||
assert success_events[0]["result"]["created_at"] == invoice1_created_at
|
||||
invoice1_expires_at = pytest.approx(int(time.time()) + 3600, abs=1)
|
||||
assert success_events[0]["result"]["expires_at"] == invoice1_expires_at
|
||||
assert "metadata" not in success_events[0]["result"]
|
||||
|
||||
lookup_hold = await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=payment_hash,
|
||||
invoice=None,
|
||||
)
|
||||
)
|
||||
assert lookup_hold.invoice == success_events[0]["result"]["invoice"]
|
||||
assert lookup_hold.amount == 5000
|
||||
assert lookup_hold.description is None
|
||||
assert lookup_hold.created_at.as_secs() == success_events[0]["result"]["created_at"]
|
||||
assert lookup_hold.description_hash is None
|
||||
assert lookup_hold.expires_at.as_secs() == success_events[0]["result"]["expires_at"]
|
||||
assert lookup_hold.fees_paid == 0
|
||||
assert lookup_hold.metadata is None
|
||||
assert lookup_hold.preimage is None
|
||||
assert lookup_hold.payment_hash == payment_hash
|
||||
assert lookup_hold.transaction_type.name == "INCOMING"
|
||||
assert lookup_hold.state.name == "PENDING"
|
||||
assert lookup_hold.settled_at is None
|
||||
|
||||
(responses2, _res) = await fetch_event_responses(
|
||||
client,
|
||||
client_pubkey,
|
||||
23196,
|
||||
lambda: executor.submit(
|
||||
l1.rpc.call, "xpay", [success_events[0]["result"]["invoice"]]
|
||||
),
|
||||
1,
|
||||
)
|
||||
hold_events = []
|
||||
for event in responses2:
|
||||
LOGGER.info(event)
|
||||
content = await signer.nip04_decrypt(uri.public_key(), event.content())
|
||||
content = json.loads(content)
|
||||
LOGGER.info(content)
|
||||
if content["notification_type"] == "hold_invoice_accepted":
|
||||
hold_events.append(content)
|
||||
assert content["notification"]["payment_hash"] == payment_hash
|
||||
|
||||
lookup_hold = await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=None,
|
||||
invoice=invoice1,
|
||||
)
|
||||
)
|
||||
assert lookup_hold.invoice == invoice1
|
||||
assert lookup_hold.amount == 5000
|
||||
assert lookup_hold.description is None
|
||||
assert lookup_hold.created_at.as_secs() == invoice1_created_at
|
||||
assert lookup_hold.description_hash is None
|
||||
assert lookup_hold.expires_at.as_secs() == invoice1_expires_at
|
||||
assert lookup_hold.fees_paid == 0
|
||||
assert lookup_hold.metadata is None
|
||||
assert lookup_hold.preimage is None
|
||||
assert lookup_hold.payment_hash == payment_hash
|
||||
assert lookup_hold.transaction_type.name == "INCOMING"
|
||||
assert lookup_hold.state.name == "PENDING" # TODO ACCEPTED STATE
|
||||
assert lookup_hold.settled_at is None
|
||||
|
||||
content = {
|
||||
"method": "settle_hold_invoice",
|
||||
"params": {
|
||||
"preimage": preimage,
|
||||
},
|
||||
}
|
||||
content = json.dumps(content)
|
||||
encrypted_content = await signer.nip04_encrypt(uri.public_key(), content)
|
||||
event = (
|
||||
await EventBuilder(Kind(23194), encrypted_content)
|
||||
.tags([Tag.public_key(uri.public_key())])
|
||||
.sign(signer)
|
||||
)
|
||||
|
||||
(responses3, _res) = await fetch_event_responses(
|
||||
client, client_pubkey, 23195, client.send_event(event), 1
|
||||
)
|
||||
error_events = []
|
||||
success_events = []
|
||||
for event in responses3:
|
||||
LOGGER.info(event)
|
||||
content = await signer.nip04_decrypt(uri.public_key(), event.content())
|
||||
content = json.loads(content)
|
||||
LOGGER.info(content)
|
||||
if (
|
||||
"result" in content
|
||||
and content["result"] is not None
|
||||
and content["result_type"] == "settle_hold_invoice"
|
||||
):
|
||||
success_events.append(content)
|
||||
if "error" in content and content["error"] is not None:
|
||||
error_events.append(content)
|
||||
|
||||
assert len(success_events) == 1
|
||||
assert len(error_events) == 0
|
||||
for content in success_events:
|
||||
assert content["result_type"] == "settle_hold_invoice"
|
||||
lookup_hold = await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=None,
|
||||
invoice=invoice1,
|
||||
)
|
||||
)
|
||||
assert lookup_hold.invoice == invoice1
|
||||
assert lookup_hold.amount == 5000
|
||||
assert lookup_hold.description is None
|
||||
assert lookup_hold.created_at.as_secs() == invoice1_created_at
|
||||
assert lookup_hold.description_hash is None
|
||||
assert lookup_hold.expires_at.as_secs() == invoice1_expires_at
|
||||
assert lookup_hold.fees_paid == 0
|
||||
assert lookup_hold.metadata is None
|
||||
assert lookup_hold.preimage == preimage
|
||||
assert lookup_hold.payment_hash == payment_hash
|
||||
assert lookup_hold.transaction_type.name == "INCOMING"
|
||||
assert lookup_hold.state.name == "SETTLED"
|
||||
assert lookup_hold.settled_at.as_secs() == pytest.approx(
|
||||
int(time.time()), abs=1
|
||||
)
|
||||
|
||||
wait_for(
|
||||
lambda: l1.rpc.call("listpays", {"payment_hash": payment_hash})["pays"][0][
|
||||
"status"
|
||||
]
|
||||
== "complete"
|
||||
)
|
||||
|
||||
preimage = secrets.token_hex(32)
|
||||
payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
content = {
|
||||
"method": "make_hold_invoice",
|
||||
"params": {
|
||||
"amount": 5000,
|
||||
"payment_hash": payment_hash,
|
||||
"description": "cancel_hold",
|
||||
"expiry": 1000,
|
||||
"cltv_expiry_delta": 200,
|
||||
},
|
||||
}
|
||||
content = json.dumps(content)
|
||||
signer = NostrSigner.keys(Keys(uri.secret()))
|
||||
encrypted_content = await signer.nip04_encrypt(uri.public_key(), content)
|
||||
event = (
|
||||
await EventBuilder(Kind(23194), encrypted_content)
|
||||
.tags([Tag.public_key(uri.public_key())])
|
||||
.sign(signer)
|
||||
)
|
||||
|
||||
(responses4, _res) = await fetch_event_responses(
|
||||
client, client_pubkey, 23195, client.send_event(event), 1
|
||||
)
|
||||
error_events = []
|
||||
success_events = []
|
||||
for event in responses4:
|
||||
LOGGER.info(event)
|
||||
content = await signer.nip04_decrypt(uri.public_key(), event.content())
|
||||
content = json.loads(content)
|
||||
LOGGER.info(content)
|
||||
if (
|
||||
"result" in content
|
||||
and content["result"] is not None
|
||||
and content["result_type"] == "make_hold_invoice"
|
||||
and content["result"]["payment_hash"] == payment_hash
|
||||
):
|
||||
success_events.append(content)
|
||||
if "error" in content and content["error"] is not None:
|
||||
error_events.append(content)
|
||||
|
||||
assert len(success_events) == 1
|
||||
assert len(error_events) == 0
|
||||
for content in success_events:
|
||||
assert content["result_type"] == "make_hold_invoice"
|
||||
assert content["result"]["payment_hash"] == payment_hash
|
||||
invoice2 = content["result"]["invoice"]
|
||||
invoice2_created_at = pytest.approx(int(time.time()), abs=1)
|
||||
invoice2_expires_at = pytest.approx(int(time.time()) + 1000, abs=1)
|
||||
|
||||
(responses5, _res) = await fetch_event_responses(
|
||||
client,
|
||||
client_pubkey,
|
||||
23196,
|
||||
lambda: executor.submit(
|
||||
l1.rpc.call, "xpay", [success_events[0]["result"]["invoice"]]
|
||||
),
|
||||
1,
|
||||
)
|
||||
hold_events = []
|
||||
for event in responses5:
|
||||
LOGGER.info(event)
|
||||
content = await signer.nip04_decrypt(uri.public_key(), event.content())
|
||||
content = json.loads(content)
|
||||
LOGGER.info(content)
|
||||
if (
|
||||
content["notification_type"] == "hold_invoice_accepted"
|
||||
and content["notification"]["payment_hash"] == payment_hash
|
||||
):
|
||||
hold_events.append(content)
|
||||
assert len(hold_events) == 1
|
||||
|
||||
content = {
|
||||
"method": "cancel_hold_invoice",
|
||||
"params": {
|
||||
"payment_hash": payment_hash,
|
||||
},
|
||||
}
|
||||
content = json.dumps(content)
|
||||
encrypted_content = await signer.nip04_encrypt(uri.public_key(), content)
|
||||
event = (
|
||||
await EventBuilder(Kind(23194), encrypted_content)
|
||||
.tags([Tag.public_key(uri.public_key())])
|
||||
.sign(signer)
|
||||
)
|
||||
|
||||
(responses6, _res) = await fetch_event_responses(
|
||||
client,
|
||||
client_pubkey,
|
||||
23195,
|
||||
client.send_event(event),
|
||||
1,
|
||||
)
|
||||
error_events = []
|
||||
success_events = []
|
||||
for event in responses6:
|
||||
LOGGER.info(event)
|
||||
content = await signer.nip04_decrypt(uri.public_key(), event.content())
|
||||
content = json.loads(content)
|
||||
LOGGER.info(content)
|
||||
if (
|
||||
"result" in content
|
||||
and content["result"] is not None
|
||||
and content["result_type"] == "cancel_hold_invoice"
|
||||
):
|
||||
success_events.append(content)
|
||||
if "error" in content and content["error"] is not None:
|
||||
error_events.append(content)
|
||||
|
||||
assert len(success_events) == 1
|
||||
assert len(error_events) == 0
|
||||
for content in success_events:
|
||||
assert content["result_type"] == "cancel_hold_invoice"
|
||||
lookup_hold = await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=None,
|
||||
invoice=invoice2,
|
||||
)
|
||||
)
|
||||
assert lookup_hold.invoice == invoice2
|
||||
assert lookup_hold.amount == 5000
|
||||
assert lookup_hold.description == "cancel_hold"
|
||||
assert lookup_hold.created_at.as_secs() == invoice2_created_at
|
||||
assert lookup_hold.description_hash is None
|
||||
assert lookup_hold.expires_at.as_secs() == invoice2_expires_at
|
||||
assert lookup_hold.fees_paid == 0
|
||||
assert lookup_hold.metadata is None
|
||||
assert lookup_hold.preimage is None
|
||||
assert lookup_hold.payment_hash == payment_hash
|
||||
assert lookup_hold.transaction_type.name == "INCOMING"
|
||||
assert lookup_hold.state.name == "EXPIRED"
|
||||
assert lookup_hold.settled_at is None
|
||||
|
||||
invoice2_decoded = l1.rpc.call("decode", [invoice2])
|
||||
assert invoice2_decoded["min_final_cltv_expiry"] == 200
|
||||
|
||||
wait_for(
|
||||
lambda: l1.rpc.call("listpays", {"payment_hash": payment_hash})["pays"][0][
|
||||
"status"
|
||||
]
|
||||
== "failed"
|
||||
)
|
||||
|
||||
nwc = Nwc(uri)
|
||||
|
||||
invoice_lookup1 = await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=payment_hash,
|
||||
invoice=None,
|
||||
)
|
||||
)
|
||||
invoice_lookup2 = await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=None,
|
||||
invoice=invoice2,
|
||||
)
|
||||
)
|
||||
invoice_lookup3 = await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=payment_hash,
|
||||
invoice=invoice2,
|
||||
)
|
||||
)
|
||||
assert invoice_lookup1 == invoice_lookup2
|
||||
assert invoice_lookup1 == invoice_lookup3
|
||||
|
||||
invoice_lookup4 = await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=None,
|
||||
invoice=invoice1,
|
||||
)
|
||||
)
|
||||
|
||||
result = await nwc.list_transactions(
|
||||
ListTransactionsRequest(
|
||||
_from=None,
|
||||
until=None,
|
||||
limit=None,
|
||||
offset=None,
|
||||
unpaid=True,
|
||||
transaction_type=None,
|
||||
)
|
||||
)
|
||||
assert len(result) == 2
|
||||
assert result == [invoice_lookup1, invoice_lookup4] or result == [
|
||||
invoice_lookup4,
|
||||
invoice_lookup1,
|
||||
]
|
||||
|
||||
description3 = "test3"
|
||||
description_hash3 = hashlib.sha256(description3.encode()).hexdigest()
|
||||
preimage = secrets.token_hex(32)
|
||||
payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
content = {
|
||||
"method": "make_hold_invoice",
|
||||
"params": {
|
||||
"amount": 5001,
|
||||
"payment_hash": payment_hash,
|
||||
"description": description3,
|
||||
"description_hash": description_hash3,
|
||||
},
|
||||
}
|
||||
content = json.dumps(content)
|
||||
encrypted_content = await signer.nip04_encrypt(uri.public_key(), content)
|
||||
event = (
|
||||
await EventBuilder(Kind(23194), encrypted_content)
|
||||
.tags([Tag.public_key(uri.public_key())])
|
||||
.sign(signer)
|
||||
)
|
||||
await client.send_event(event)
|
||||
|
||||
start_time = datetime.now()
|
||||
while (datetime.now() - start_time) < timedelta(seconds=10):
|
||||
time.sleep(1)
|
||||
try:
|
||||
await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=payment_hash,
|
||||
invoice=None,
|
||||
)
|
||||
)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
lookup_hold = await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=payment_hash,
|
||||
invoice=None,
|
||||
)
|
||||
)
|
||||
assert lookup_hold.amount == 5001
|
||||
assert lookup_hold.description is None
|
||||
assert lookup_hold.description_hash == description_hash3
|
||||
assert lookup_hold.metadata is None
|
||||
assert lookup_hold.preimage is None
|
||||
assert lookup_hold.payment_hash == payment_hash
|
||||
assert lookup_hold.transaction_type.name == "INCOMING"
|
||||
assert lookup_hold.state.name == "PENDING"
|
||||
assert lookup_hold.settled_at is None
|
||||
|
||||
description4 = "test4"
|
||||
description_hash4 = hashlib.sha256(description4.encode()).hexdigest()
|
||||
preimage = secrets.token_hex(32)
|
||||
payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
content = {
|
||||
"method": "make_hold_invoice",
|
||||
"params": {
|
||||
"amount": 5002,
|
||||
"payment_hash": payment_hash,
|
||||
"description_hash": description_hash4,
|
||||
},
|
||||
}
|
||||
content = json.dumps(content)
|
||||
encrypted_content = await signer.nip04_encrypt(uri.public_key(), content)
|
||||
event = (
|
||||
await EventBuilder(Kind(23194), encrypted_content)
|
||||
.tags([Tag.public_key(uri.public_key())])
|
||||
.sign(signer)
|
||||
)
|
||||
await client.send_event(event)
|
||||
|
||||
start_time = datetime.now()
|
||||
while (datetime.now() - start_time) < timedelta(seconds=10):
|
||||
time.sleep(1)
|
||||
try:
|
||||
await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=payment_hash,
|
||||
invoice=None,
|
||||
)
|
||||
)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
lookup_hold = await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
payment_hash=payment_hash,
|
||||
invoice=None,
|
||||
)
|
||||
)
|
||||
assert lookup_hold.amount == 5002
|
||||
assert lookup_hold.description is None
|
||||
assert lookup_hold.description_hash == description_hash4
|
||||
assert lookup_hold.metadata is None
|
||||
assert lookup_hold.preimage is None
|
||||
assert lookup_hold.payment_hash == payment_hash
|
||||
assert lookup_hold.transaction_type.name == "INCOMING"
|
||||
assert lookup_hold.state.name == "PENDING"
|
||||
assert lookup_hold.settled_at is None
|
||||
|
||||
info_event = await fetch_info_event(client, uri)
|
||||
assert (
|
||||
info_event.content()
|
||||
== "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice multi_pay_invoice pay_keysend multi_pay_keysend make_hold_invoice cancel_hold_invoice settle_hold_invoice notifications"
|
||||
)
|
||||
assert (
|
||||
info_event.tags().find(TagKind.UNKNOWN("encryption")).content()
|
||||
== "nip44_v2 nip04"
|
||||
)
|
||||
assert (
|
||||
info_event.tags().find(TagKind.UNKNOWN("notifications")).content()
|
||||
== "payment_received payment_sent hold_invoice_accepted"
|
||||
)
|
||||
|
||||
l2.restart()
|
||||
|
||||
l1.rpc.connect(l2.info["id"], "localhost", l2.port)
|
||||
|
||||
(responses7, _res) = await fetch_event_responses(
|
||||
client,
|
||||
client_pubkey,
|
||||
23196,
|
||||
lambda: executor.submit(l1.rpc.call, "xpay", [lookup_hold.invoice]),
|
||||
1,
|
||||
)
|
||||
hold_events = []
|
||||
for event in responses7:
|
||||
LOGGER.info(event)
|
||||
content = await signer.nip04_decrypt(uri.public_key(), event.content())
|
||||
content = json.loads(content)
|
||||
LOGGER.info(content)
|
||||
if (
|
||||
content["notification_type"] == "hold_invoice_accepted"
|
||||
and content["notification"]["payment_hash"] == payment_hash
|
||||
):
|
||||
hold_events.append(content)
|
||||
assert len(hold_events) == 1
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import logging
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
from pathlib import Path
|
||||
from hashlib import sha256
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -11,9 +11,11 @@ plugin_dir = Path(__file__).parent.parent.resolve()
|
|||
COMPILED_PATH = plugin_dir / "target" / RUST_PROFILE / "cln-nip47"
|
||||
DOWNLOAD_PATH = plugin_dir / "tests" / "cln-nip47"
|
||||
|
||||
DOWNLOAD_HOLD_PATH = plugin_dir / "tests" / "hold"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_plugin(directory):
|
||||
def get_plugin():
|
||||
if COMPILED_PATH.is_file():
|
||||
return COMPILED_PATH
|
||||
elif DOWNLOAD_PATH.is_file():
|
||||
|
|
@ -22,6 +24,14 @@ def get_plugin(directory):
|
|||
raise ValueError("No files were found.")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_hold():
|
||||
if DOWNLOAD_HOLD_PATH.is_file():
|
||||
return DOWNLOAD_HOLD_PATH
|
||||
else:
|
||||
raise ValueError("No files were found.")
|
||||
|
||||
|
||||
def generate_random_label():
|
||||
label_length = 8
|
||||
random_label = "".join(
|
||||
|
|
@ -34,15 +44,6 @@ def generate_random_number():
|
|||
return random.randint(1, 20_000_000_000_000_00_000)
|
||||
|
||||
|
||||
def pay_with_thread(rpc, bolt11):
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
try:
|
||||
rpc.dev_pay(bolt11, dev_use_shadow=False)
|
||||
except Exception as e:
|
||||
LOGGER.info(f"holdinvoice: Error paying payment hash:{e}")
|
||||
pass
|
||||
|
||||
|
||||
def update_config_file_option(lightning_dir, option_name, option_value):
|
||||
with open(lightning_dir + "/config", "r") as file:
|
||||
lines = file.readlines()
|
||||
|
|
|
|||
142
tests/uv.lock
generated
142
tests/uv.lock
generated
|
|
@ -451,62 +451,62 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "46.0.7"
|
||||
version = "48.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/0c/dca8abb64e7ca4f6b2978769f6fea5ad06686a190cec381f0a796fdcaaba/cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f", size = 3476879, upload-time = "2026-04-08T01:57:38.664Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/d2/024b5e06be9d44cb021fb0e1a03d34d63989cf56a0fe62f3dfbab695b9b4/cryptography-48.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:84cf79f0dc8b36ac5da873481716e87aef31fcfa0444f9e1d8b4b2cece142855", size = 3950391, upload-time = "2026-05-04T22:59:17.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/17/3861e17c56fa0fd37491a14a8673fdb77c57fc5693cafe745ea8b06dba75/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b", size = 4637126, upload-time = "2026-05-04T22:59:20.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/0a/7e226dbff530f21480727eb764973a7bff2b912f8e15cd4f129e71b56d1d/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13", size = 4667270, upload-time = "2026-05-04T22:59:22.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/f2/5a72274ca9f1b2a8b44a662ee0bf1b435909deb473d6f97bcd035bcdbc71/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb", size = 4636797, upload-time = "2026-05-04T22:59:24.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/e1/48cedb2fe63626e91ded1edad159e2a4fb8b6906c4425eb7749673077ce7/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355", size = 4666800, upload-time = "2026-05-04T22:59:27.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/ca/7e8365deec19afb2b2c7be7c1c0aa8f99633b54e90c570999acda93260fc/cryptography-48.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a", size = 3739536, upload-time = "2026-05-04T22:59:29.61Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -558,11 +558,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.13"
|
||||
version = "3.14"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/05/b1/efac073e0c297ecf2fb33c346989a529d4e19164f1759102dee5953ee17e/idna-3.14.tar.gz", hash = "sha256:466d810d7a2cc1022bea9b037c39728d51ae7dad40d480fc9b7d7ecf98ba8ee3", size = 198272, upload-time = "2026-05-10T20:32:15.935Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/3c/3f62dee257eb3d6b2c1ef2a09d36d9793c7111156a73b5654d2c2305e5ce/idna-3.14-py3-none-any.whl", hash = "sha256:e677eaf072e290f7b725f9acf0b3a2bd55f9fd6f7c70abe5f0e34823d0accf69", size = 72184, upload-time = "2026-05-10T20:32:14.295Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -759,11 +759,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "26.1"
|
||||
version = "26.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -895,20 +895,20 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pyln-client"
|
||||
version = "26.4"
|
||||
version = "26.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyln-bolt7" },
|
||||
{ name = "pyln-proto" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/14/0faf6b8b04f368629897a010357d3b8d21c8a800b972b433a358caebe32b/pyln_client-26.4.tar.gz", hash = "sha256:e696076fcf1329ee22198097b8139a096f653cbbfc6a03e471455ca6f5d68a46", size = 92160, upload-time = "2026-04-20T04:58:12.666Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/28/fe/e1e19c59dd7520dec2de992f0a3da6292db24162c9830be279616a7b8810/pyln_client-26.4.1.tar.gz", hash = "sha256:26dfb21ac39c61b7ab7a40db18798b002f3f19a179ff2ec40374a9419d032050", size = 92170, upload-time = "2026-04-24T04:53:08.46Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/60/41/f67df5064b4b9d258fb30e96e1d3ca8f0395c7c0098b24091dde6238fb8e/pyln_client-26.4-py3-none-any.whl", hash = "sha256:d5470e1ecccc1b6e46e39658c1019c62868c5aed4a0203635052803ddedcce2e", size = 37655, upload-time = "2026-04-20T04:58:11.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/90/d3b5e11f85cf076b1011a1ea6031ced3745abf451e47a1697216e1ea7c29/pyln_client-26.4.1-py3-none-any.whl", hash = "sha256:a9afba9b117818023bf4567a94c34633e9f2e89f0e1ca11e433329a2c678a685", size = 37673, upload-time = "2026-04-24T04:53:07.187Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyln-proto"
|
||||
version = "26.4"
|
||||
version = "26.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "base58" },
|
||||
|
|
@ -917,14 +917,14 @@ dependencies = [
|
|||
{ name = "cryptography" },
|
||||
{ name = "pysocks" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0d/35/5d30332fcaa8d77be87290c5534d45c48822310611ae6ea6d3e48c242ded/pyln_proto-26.4.tar.gz", hash = "sha256:dde9e243c7bf6fbcf7904e0dbd18c4f05dc17e668b074bfab090a7376e3de6cf", size = 44861, upload-time = "2026-04-20T04:58:09.361Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/4b/c8d676ad84d6066553b203668c0e36f45cfae0a4d7140eb88aed6d65b38b/pyln_proto-26.4.1.tar.gz", hash = "sha256:d73f261bacc06a6a613dcece05fc429699b740a4673f24dffb7c41bd3542d589", size = 44864, upload-time = "2026-04-24T04:53:10.196Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/c9/8a34960e1e9550f87697d1f05208d9cc916c14419f18f2d6013801079fc1/pyln_proto-26.4-py3-none-any.whl", hash = "sha256:97772d285aa0d0c8a1545577aeef8821e08d59676f63ae760d270e1b3015bc9d", size = 31811, upload-time = "2026-04-20T04:58:08.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/f1/6541b6cd8a017d3505fcb49885d3f01b8e6bb09e40db18e686edb9edeb99/pyln_proto-26.4.1-py3-none-any.whl", hash = "sha256:b5d10d5e69cf3debd9b289e2ffa49713fca6c349a71cb0eaf9e733024f15b3da", size = 31833, upload-time = "2026-04-24T04:53:08.686Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyln-testing"
|
||||
version = "26.4"
|
||||
version = "26.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cheroot" },
|
||||
|
|
@ -939,9 +939,9 @@ dependencies = [
|
|||
{ name = "python-bitcoinlib" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/7e/275f0416e30faa3093ad31703f3d8702dc06569164a185813b23672ba6d9/pyln_testing-26.4.tar.gz", hash = "sha256:cca1f4fec65f12bfda809674ea7c231285a96ef0b8bc2c69ef4919f1b465d2ae", size = 52609, upload-time = "2026-04-20T04:58:07.746Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/64/73/d969847564591fe16633b11bf8be9f8184ccd885e367acee171e9fd6d2d8/pyln_testing-26.4.1.tar.gz", hash = "sha256:0a40d66ec2f0840df95bdbc4845c214aede01e9d7a89a422f5b6b00a89e63690", size = 52617, upload-time = "2026-04-24T04:53:10.904Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/c4/3cb55bf86a2499510a24b310897903efafce847869eb1ec1c7b67b7fcfa4/pyln_testing-26.4-py3-none-any.whl", hash = "sha256:ccb7641bbbb1b6b650f1b8238aacf04d7ebeea944ebd2855a901e25d18846804", size = 54365, upload-time = "2026-04-20T04:58:06.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/ad/603ff35f7b60bcc6cf364a101c58a73887132063f0773767108946361ed2/pyln_testing-26.4.1-py3-none-any.whl", hash = "sha256:dd0e9d8a865593ddc9a14ef43e9aaec0fe0d348ce9ec2fcd23807a35c98c9e8c", size = 54381, upload-time = "2026-04-24T04:53:09.599Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1035,7 +1035,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.33.1"
|
||||
version = "2.34.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
|
|
@ -1043,9 +1043,9 @@ dependencies = [
|
|||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/b8/7a707d60fea4c49094e40262cc0e2ca6c768cca21587e34d3f705afec47e/requests-2.34.0.tar.gz", hash = "sha256:7d62fe92f50eb82c529b0916bb445afa1531a566fc8f35ffdc64446e771b856a", size = 142436, upload-time = "2026-05-11T19:29:51.717Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl", hash = "sha256:917520a21b767485ce7c588f4ebb917c436b24a31231b44228715eaeb5a52c60", size = 73021, upload-time = "2026-05-11T19:29:49.923Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1273,11 +1273,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.3"
|
||||
version = "2.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue