mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2025-12-23 08:54:22 +01:00
feat: add Esplora client to chain services (#761)
Co-authored-by: Daniel Granhão <32176319+danielgranhao@users.noreply.github.com>
This commit is contained in:
178
cli/Cargo.lock
generated
178
cli/Cargo.lock
generated
@@ -412,6 +412,12 @@ dependencies = [
|
|||||||
"bitcoin_hashes 0.14.0",
|
"bitcoin_hashes 0.14.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
@@ -685,6 +691,7 @@ dependencies = [
|
|||||||
"ecies",
|
"ecies",
|
||||||
"electrum-client",
|
"electrum-client",
|
||||||
"env_logger 0.11.7",
|
"env_logger 0.11.7",
|
||||||
|
"esplora-client",
|
||||||
"flutter_rust_bridge",
|
"flutter_rust_bridge",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"glob",
|
"glob",
|
||||||
@@ -1320,6 +1327,20 @@ version = "3.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
|
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "esplora-client"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "git+https://github.com/hydra-yse/rust-esplora-client?branch=scripthash-utxo#513fb83a872425a69252e12db2f84d96973d08a2"
|
||||||
|
dependencies = [
|
||||||
|
"bitcoin 0.32.5",
|
||||||
|
"hex-conservative 0.2.1",
|
||||||
|
"log",
|
||||||
|
"minreq",
|
||||||
|
"reqwest 0.11.27",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible-iterator"
|
name = "fallible-iterator"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -1993,6 +2014,20 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-rustls"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"http 0.2.12",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"rustls 0.21.12",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls 0.24.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-rustls"
|
name = "hyper-rustls"
|
||||||
version = "0.27.5"
|
version = "0.27.5"
|
||||||
@@ -2023,6 +2058,19 @@ dependencies = [
|
|||||||
"tokio-io-timeout",
|
"tokio-io-timeout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-tls"
|
name = "hyper-tls"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -2677,8 +2725,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "lwk_wollet"
|
name = "lwk_wollet"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/breez/lwk?rev=0b18e777d496#0b18e777d496bd3ee0602b49b838d3d293e43e23"
|
||||||
checksum = "44164918e75771585624098a328cf0d5e28931ccdd5af1c9a95a6ecf0c5cb67a"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm-siv",
|
"aes-gcm-siv",
|
||||||
"age",
|
"age",
|
||||||
@@ -2790,6 +2837,7 @@ version = "2.13.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567496f13503d6cae8c9f961f34536850275f396307d7a6b981eef1464032f53"
|
checksum = "567496f13503d6cae8c9f961f34536850275f396307d7a6b981eef1464032f53"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.12.3",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -3621,6 +3669,51 @@ version = "0.8.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.11.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"bytes",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2 0.3.26",
|
||||||
|
"http 0.2.12",
|
||||||
|
"http-body 0.4.6",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"hyper-rustls 0.24.2",
|
||||||
|
"hyper-tls 0.5.0",
|
||||||
|
"ipnet",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"mime",
|
||||||
|
"native-tls",
|
||||||
|
"once_cell",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustls 0.21.12",
|
||||||
|
"rustls-pemfile 1.0.4",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper 0.1.2",
|
||||||
|
"system-configuration 0.5.1",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tokio-rustls 0.24.1",
|
||||||
|
"tokio-socks",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"webpki-roots 0.25.4",
|
||||||
|
"winreg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.12"
|
version = "0.12.12"
|
||||||
@@ -3636,8 +3729,8 @@ dependencies = [
|
|||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"hyper-rustls",
|
"hyper-rustls 0.27.5",
|
||||||
"hyper-tls",
|
"hyper-tls 0.6.0",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -3652,7 +3745,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper 1.0.2",
|
||||||
"system-configuration",
|
"system-configuration 0.6.1",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
"tower 0.5.2",
|
"tower 0.5.2",
|
||||||
@@ -3681,8 +3774,8 @@ dependencies = [
|
|||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"hyper-rustls",
|
"hyper-rustls 0.27.5",
|
||||||
"hyper-tls",
|
"hyper-tls 0.6.0",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -3700,7 +3793,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper 1.0.2",
|
||||||
"system-configuration",
|
"system-configuration 0.6.1",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
"tokio-rustls 0.26.2",
|
"tokio-rustls 0.26.2",
|
||||||
@@ -3884,6 +3977,18 @@ dependencies = [
|
|||||||
"webpki",
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.21.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"ring 0.17.14",
|
||||||
|
"rustls-webpki 0.101.7",
|
||||||
|
"sct",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.23"
|
version = "0.23.23"
|
||||||
@@ -3894,7 +3999,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"ring 0.17.14",
|
"ring 0.17.14",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki",
|
"rustls-webpki 0.102.8",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -3938,6 +4043,16 @@ dependencies = [
|
|||||||
"web-time",
|
"web-time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.101.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||||
|
dependencies = [
|
||||||
|
"ring 0.17.14",
|
||||||
|
"untrusted 0.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.102.8"
|
version = "0.102.8"
|
||||||
@@ -4423,6 +4538,17 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"core-foundation",
|
||||||
|
"system-configuration-sys 0.5.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-configuration"
|
name = "system-configuration"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@@ -4431,7 +4557,17 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"system-configuration-sys",
|
"system-configuration-sys 0.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4636,6 +4772,16 @@ dependencies = [
|
|||||||
"webpki",
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||||
|
dependencies = [
|
||||||
|
"rustls 0.21.12",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.2"
|
version = "0.26.2"
|
||||||
@@ -4646,6 +4792,18 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-socks"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"futures-util",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
|
|||||||
178
lib/Cargo.lock
generated
178
lib/Cargo.lock
generated
@@ -492,6 +492,12 @@ dependencies = [
|
|||||||
"bitcoin_hashes 0.14.0",
|
"bitcoin_hashes 0.14.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
@@ -780,6 +786,7 @@ dependencies = [
|
|||||||
"ecies",
|
"ecies",
|
||||||
"electrum-client",
|
"electrum-client",
|
||||||
"env_logger 0.11.7",
|
"env_logger 0.11.7",
|
||||||
|
"esplora-client",
|
||||||
"flutter_rust_bridge",
|
"flutter_rust_bridge",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"getrandom 0.2.14",
|
"getrandom 0.2.14",
|
||||||
@@ -1505,6 +1512,20 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "esplora-client"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "git+https://github.com/hydra-yse/rust-esplora-client?branch=scripthash-utxo#513fb83a872425a69252e12db2f84d96973d08a2"
|
||||||
|
dependencies = [
|
||||||
|
"bitcoin 0.32.5",
|
||||||
|
"hex-conservative 0.2.1",
|
||||||
|
"log",
|
||||||
|
"minreq",
|
||||||
|
"reqwest 0.11.27",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fallible-iterator"
|
name = "fallible-iterator"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -2219,6 +2240,20 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-rustls"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"http 0.2.12",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"rustls 0.21.12",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls 0.24.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-rustls"
|
name = "hyper-rustls"
|
||||||
version = "0.27.5"
|
version = "0.27.5"
|
||||||
@@ -2249,6 +2284,19 @@ dependencies = [
|
|||||||
"tokio-io-timeout",
|
"tokio-io-timeout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-tls"
|
name = "hyper-tls"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -2923,8 +2971,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "lwk_wollet"
|
name = "lwk_wollet"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/breez/lwk?rev=0b18e777d496#0b18e777d496bd3ee0602b49b838d3d293e43e23"
|
||||||
checksum = "44164918e75771585624098a328cf0d5e28931ccdd5af1c9a95a6ecf0c5cb67a"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm-siv",
|
"aes-gcm-siv",
|
||||||
"age",
|
"age",
|
||||||
@@ -3050,6 +3097,7 @@ version = "2.13.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567496f13503d6cae8c9f961f34536850275f396307d7a6b981eef1464032f53"
|
checksum = "567496f13503d6cae8c9f961f34536850275f396307d7a6b981eef1464032f53"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.12.3",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -3947,6 +3995,51 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.11.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"bytes",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2 0.3.26",
|
||||||
|
"http 0.2.12",
|
||||||
|
"http-body 0.4.6",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"hyper-rustls 0.24.2",
|
||||||
|
"hyper-tls 0.5.0",
|
||||||
|
"ipnet",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"mime",
|
||||||
|
"native-tls",
|
||||||
|
"once_cell",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustls 0.21.12",
|
||||||
|
"rustls-pemfile 1.0.4",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper 0.1.2",
|
||||||
|
"system-configuration 0.5.1",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tokio-rustls 0.24.1",
|
||||||
|
"tokio-socks",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"webpki-roots 0.25.4",
|
||||||
|
"winreg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.12"
|
version = "0.12.12"
|
||||||
@@ -3962,8 +4055,8 @@ dependencies = [
|
|||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"hyper-rustls",
|
"hyper-rustls 0.27.5",
|
||||||
"hyper-tls",
|
"hyper-tls 0.6.0",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -3978,7 +4071,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper 1.0.2",
|
||||||
"system-configuration",
|
"system-configuration 0.6.1",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
"tower 0.5.2",
|
"tower 0.5.2",
|
||||||
@@ -4007,8 +4100,8 @@ dependencies = [
|
|||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"hyper-rustls",
|
"hyper-rustls 0.27.5",
|
||||||
"hyper-tls",
|
"hyper-tls 0.6.0",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -4026,7 +4119,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper 1.0.2",
|
||||||
"system-configuration",
|
"system-configuration 0.6.1",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
"tokio-rustls 0.26.2",
|
"tokio-rustls 0.26.2",
|
||||||
@@ -4209,6 +4302,18 @@ dependencies = [
|
|||||||
"webpki",
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.21.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"ring 0.17.14",
|
||||||
|
"rustls-webpki 0.101.7",
|
||||||
|
"sct",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.25"
|
version = "0.23.25"
|
||||||
@@ -4219,7 +4324,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"ring 0.17.14",
|
"ring 0.17.14",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki",
|
"rustls-webpki 0.103.0",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -4263,6 +4368,16 @@ dependencies = [
|
|||||||
"web-time",
|
"web-time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.101.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||||
|
dependencies = [
|
||||||
|
"ring 0.17.14",
|
||||||
|
"untrusted 0.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.0"
|
version = "0.103.0"
|
||||||
@@ -4801,6 +4916,17 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"core-foundation",
|
||||||
|
"system-configuration-sys 0.5.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-configuration"
|
name = "system-configuration"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@@ -4809,7 +4935,17 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"system-configuration-sys",
|
"system-configuration-sys 0.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5034,6 +5170,16 @@ dependencies = [
|
|||||||
"webpki",
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||||
|
dependencies = [
|
||||||
|
"rustls 0.21.12",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.2"
|
version = "0.26.2"
|
||||||
@@ -5044,6 +5190,18 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-socks"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"futures-util",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
|
|||||||
@@ -635,6 +635,25 @@ typedef struct wire_cst_sdk_event {
|
|||||||
union SdkEventKind kind;
|
union SdkEventKind kind;
|
||||||
} wire_cst_sdk_event;
|
} wire_cst_sdk_event;
|
||||||
|
|
||||||
|
typedef struct wire_cst_BlockchainExplorer_Electrum {
|
||||||
|
struct wire_cst_list_prim_u_8_strict *url;
|
||||||
|
} wire_cst_BlockchainExplorer_Electrum;
|
||||||
|
|
||||||
|
typedef struct wire_cst_BlockchainExplorer_Esplora {
|
||||||
|
struct wire_cst_list_prim_u_8_strict *url;
|
||||||
|
bool use_waterfalls;
|
||||||
|
} wire_cst_BlockchainExplorer_Esplora;
|
||||||
|
|
||||||
|
typedef union BlockchainExplorerKind {
|
||||||
|
struct wire_cst_BlockchainExplorer_Electrum Electrum;
|
||||||
|
struct wire_cst_BlockchainExplorer_Esplora Esplora;
|
||||||
|
} BlockchainExplorerKind;
|
||||||
|
|
||||||
|
typedef struct wire_cst_blockchain_explorer {
|
||||||
|
int32_t tag;
|
||||||
|
union BlockchainExplorerKind kind;
|
||||||
|
} wire_cst_blockchain_explorer;
|
||||||
|
|
||||||
typedef struct wire_cst_external_input_parser {
|
typedef struct wire_cst_external_input_parser {
|
||||||
struct wire_cst_list_prim_u_8_strict *provider_id;
|
struct wire_cst_list_prim_u_8_strict *provider_id;
|
||||||
struct wire_cst_list_prim_u_8_strict *input_regex;
|
struct wire_cst_list_prim_u_8_strict *input_regex;
|
||||||
@@ -659,9 +678,8 @@ typedef struct wire_cst_list_asset_metadata {
|
|||||||
} wire_cst_list_asset_metadata;
|
} wire_cst_list_asset_metadata;
|
||||||
|
|
||||||
typedef struct wire_cst_config {
|
typedef struct wire_cst_config {
|
||||||
struct wire_cst_list_prim_u_8_strict *liquid_electrum_url;
|
struct wire_cst_blockchain_explorer liquid_explorer;
|
||||||
struct wire_cst_list_prim_u_8_strict *bitcoin_electrum_url;
|
struct wire_cst_blockchain_explorer bitcoin_explorer;
|
||||||
struct wire_cst_list_prim_u_8_strict *mempoolspace_url;
|
|
||||||
struct wire_cst_list_prim_u_8_strict *working_dir;
|
struct wire_cst_list_prim_u_8_strict *working_dir;
|
||||||
struct wire_cst_list_prim_u_8_strict *cache_dir;
|
struct wire_cst_list_prim_u_8_strict *cache_dir;
|
||||||
int32_t network;
|
int32_t network;
|
||||||
|
|||||||
@@ -325,10 +325,15 @@ enum PaymentError {
|
|||||||
"SignerError",
|
"SignerError",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Enum]
|
||||||
|
interface BlockchainExplorer {
|
||||||
|
Electrum(string url);
|
||||||
|
Esplora(string url, boolean use_waterfalls);
|
||||||
|
};
|
||||||
|
|
||||||
dictionary Config {
|
dictionary Config {
|
||||||
string liquid_electrum_url;
|
BlockchainExplorer liquid_explorer;
|
||||||
string bitcoin_electrum_url;
|
BlockchainExplorer bitcoin_explorer;
|
||||||
string mempoolspace_url;
|
|
||||||
string working_dir;
|
string working_dir;
|
||||||
LiquidNetwork network;
|
LiquidNetwork network;
|
||||||
u64 payment_timeout_sec;
|
u64 payment_timeout_sec;
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ tempfile = "3"
|
|||||||
ecies = { version = "0.2.7", default-features = false, features = ["pure"] }
|
ecies = { version = "0.2.7", default-features = false, features = ["pure"] }
|
||||||
semver = "1.0.23"
|
semver = "1.0.23"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
|
esplora-client = { git = "https://github.com/hydra-yse/rust-esplora-client", branch = "scripthash-utxo", features = ["async-https-rustls"] }
|
||||||
|
|
||||||
# Non-Wasm dependencies
|
# Non-Wasm dependencies
|
||||||
[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies]
|
[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies]
|
||||||
@@ -68,7 +69,7 @@ electrum-client = { version = "0.21.0", default-features = false, features = [
|
|||||||
"use-rustls-ring",
|
"use-rustls-ring",
|
||||||
"proxy",
|
"proxy",
|
||||||
] }
|
] }
|
||||||
lwk_wollet = { version = "0.9.0" }
|
lwk_wollet = { git = "https://github.com/breez/lwk", rev = "0b18e777d496" }
|
||||||
maybe-sync = { version = "0.1.1", features = ["sync"] }
|
maybe-sync = { version = "0.1.1", features = ["sync"] }
|
||||||
prost = "^0.11"
|
prost = "^0.11"
|
||||||
tonic = { version = "^0.8", features = ["tls", "tls-webpki-roots"] }
|
tonic = { version = "^0.8", features = ["tls", "tls-webpki-roots"] }
|
||||||
@@ -77,7 +78,7 @@ uuid = { version = "1.8.0", features = ["v4"] }
|
|||||||
# Wasm dependencies
|
# Wasm dependencies
|
||||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
|
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
|
||||||
console_log = "1"
|
console_log = "1"
|
||||||
lwk_wollet = { version = "0.9.0", default-features = false, features = [
|
lwk_wollet = { git = "https://github.com/breez/lwk", rev = "0b18e777d496", default-features = false, features = [
|
||||||
"esplora",
|
"esplora",
|
||||||
] }
|
] }
|
||||||
maybe-sync = "0.1.1"
|
maybe-sync = "0.1.1"
|
||||||
|
|||||||
@@ -1,114 +1,54 @@
|
|||||||
use std::{
|
#![cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||||
collections::HashMap,
|
|
||||||
sync::{Arc, Mutex, OnceLock},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use std::{collections::HashMap, sync::OnceLock, time::Duration};
|
||||||
use electrum_client::{
|
|
||||||
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
bitcoin::{
|
bitcoin::{
|
||||||
consensus::{deserialize, serialize},
|
consensus::{deserialize, serialize},
|
||||||
hashes::{sha256, Hash},
|
hashes::{sha256, Hash},
|
||||||
Address, OutPoint, Script, Transaction, Txid,
|
Address, OutPoint, Script, ScriptBuf, Transaction, Txid,
|
||||||
},
|
},
|
||||||
Client, ElectrumApi, GetBalanceRes, HeaderNotification,
|
model::{BlockchainExplorer, Config, LiquidNetwork, RecommendedFees, Utxo},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use electrum_client::{Client, ElectrumApi, HeaderNotification};
|
||||||
use log::info;
|
use log::info;
|
||||||
use lwk_wollet::{bitcoin::ScriptBuf, ElectrumOptions, ElectrumUrl, Error, History};
|
use lwk_wollet::{ElectrumOptions, ElectrumUrl};
|
||||||
use sdk_common::{
|
use sdk_common::bitcoin::hashes::hex::ToHex as _;
|
||||||
bitcoin::hashes::hex::ToHex,
|
|
||||||
prelude::{get_and_check_success, parse_json, RestClient},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use super::{BitcoinChainService, BtcScriptBalance, History};
|
||||||
model::{Config, LiquidNetwork, RecommendedFees},
|
|
||||||
prelude::Utxo,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Trait implemented by types that can fetch data from a blockchain data source.
|
pub(crate) struct ElectrumBitcoinChainService {
|
||||||
#[allow(dead_code)]
|
|
||||||
#[sdk_macros::async_trait]
|
|
||||||
pub trait BitcoinChainService: Send + Sync {
|
|
||||||
/// Get the blockchain latest block
|
|
||||||
fn tip(&self) -> Result<HeaderNotification>;
|
|
||||||
|
|
||||||
/// Broadcast a transaction
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<Txid>;
|
|
||||||
|
|
||||||
/// Get a list of transactions
|
|
||||||
fn get_transactions(&self, txids: &[Txid]) -> Result<Vec<Transaction>>;
|
|
||||||
|
|
||||||
/// Get the transactions involved for a script
|
|
||||||
fn get_script_history(&self, script: &Script) -> Result<Vec<History>>;
|
|
||||||
|
|
||||||
/// Get the transactions involved in a list of scripts.
|
|
||||||
fn get_scripts_history(&self, scripts: &[&Script]) -> Result<Vec<Vec<History>>>;
|
|
||||||
|
|
||||||
/// Get the transactions involved for a script
|
|
||||||
async fn get_script_history_with_retry(
|
|
||||||
&self,
|
|
||||||
script: &Script,
|
|
||||||
retries: u64,
|
|
||||||
) -> Result<Vec<History>>;
|
|
||||||
|
|
||||||
/// Get the utxos associated with a script pubkey
|
|
||||||
fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>>;
|
|
||||||
|
|
||||||
/// Get the utxos associated with a list of scripts
|
|
||||||
fn get_scripts_utxos(&self, scripts: &[&Script]) -> Result<Vec<Vec<Utxo>>>;
|
|
||||||
|
|
||||||
/// Return the confirmed and unconfirmed balances of a script hash
|
|
||||||
fn script_get_balance(&self, script: &Script) -> Result<GetBalanceRes>;
|
|
||||||
|
|
||||||
/// Return the confirmed and unconfirmed balances of a list of script hashes
|
|
||||||
fn scripts_get_balance(&self, scripts: &[&Script]) -> Result<Vec<GetBalanceRes>>;
|
|
||||||
|
|
||||||
/// Return the confirmed and unconfirmed balances of a script hash
|
|
||||||
async fn script_get_balance_with_retry(
|
|
||||||
&self,
|
|
||||||
script: &Script,
|
|
||||||
retries: u64,
|
|
||||||
) -> Result<GetBalanceRes>;
|
|
||||||
|
|
||||||
/// Verify that a transaction appears in the address script history
|
|
||||||
async fn verify_tx(
|
|
||||||
&self,
|
|
||||||
address: &Address,
|
|
||||||
tx_id: &str,
|
|
||||||
tx_hex: &str,
|
|
||||||
verify_confirmation: bool,
|
|
||||||
) -> Result<Transaction>;
|
|
||||||
|
|
||||||
/// Get the recommended fees, in sat/vbyte
|
|
||||||
async fn recommended_fees(&self) -> Result<RecommendedFees>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct HybridBitcoinChainService {
|
|
||||||
config: Config,
|
config: Config,
|
||||||
rest_client: Arc<dyn RestClient>,
|
|
||||||
client: OnceLock<Client>,
|
client: OnceLock<Client>,
|
||||||
last_known_tip: Mutex<Option<HeaderNotification>>,
|
last_known_tip: Mutex<Option<u32>>,
|
||||||
}
|
}
|
||||||
impl HybridBitcoinChainService {
|
|
||||||
pub fn new(config: Config, rest_client: Arc<dyn RestClient>) -> Result<Self, Error> {
|
impl ElectrumBitcoinChainService {
|
||||||
Ok(Self {
|
pub(crate) fn new(config: Config) -> Self {
|
||||||
|
Self {
|
||||||
config,
|
config,
|
||||||
rest_client,
|
|
||||||
client: OnceLock::new(),
|
client: OnceLock::new(),
|
||||||
last_known_tip: Mutex::new(None),
|
last_known_tip: Mutex::new(None),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_client(&self) -> Result<&Client> {
|
fn get_client(&self) -> Result<&Client> {
|
||||||
if let Some(c) = self.client.get() {
|
if let Some(c) = self.client.get() {
|
||||||
return Ok(c);
|
return Ok(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (tls, validate_domain) = match self.config.network {
|
let (tls, validate_domain) = match self.config.network {
|
||||||
LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
|
LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
|
||||||
LiquidNetwork::Regtest => (false, false),
|
LiquidNetwork::Regtest => (false, false),
|
||||||
};
|
};
|
||||||
let electrum_url =
|
let electrum_url = match &self.config.bitcoin_explorer {
|
||||||
ElectrumUrl::new(&self.config.bitcoin_electrum_url, tls, validate_domain)?;
|
BlockchainExplorer::Electrum { url } => ElectrumUrl::new(url, tls, validate_domain)?,
|
||||||
|
_ => bail!("Cannot start Bitcoin Electrum chain service without an Electrum url"),
|
||||||
|
};
|
||||||
let client = electrum_url.build_client(&ElectrumOptions { timeout: Some(3) })?;
|
let client = electrum_url.build_client(&ElectrumOptions { timeout: Some(3) })?;
|
||||||
|
|
||||||
let client = self.client.get_or_init(|| client);
|
let client = self.client.get_or_init(|| client);
|
||||||
@@ -117,8 +57,8 @@ impl HybridBitcoinChainService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[sdk_macros::async_trait]
|
#[sdk_macros::async_trait]
|
||||||
impl BitcoinChainService for HybridBitcoinChainService {
|
impl BitcoinChainService for ElectrumBitcoinChainService {
|
||||||
fn tip(&self) -> Result<HeaderNotification> {
|
async fn tip(&self) -> Result<u32> {
|
||||||
let client = self.get_client()?;
|
let client = self.get_client()?;
|
||||||
let mut maybe_popped_header = None;
|
let mut maybe_popped_header = None;
|
||||||
while let Some(header) = client.block_headers_pop_raw()? {
|
while let Some(header) = client.block_headers_pop_raw()? {
|
||||||
@@ -140,24 +80,25 @@ impl BitcoinChainService for HybridBitcoinChainService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut last_tip = self.last_known_tip.lock().unwrap();
|
let mut last_tip = self.last_known_tip.lock().await;
|
||||||
match new_tip {
|
match new_tip {
|
||||||
Some(header) => {
|
Some(header) => {
|
||||||
*last_tip = Some(header.clone());
|
let height = header.height as u32;
|
||||||
Ok(header)
|
*last_tip = Some(height);
|
||||||
|
Ok(height)
|
||||||
}
|
}
|
||||||
None => last_tip.clone().ok_or_else(|| anyhow!("Failed to get tip")),
|
None => (*last_tip).ok_or_else(|| anyhow!("Failed to get tip")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
|
async fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
|
||||||
let txid = self
|
let txid = self
|
||||||
.get_client()?
|
.get_client()?
|
||||||
.transaction_broadcast_raw(&serialize(&tx))?;
|
.transaction_broadcast_raw(&serialize(&tx))?;
|
||||||
Ok(Txid::from_raw_hash(txid.to_raw_hash()))
|
Ok(Txid::from_raw_hash(txid.to_raw_hash()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_transactions(&self, txids: &[Txid]) -> Result<Vec<Transaction>> {
|
async fn get_transactions(&self, txids: &[Txid]) -> Result<Vec<Transaction>> {
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
for tx in self.get_client()?.batch_transaction_get_raw(txids)? {
|
for tx in self.get_client()?.batch_transaction_get_raw(txids)? {
|
||||||
let tx: Transaction = deserialize(&tx)?;
|
let tx: Transaction = deserialize(&tx)?;
|
||||||
@@ -166,7 +107,7 @@ impl BitcoinChainService for HybridBitcoinChainService {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_script_history(&self, script: &Script) -> Result<Vec<History>> {
|
async fn get_script_history(&self, script: &Script) -> Result<Vec<History>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.get_client()?
|
.get_client()?
|
||||||
.script_get_history(script)?
|
.script_get_history(script)?
|
||||||
@@ -175,7 +116,7 @@ impl BitcoinChainService for HybridBitcoinChainService {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_scripts_history(&self, scripts: &[&Script]) -> Result<Vec<Vec<History>>> {
|
async fn get_scripts_history(&self, scripts: &[&Script]) -> Result<Vec<Vec<History>>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.get_client()?
|
.get_client()?
|
||||||
.batch_script_get_history(scripts)?
|
.batch_script_get_history(scripts)?
|
||||||
@@ -195,7 +136,7 @@ impl BitcoinChainService for HybridBitcoinChainService {
|
|||||||
|
|
||||||
let mut retry = 0;
|
let mut retry = 0;
|
||||||
while retry <= retries {
|
while retry <= retries {
|
||||||
script_history = self.get_script_history(script)?;
|
script_history = self.get_script_history(script).await?;
|
||||||
match script_history.is_empty() {
|
match script_history.is_empty() {
|
||||||
true => {
|
true => {
|
||||||
retry += 1;
|
retry += 1;
|
||||||
@@ -211,22 +152,25 @@ impl BitcoinChainService for HybridBitcoinChainService {
|
|||||||
Ok(script_history)
|
Ok(script_history)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>> {
|
async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.get_scripts_utxos(&[script])?
|
.get_scripts_utxos(&[script])
|
||||||
|
.await?
|
||||||
.first()
|
.first()
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default())
|
.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_scripts_utxos(&self, scripts: &[&Script]) -> Result<Vec<Vec<Utxo>>> {
|
async fn get_scripts_utxos(&self, scripts: &[&Script]) -> Result<Vec<Vec<Utxo>>> {
|
||||||
let scripts_history = self.get_scripts_history(scripts)?;
|
let scripts_history = self.get_scripts_history(scripts).await?;
|
||||||
let tx_confirmed_map: HashMap<_, _> = scripts_history
|
let tx_confirmed_map: HashMap<_, _> = scripts_history
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|h| (Txid::from_raw_hash(h.txid.to_raw_hash()), h.height > 0))
|
.map(|h| (Txid::from_raw_hash(h.txid.to_raw_hash()), h.height > 0))
|
||||||
.collect();
|
.collect();
|
||||||
let txs = self.get_transactions(&tx_confirmed_map.keys().cloned().collect::<Vec<_>>())?;
|
let txs = self
|
||||||
|
.get_transactions(&tx_confirmed_map.keys().cloned().collect::<Vec<_>>())
|
||||||
|
.await?;
|
||||||
let script_txs_map: HashMap<ScriptBuf, Vec<Transaction>> = scripts
|
let script_txs_map: HashMap<ScriptBuf, Vec<Transaction>> = scripts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|script| ScriptBuf::from_bytes(script.to_bytes().to_vec()))
|
.map(|script| ScriptBuf::from_bytes(script.to_bytes().to_vec()))
|
||||||
@@ -287,31 +231,36 @@ impl BitcoinChainService for HybridBitcoinChainService {
|
|||||||
Ok(scripts_utxos)
|
Ok(scripts_utxos)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn script_get_balance(&self, script: &Script) -> Result<GetBalanceRes> {
|
async fn script_get_balance(&self, script: &Script) -> Result<BtcScriptBalance> {
|
||||||
Ok(self.get_client()?.script_get_balance(script)?)
|
Ok(self.get_client()?.script_get_balance(script)?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scripts_get_balance(&self, scripts: &[&Script]) -> Result<Vec<GetBalanceRes>> {
|
async fn scripts_get_balance(&self, scripts: &[&Script]) -> Result<Vec<BtcScriptBalance>> {
|
||||||
Ok(self.get_client()?.batch_script_get_balance(scripts)?)
|
Ok(self
|
||||||
|
.get_client()?
|
||||||
|
.batch_script_get_balance(scripts)?
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn script_get_balance_with_retry(
|
async fn script_get_balance_with_retry(
|
||||||
&self,
|
&self,
|
||||||
script: &Script,
|
script: &Script,
|
||||||
retries: u64,
|
retries: u64,
|
||||||
) -> Result<GetBalanceRes> {
|
) -> Result<BtcScriptBalance> {
|
||||||
let script_hash = sha256::Hash::hash(script.as_bytes()).to_hex();
|
let script_hash = sha256::Hash::hash(script.as_bytes()).to_hex();
|
||||||
info!("Fetching script balance for {}", script_hash);
|
info!("Fetching script balance for {}", script_hash);
|
||||||
let mut script_balance = GetBalanceRes {
|
let mut script_balance = BtcScriptBalance {
|
||||||
confirmed: 0,
|
confirmed: 0,
|
||||||
unconfirmed: 0,
|
unconfirmed: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut retry = 0;
|
let mut retry = 0;
|
||||||
while retry <= retries {
|
while retry <= retries {
|
||||||
script_balance = self.script_get_balance(script)?;
|
script_balance = self.script_get_balance(script).await?;
|
||||||
match script_balance {
|
match script_balance {
|
||||||
GetBalanceRes {
|
BtcScriptBalance {
|
||||||
confirmed: 0,
|
confirmed: 0,
|
||||||
unconfirmed: 0,
|
unconfirmed: 0,
|
||||||
} => {
|
} => {
|
||||||
@@ -368,11 +317,18 @@ impl BitcoinChainService for HybridBitcoinChainService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn recommended_fees(&self) -> Result<RecommendedFees> {
|
async fn recommended_fees(&self) -> Result<RecommendedFees> {
|
||||||
let (response, _) = get_and_check_success(
|
let fees: Vec<u64> = self
|
||||||
self.rest_client.as_ref(),
|
.get_client()?
|
||||||
&format!("{}/v1/fees/recommended", self.config.mempoolspace_url),
|
.batch_estimate_fee([1, 3, 6, 25, 1008])?
|
||||||
)
|
.into_iter()
|
||||||
.await?;
|
.map(|v| v.ceil() as u64)
|
||||||
Ok(parse_json(&response)?)
|
.collect();
|
||||||
|
Ok(RecommendedFees {
|
||||||
|
fastest_fee: fees[0],
|
||||||
|
half_hour_fee: fees[1],
|
||||||
|
hour_fee: fees[2],
|
||||||
|
economy_fee: fees[3],
|
||||||
|
minimum_fee: fees[4],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
333
lib/core/src/chain/bitcoin/esplora.rs
Normal file
333
lib/core/src/chain/bitcoin/esplora.rs
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
use std::{collections::HashMap, sync::OnceLock, time::Duration};
|
||||||
|
|
||||||
|
use esplora_client::{AsyncClient, Builder};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bitcoin::{
|
||||||
|
consensus::deserialize,
|
||||||
|
hashes::{sha256, Hash},
|
||||||
|
Address, OutPoint, Script, ScriptBuf, Transaction, Txid,
|
||||||
|
},
|
||||||
|
model::{BlockchainExplorer, Config},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
|
||||||
|
use crate::model::{RecommendedFees, Utxo};
|
||||||
|
use log::info;
|
||||||
|
use sdk_common::bitcoin::hashes::hex::ToHex as _;
|
||||||
|
|
||||||
|
use super::{BitcoinChainService, BtcScriptBalance, History};
|
||||||
|
|
||||||
|
pub(crate) struct EsploraBitcoinChainService {
|
||||||
|
config: Config,
|
||||||
|
client: OnceLock<AsyncClient>,
|
||||||
|
last_known_tip: Mutex<Option<u32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EsploraBitcoinChainService {
|
||||||
|
pub(crate) fn new(config: Config) -> Self {
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
client: OnceLock::new(),
|
||||||
|
last_known_tip: Mutex::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_client(&self) -> Result<&AsyncClient> {
|
||||||
|
if let Some(c) = self.client.get() {
|
||||||
|
return Ok(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
let esplora_url = match &self.config.bitcoin_explorer {
|
||||||
|
BlockchainExplorer::Esplora { url, .. } => url,
|
||||||
|
_ => bail!("Cannot start Bitcoin Esplora chain service without an Esplora url"),
|
||||||
|
};
|
||||||
|
let client = Builder::new(esplora_url)
|
||||||
|
.timeout(3)
|
||||||
|
.max_retries(10)
|
||||||
|
.build_async()?;
|
||||||
|
|
||||||
|
let client = self.client.get_or_init(|| client);
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::async_trait]
|
||||||
|
impl BitcoinChainService for EsploraBitcoinChainService {
|
||||||
|
async fn tip(&self) -> Result<u32> {
|
||||||
|
let client = self.get_client()?;
|
||||||
|
let new_tip = client.get_height().await.ok();
|
||||||
|
|
||||||
|
let mut last_tip = self.last_known_tip.lock().await;
|
||||||
|
match new_tip {
|
||||||
|
Some(height) => {
|
||||||
|
*last_tip = Some(height);
|
||||||
|
Ok(height)
|
||||||
|
}
|
||||||
|
None => (*last_tip).ok_or_else(|| anyhow!("Failed to get tip")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
|
||||||
|
self.get_client()?.broadcast(tx).await?;
|
||||||
|
Ok(tx.compute_txid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Switch to batch search
|
||||||
|
async fn get_transactions(&self, txids: &[Txid]) -> Result<Vec<Transaction>> {
|
||||||
|
let client = self.get_client()?;
|
||||||
|
let mut result = vec![];
|
||||||
|
for txid in txids {
|
||||||
|
let tx = client
|
||||||
|
.get_tx(txid)
|
||||||
|
.await?
|
||||||
|
.context("Transaction not found")?;
|
||||||
|
result.push(tx);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script_history(&self, script: &Script) -> Result<Vec<History>> {
|
||||||
|
let client = self.get_client()?;
|
||||||
|
let history = client
|
||||||
|
.scripthash_txs(script, None)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|tx| History {
|
||||||
|
txid: tx.txid,
|
||||||
|
height: tx.status.block_height.map(|h| h as i32).unwrap_or(-1),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(history)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Switch to batch search
|
||||||
|
async fn get_scripts_history(&self, scripts: &[&Script]) -> Result<Vec<Vec<History>>> {
|
||||||
|
let mut result = vec![];
|
||||||
|
for script in scripts {
|
||||||
|
let history = self.get_script_history(script).await?;
|
||||||
|
result.push(history);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script_history_with_retry(
|
||||||
|
&self,
|
||||||
|
script: &Script,
|
||||||
|
retries: u64,
|
||||||
|
) -> Result<Vec<History>> {
|
||||||
|
let script_hash = sha256::Hash::hash(script.as_bytes()).to_hex();
|
||||||
|
info!("Fetching script history for {}", script_hash);
|
||||||
|
let mut script_history = vec![];
|
||||||
|
|
||||||
|
let mut retry = 0;
|
||||||
|
while retry <= retries {
|
||||||
|
script_history = self.get_script_history(script).await?;
|
||||||
|
match script_history.is_empty() {
|
||||||
|
true => {
|
||||||
|
retry += 1;
|
||||||
|
info!(
|
||||||
|
"Script history for {} got zero transactions, retrying in {} seconds...",
|
||||||
|
script_hash, retry
|
||||||
|
);
|
||||||
|
tokio::time::sleep(Duration::from_secs(retry)).await;
|
||||||
|
}
|
||||||
|
false => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(script_history)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>> {
|
||||||
|
Ok(self
|
||||||
|
.get_scripts_utxos(&[script])
|
||||||
|
.await?
|
||||||
|
.first()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_scripts_utxos(&self, scripts: &[&Script]) -> Result<Vec<Vec<Utxo>>> {
|
||||||
|
let scripts_history = self.get_scripts_history(scripts).await?;
|
||||||
|
let tx_confirmed_map: HashMap<_, _> = scripts_history
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|h| (h.txid, h.height > 0))
|
||||||
|
.collect();
|
||||||
|
let txs = self
|
||||||
|
.get_transactions(&tx_confirmed_map.keys().cloned().collect::<Vec<_>>())
|
||||||
|
.await?;
|
||||||
|
let script_txs_map: HashMap<ScriptBuf, Vec<Transaction>> = scripts
|
||||||
|
.iter()
|
||||||
|
.map(|script| ScriptBuf::from_bytes(script.to_bytes().to_vec()))
|
||||||
|
.zip(scripts_history)
|
||||||
|
.map(|(script_buf, script_history)| {
|
||||||
|
(
|
||||||
|
script_buf,
|
||||||
|
script_history
|
||||||
|
.iter()
|
||||||
|
.filter_map(|h| {
|
||||||
|
txs.iter()
|
||||||
|
.find(|tx| tx.compute_txid().as_raw_hash() == h.txid.as_raw_hash())
|
||||||
|
.cloned()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let scripts_utxos = script_txs_map
|
||||||
|
.iter()
|
||||||
|
.map(|(script_buf, txs)| {
|
||||||
|
txs.iter()
|
||||||
|
.flat_map(|tx| {
|
||||||
|
tx.output
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, output)| output.script_pubkey == *script_buf)
|
||||||
|
.filter(|(vout, _)| {
|
||||||
|
// Check if output is unspent (only consider confirmed spending txs)
|
||||||
|
!txs.iter().any(|spending_tx| {
|
||||||
|
let spends_our_output = spending_tx.input.iter().any(|input| {
|
||||||
|
input.previous_output.txid == tx.compute_txid()
|
||||||
|
&& input.previous_output.vout == *vout as u32
|
||||||
|
});
|
||||||
|
|
||||||
|
if spends_our_output {
|
||||||
|
// If it does spend our output, check if it's confirmed
|
||||||
|
let spending_tx_hash = spending_tx.compute_txid();
|
||||||
|
tx_confirmed_map
|
||||||
|
.get(&spending_tx_hash)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|(vout, output)| {
|
||||||
|
Utxo::Bitcoin((
|
||||||
|
OutPoint::new(tx.compute_txid(), vout as u32),
|
||||||
|
output.clone(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(scripts_utxos)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn script_get_balance(&self, script: &Script) -> Result<BtcScriptBalance> {
|
||||||
|
let client = self.get_client()?;
|
||||||
|
let utxos = client.scripthash_utxos(script).await?;
|
||||||
|
let mut balance = BtcScriptBalance {
|
||||||
|
confirmed: 0,
|
||||||
|
unconfirmed: 0,
|
||||||
|
};
|
||||||
|
for utxo in utxos {
|
||||||
|
match utxo.status.confirmed {
|
||||||
|
true => balance.confirmed += utxo.value,
|
||||||
|
false => balance.unconfirmed += utxo.value as i64,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Switch to batch search
|
||||||
|
async fn scripts_get_balance(&self, scripts: &[&Script]) -> Result<Vec<BtcScriptBalance>> {
|
||||||
|
let mut result = vec![];
|
||||||
|
for script in scripts {
|
||||||
|
let balance = self.script_get_balance(script).await?;
|
||||||
|
result.push(balance);
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn script_get_balance_with_retry(
|
||||||
|
&self,
|
||||||
|
script: &Script,
|
||||||
|
retries: u64,
|
||||||
|
) -> Result<BtcScriptBalance> {
|
||||||
|
let script_hash = sha256::Hash::hash(script.as_bytes()).to_hex();
|
||||||
|
info!("Fetching script balance for {}", script_hash);
|
||||||
|
let mut script_balance = BtcScriptBalance {
|
||||||
|
confirmed: 0,
|
||||||
|
unconfirmed: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut retry = 0;
|
||||||
|
while retry <= retries {
|
||||||
|
script_balance = self.script_get_balance(script).await?;
|
||||||
|
match script_balance {
|
||||||
|
BtcScriptBalance {
|
||||||
|
confirmed: 0,
|
||||||
|
unconfirmed: 0,
|
||||||
|
} => {
|
||||||
|
retry += 1;
|
||||||
|
info!(
|
||||||
|
"Got zero balance for script {}, retrying in {} seconds...",
|
||||||
|
script_hash, retry
|
||||||
|
);
|
||||||
|
tokio::time::sleep(Duration::from_secs(retry)).await;
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(script_balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_tx(
|
||||||
|
&self,
|
||||||
|
address: &Address,
|
||||||
|
tx_id: &str,
|
||||||
|
tx_hex: &str,
|
||||||
|
verify_confirmation: bool,
|
||||||
|
) -> Result<Transaction> {
|
||||||
|
let script = address.script_pubkey();
|
||||||
|
let script_history = self.get_script_history_with_retry(&script, 10).await?;
|
||||||
|
let lockup_tx_history = script_history.iter().find(|h| h.txid.to_hex().eq(tx_id));
|
||||||
|
|
||||||
|
match lockup_tx_history {
|
||||||
|
Some(history) => {
|
||||||
|
info!("Bitcoin transaction found, verifying transaction content...");
|
||||||
|
let tx: Transaction = deserialize(&hex::decode(tx_hex)?)?;
|
||||||
|
let tx_hex = tx.compute_txid().to_hex();
|
||||||
|
if !tx_hex.eq(&history.txid.to_hex()) {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Bitcoin transaction id and hex do not match: {} vs {}",
|
||||||
|
tx_id,
|
||||||
|
tx_hex
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if verify_confirmation && history.height <= 0 {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Bitcoin transaction was not confirmed, txid={} waiting for confirmation",
|
||||||
|
tx_id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(tx)
|
||||||
|
}
|
||||||
|
None => Err(anyhow!(
|
||||||
|
"Bitcoin transaction was not found, txid={} waiting for broadcast",
|
||||||
|
tx_id,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn recommended_fees(&self) -> Result<RecommendedFees> {
|
||||||
|
let client = self.get_client()?;
|
||||||
|
let fees = client.get_fee_estimates().await?;
|
||||||
|
let get_fees = |block: &u16| fees.get(block).map(|fee| fee.ceil() as u64).unwrap_or(0);
|
||||||
|
|
||||||
|
Ok(RecommendedFees {
|
||||||
|
fastest_fee: get_fees(&1),
|
||||||
|
half_hour_fee: get_fees(&3),
|
||||||
|
hour_fee: get_fees(&6),
|
||||||
|
economy_fee: get_fees(&25),
|
||||||
|
minimum_fee: get_fees(&1008),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
69
lib/core/src/chain/bitcoin/mod.rs
Normal file
69
lib/core/src/chain/bitcoin/mod.rs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
pub(crate) mod electrum;
|
||||||
|
pub(crate) mod esplora;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bitcoin,
|
||||||
|
model::{BtcHistory, BtcScriptBalance, RecommendedFees, Utxo},
|
||||||
|
};
|
||||||
|
use bitcoin::{Address, Script, Transaction, Txid};
|
||||||
|
|
||||||
|
pub(crate) type History = BtcHistory;
|
||||||
|
|
||||||
|
/// Trait implemented by types that can fetch data from a blockchain data source.
|
||||||
|
#[sdk_macros::async_trait]
|
||||||
|
pub trait BitcoinChainService: Send + Sync {
|
||||||
|
/// Get the blockchain latest block
|
||||||
|
async fn tip(&self) -> Result<u32>;
|
||||||
|
|
||||||
|
/// Broadcast a transaction
|
||||||
|
async fn broadcast(&self, tx: &Transaction) -> Result<Txid>;
|
||||||
|
|
||||||
|
/// Get a list of transactions
|
||||||
|
async fn get_transactions(&self, txids: &[Txid]) -> Result<Vec<Transaction>>;
|
||||||
|
|
||||||
|
/// Get the transactions involved for a script
|
||||||
|
async fn get_script_history(&self, script: &Script) -> Result<Vec<History>>;
|
||||||
|
|
||||||
|
/// Get the transactions involved in a list of scripts.
|
||||||
|
async fn get_scripts_history(&self, scripts: &[&Script]) -> Result<Vec<Vec<History>>>;
|
||||||
|
|
||||||
|
/// Get the transactions involved for a script
|
||||||
|
async fn get_script_history_with_retry(
|
||||||
|
&self,
|
||||||
|
script: &Script,
|
||||||
|
retries: u64,
|
||||||
|
) -> Result<Vec<History>>;
|
||||||
|
|
||||||
|
/// Get the utxos associated with a script pubkey
|
||||||
|
async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>>;
|
||||||
|
|
||||||
|
/// Get the utxos associated with a list of scripts
|
||||||
|
async fn get_scripts_utxos(&self, scripts: &[&Script]) -> Result<Vec<Vec<Utxo>>>;
|
||||||
|
|
||||||
|
/// Return the confirmed and unconfirmed balances of a script hash
|
||||||
|
async fn script_get_balance(&self, script: &Script) -> Result<BtcScriptBalance>;
|
||||||
|
|
||||||
|
/// Return the confirmed and unconfirmed balances of a list of script hashes
|
||||||
|
async fn scripts_get_balance(&self, scripts: &[&Script]) -> Result<Vec<BtcScriptBalance>>;
|
||||||
|
|
||||||
|
/// Return the confirmed and unconfirmed balances of a script hash
|
||||||
|
async fn script_get_balance_with_retry(
|
||||||
|
&self,
|
||||||
|
script: &Script,
|
||||||
|
retries: u64,
|
||||||
|
) -> Result<BtcScriptBalance>;
|
||||||
|
|
||||||
|
/// Verify that a transaction appears in the address script history
|
||||||
|
async fn verify_tx(
|
||||||
|
&self,
|
||||||
|
address: &Address,
|
||||||
|
tx_id: &str,
|
||||||
|
tx_hex: &str,
|
||||||
|
verify_confirmation: bool,
|
||||||
|
) -> Result<Transaction>;
|
||||||
|
|
||||||
|
/// Get the recommended fees, in sat/vbyte
|
||||||
|
async fn recommended_fees(&self) -> Result<RecommendedFees>;
|
||||||
|
}
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
use std::sync::{Mutex, OnceLock};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use boltz_client::ToHex;
|
|
||||||
use electrum_client::{Client, ElectrumApi};
|
|
||||||
use elements::encode::serialize as elements_serialize;
|
|
||||||
use log::info;
|
|
||||||
use lwk_wollet::elements::hex::FromHex;
|
|
||||||
use lwk_wollet::{bitcoin, elements, ElectrumOptions};
|
|
||||||
use lwk_wollet::{
|
|
||||||
elements::{Address, OutPoint, Script, Transaction, Txid},
|
|
||||||
hashes::{sha256, Hash},
|
|
||||||
ElectrumUrl, History,
|
|
||||||
};
|
|
||||||
use mockall::automock;
|
|
||||||
|
|
||||||
use crate::model::LiquidNetwork;
|
|
||||||
use crate::prelude::Utxo;
|
|
||||||
use crate::{model::Config, utils};
|
|
||||||
|
|
||||||
#[automock]
|
|
||||||
#[sdk_macros::async_trait]
|
|
||||||
pub trait LiquidChainService: Send + Sync {
|
|
||||||
/// Get the blockchain latest block
|
|
||||||
async fn tip(&self) -> Result<u32>;
|
|
||||||
|
|
||||||
/// Broadcast a transaction
|
|
||||||
async fn broadcast(&self, tx: &Transaction) -> Result<Txid>;
|
|
||||||
|
|
||||||
/// Get a single transaction from its raw hash
|
|
||||||
async fn get_transaction_hex(&self, txid: &Txid) -> Result<Option<Transaction>>;
|
|
||||||
|
|
||||||
/// Get a list of transactions
|
|
||||||
async fn get_transactions(&self, txids: &[Txid]) -> Result<Vec<Transaction>>;
|
|
||||||
|
|
||||||
/// Get the transactions involved in a script
|
|
||||||
async fn get_script_history(&self, scripts: &Script) -> Result<Vec<History>>;
|
|
||||||
|
|
||||||
/// Get the transactions involved in a list of scripts.
|
|
||||||
///
|
|
||||||
/// The data is fetched in a single call from the Electrum endpoint.
|
|
||||||
async fn get_scripts_history(&self, scripts: &[Script]) -> Result<Vec<Vec<History>>>;
|
|
||||||
|
|
||||||
/// Get the transactions involved in a list of scripts
|
|
||||||
async fn get_script_history_with_retry(
|
|
||||||
&self,
|
|
||||||
script: &Script,
|
|
||||||
retries: u64,
|
|
||||||
) -> Result<Vec<History>>;
|
|
||||||
|
|
||||||
/// Get the utxos associated with a script pubkey
|
|
||||||
async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>>;
|
|
||||||
|
|
||||||
/// Verify that a transaction appears in the address script history
|
|
||||||
async fn verify_tx(
|
|
||||||
&self,
|
|
||||||
address: &Address,
|
|
||||||
tx_id: &str,
|
|
||||||
tx_hex: &str,
|
|
||||||
verify_confirmation: bool,
|
|
||||||
) -> Result<Transaction>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct HybridLiquidChainService {
|
|
||||||
client: OnceLock<Client>,
|
|
||||||
config: Config,
|
|
||||||
last_known_tip: Mutex<Option<u32>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HybridLiquidChainService {
|
|
||||||
pub(crate) fn new(config: Config) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
config,
|
|
||||||
client: OnceLock::new(),
|
|
||||||
last_known_tip: Mutex::new(None),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_client(&self) -> Result<&Client> {
|
|
||||||
if let Some(c) = self.client.get() {
|
|
||||||
return Ok(c);
|
|
||||||
}
|
|
||||||
let (tls, validate_domain) = match self.config.network {
|
|
||||||
LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
|
|
||||||
LiquidNetwork::Regtest => (false, false),
|
|
||||||
};
|
|
||||||
let electrum_url =
|
|
||||||
ElectrumUrl::new(&self.config.liquid_electrum_url, tls, validate_domain)?;
|
|
||||||
let client = electrum_url.build_client(&ElectrumOptions { timeout: Some(3) })?;
|
|
||||||
|
|
||||||
let client = self.client.get_or_init(|| client);
|
|
||||||
Ok(client)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sdk_macros::async_trait]
|
|
||||||
impl LiquidChainService for HybridLiquidChainService {
|
|
||||||
async fn tip(&self) -> Result<u32> {
|
|
||||||
let client = self.get_client()?;
|
|
||||||
let mut maybe_popped_header = None;
|
|
||||||
while let Some(header) = client.block_headers_pop_raw()? {
|
|
||||||
maybe_popped_header = Some(header)
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_tip: Option<u32> = match maybe_popped_header {
|
|
||||||
Some(popped_header) => Some(popped_header.height.try_into()?),
|
|
||||||
None => {
|
|
||||||
// https://github.com/bitcoindevkit/rust-electrum-client/issues/124
|
|
||||||
// It might be that the client has reconnected and subscriptions don't persist
|
|
||||||
// across connections. Calling `client.ping()` won't help here because the
|
|
||||||
// successful retry will prevent us knowing about the reconnect.
|
|
||||||
if let Ok(header) = client.block_headers_subscribe_raw() {
|
|
||||||
Some(header.height.try_into()?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut last_tip: std::sync::MutexGuard<'_, Option<u32>> =
|
|
||||||
self.last_known_tip.lock().unwrap();
|
|
||||||
match new_tip {
|
|
||||||
Some(height) => {
|
|
||||||
*last_tip = Some(height);
|
|
||||||
Ok(height)
|
|
||||||
}
|
|
||||||
None => last_tip.ok_or_else(|| anyhow!("Failed to get tip")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
|
|
||||||
let txid = self
|
|
||||||
.get_client()?
|
|
||||||
.transaction_broadcast_raw(&elements_serialize(tx))?;
|
|
||||||
Ok(Txid::from_raw_hash(txid.to_raw_hash()))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_transaction_hex(&self, txid: &Txid) -> Result<Option<Transaction>> {
|
|
||||||
Ok(self.get_transactions(&[*txid]).await?.first().cloned())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_transactions(&self, txids: &[Txid]) -> Result<Vec<Transaction>> {
|
|
||||||
let txids: Vec<bitcoin::Txid> = txids
|
|
||||||
.iter()
|
|
||||||
.map(|t| bitcoin::Txid::from_raw_hash(t.to_raw_hash()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut result = vec![];
|
|
||||||
for tx in self.get_client()?.batch_transaction_get_raw(&txids)? {
|
|
||||||
let tx: Transaction = elements::encode::deserialize(&tx)?;
|
|
||||||
result.push(tx);
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_script_history(&self, script: &Script) -> Result<Vec<History>> {
|
|
||||||
let scripts = &[script];
|
|
||||||
let scripts: Vec<&bitcoin::Script> = scripts
|
|
||||||
.iter()
|
|
||||||
.map(|t| bitcoin::Script::from_bytes(t.as_bytes()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut history_vec: Vec<Vec<History>> = self
|
|
||||||
.get_client()?
|
|
||||||
.batch_script_get_history(&scripts)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|e| e.into_iter().map(Into::into).collect())
|
|
||||||
.collect();
|
|
||||||
let h = history_vec.pop();
|
|
||||||
Ok(h.unwrap_or_default())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_scripts_history(&self, scripts: &[Script]) -> Result<Vec<Vec<History>>> {
|
|
||||||
let scripts: Vec<&bitcoin::Script> = scripts
|
|
||||||
.iter()
|
|
||||||
.map(|t| bitcoin::Script::from_bytes(t.as_bytes()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.get_client()?
|
|
||||||
.batch_script_get_history(&scripts)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|e| e.into_iter().map(Into::into).collect())
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_script_history_with_retry(
|
|
||||||
&self,
|
|
||||||
script: &Script,
|
|
||||||
retries: u64,
|
|
||||||
) -> Result<Vec<History>> {
|
|
||||||
let script_hash = sha256::Hash::hash(script.as_bytes())
|
|
||||||
.to_byte_array()
|
|
||||||
.to_hex();
|
|
||||||
info!("Fetching script history for {}", script_hash);
|
|
||||||
let mut script_history = vec![];
|
|
||||||
|
|
||||||
let mut retry = 0;
|
|
||||||
while retry <= retries {
|
|
||||||
script_history = self.get_script_history(script).await?;
|
|
||||||
match script_history.is_empty() {
|
|
||||||
true => {
|
|
||||||
retry += 1;
|
|
||||||
info!("Script history for {script_hash} is empty, retrying in 1 second... ({retry} of {retries})");
|
|
||||||
// Waiting 1s between retries, so we detect the new tx as soon as possible
|
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
false => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(script_history)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>> {
|
|
||||||
let history = self.get_script_history_with_retry(script, 10).await?;
|
|
||||||
|
|
||||||
let mut utxos: Vec<Utxo> = vec![];
|
|
||||||
for history_item in history {
|
|
||||||
match self.get_transaction_hex(&history_item.txid).await {
|
|
||||||
Ok(Some(tx)) => {
|
|
||||||
let mut new_utxos = tx
|
|
||||||
.output
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(vout, output)| {
|
|
||||||
Utxo::Liquid(Box::new((
|
|
||||||
OutPoint::new(history_item.txid, vout as u32),
|
|
||||||
output.clone(),
|
|
||||||
)))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
utxos.append(&mut new_utxos);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
log::warn!("Could not retrieve transaction from history item");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(utxos);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn verify_tx(
|
|
||||||
&self,
|
|
||||||
address: &Address,
|
|
||||||
tx_id: &str,
|
|
||||||
tx_hex: &str,
|
|
||||||
verify_confirmation: bool,
|
|
||||||
) -> Result<Transaction> {
|
|
||||||
let script = Script::from_hex(
|
|
||||||
hex::encode(address.to_unconfidential().script_pubkey().as_bytes()).as_str(),
|
|
||||||
)
|
|
||||||
.map_err(|e| anyhow!("Failed to get script from address {e:?}"))?;
|
|
||||||
|
|
||||||
let script_history = self.get_script_history_with_retry(&script, 30).await?;
|
|
||||||
let lockup_tx_history = script_history.iter().find(|h| h.txid.to_hex().eq(tx_id));
|
|
||||||
|
|
||||||
match lockup_tx_history {
|
|
||||||
Some(history) => {
|
|
||||||
info!("Liquid transaction found, verifying transaction content...");
|
|
||||||
let tx: Transaction = utils::deserialize_tx_hex(tx_hex)?;
|
|
||||||
if !tx.txid().to_hex().eq(&history.txid.to_hex()) {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Liquid transaction id and hex do not match: {} vs {}",
|
|
||||||
tx_id,
|
|
||||||
tx.txid().to_hex()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if verify_confirmation && history.height <= 0 {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Liquid transaction was not confirmed, txid={} waiting for confirmation",
|
|
||||||
tx_id,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(tx)
|
|
||||||
}
|
|
||||||
None => Err(anyhow!(
|
|
||||||
"Liquid transaction was not found, txid={} waiting for broadcast",
|
|
||||||
tx_id,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
195
lib/core/src/chain/liquid/electrum.rs
Normal file
195
lib/core/src/chain/liquid/electrum.rs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
#![cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||||
|
|
||||||
|
use std::{sync::OnceLock, time::Duration};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, bail, Context as _, Result};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
elements::{Address, OutPoint, Script, Transaction, Txid},
|
||||||
|
model::{BlockchainExplorer, Config, LiquidNetwork, Utxo},
|
||||||
|
utils,
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::info;
|
||||||
|
use lwk_wollet::{
|
||||||
|
clients::blocking::BlockchainBackend as _, elements::hex::FromHex as _, ElectrumClient,
|
||||||
|
ElectrumOptions, ElectrumUrl,
|
||||||
|
};
|
||||||
|
use sdk_common::bitcoin::hashes::hex::ToHex as _;
|
||||||
|
|
||||||
|
use super::{History, LiquidChainService};
|
||||||
|
|
||||||
|
pub(crate) struct ElectrumLiquidChainService {
|
||||||
|
config: Config,
|
||||||
|
client: OnceLock<RwLock<ElectrumClient>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElectrumLiquidChainService {
|
||||||
|
pub(crate) fn new(config: Config) -> Self {
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
client: OnceLock::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_client(&self) -> Result<&RwLock<ElectrumClient>> {
|
||||||
|
if let Some(c) = self.client.get() {
|
||||||
|
return Ok(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (tls, validate_domain) = match self.config.network {
|
||||||
|
LiquidNetwork::Mainnet | LiquidNetwork::Testnet => (true, true),
|
||||||
|
LiquidNetwork::Regtest => (false, false),
|
||||||
|
};
|
||||||
|
let electrum_url = match &self.config.liquid_explorer {
|
||||||
|
BlockchainExplorer::Electrum { url } => ElectrumUrl::new(url, tls, validate_domain)?,
|
||||||
|
_ => bail!("Cannot start Liquid Electrum chain service without an Electrum url"),
|
||||||
|
};
|
||||||
|
let client =
|
||||||
|
ElectrumClient::with_options(&electrum_url, ElectrumOptions { timeout: Some(3) })?;
|
||||||
|
|
||||||
|
let client = self.client.get_or_init(|| RwLock::new(client));
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::async_trait]
|
||||||
|
impl LiquidChainService for ElectrumLiquidChainService {
|
||||||
|
async fn tip(&self) -> Result<u32> {
|
||||||
|
Ok(self
|
||||||
|
.get_client()?
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.tip()
|
||||||
|
.map(|header| header.height)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
|
||||||
|
Ok(self.get_client()?.read().await.broadcast(tx)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_transaction_hex(&self, txid: &Txid) -> Result<Option<Transaction>> {
|
||||||
|
Ok(self.get_transactions(&[*txid]).await?.first().cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_transactions(&self, txids: &[Txid]) -> Result<Vec<Transaction>> {
|
||||||
|
Ok(self.get_client()?.read().await.get_transactions(txids)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script_history(&self, script: &Script) -> Result<Vec<History>> {
|
||||||
|
self.get_scripts_history(&[script.clone()])
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.nth(0)
|
||||||
|
.context("History not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_scripts_history(&self, scripts: &[Script]) -> Result<Vec<Vec<History>>> {
|
||||||
|
let scripts: Vec<&Script> = scripts.iter().collect();
|
||||||
|
Ok(self
|
||||||
|
.get_client()?
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get_scripts_history(&scripts)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|h| h.into_iter().map(Into::into).collect())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script_history_with_retry(
|
||||||
|
&self,
|
||||||
|
script: &Script,
|
||||||
|
retries: u64,
|
||||||
|
) -> Result<Vec<History>> {
|
||||||
|
info!("Fetching script history for {script:x}");
|
||||||
|
let mut script_history = vec![];
|
||||||
|
|
||||||
|
let mut retry = 0;
|
||||||
|
while retry <= retries {
|
||||||
|
script_history = self.get_script_history(script).await?;
|
||||||
|
match script_history.is_empty() {
|
||||||
|
true => {
|
||||||
|
retry += 1;
|
||||||
|
info!("Script history for {script:x} is empty, retrying in 1 second... ({retry} of {retries})");
|
||||||
|
// Waiting 1s between retries, so we detect the new tx as soon as possible
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
false => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(script_history)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>> {
|
||||||
|
let history = self.get_script_history_with_retry(script, 10).await?;
|
||||||
|
|
||||||
|
let mut utxos: Vec<Utxo> = vec![];
|
||||||
|
for history_item in history {
|
||||||
|
match self.get_transaction_hex(&history_item.txid).await {
|
||||||
|
Ok(Some(tx)) => {
|
||||||
|
let mut new_utxos = tx
|
||||||
|
.output
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(vout, output)| {
|
||||||
|
Utxo::Liquid(Box::new((
|
||||||
|
OutPoint::new(history_item.txid, vout as u32),
|
||||||
|
output.clone(),
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
utxos.append(&mut new_utxos);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::warn!("Could not retrieve transaction from history item");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(utxos)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_tx(
|
||||||
|
&self,
|
||||||
|
address: &Address,
|
||||||
|
tx_id: &str,
|
||||||
|
tx_hex: &str,
|
||||||
|
verify_confirmation: bool,
|
||||||
|
) -> Result<Transaction> {
|
||||||
|
let script = Script::from_hex(
|
||||||
|
hex::encode(address.to_unconfidential().script_pubkey().as_bytes()).as_str(),
|
||||||
|
)
|
||||||
|
.map_err(|e| anyhow!("Failed to get script from address {e:?}"))?;
|
||||||
|
|
||||||
|
let script_history = self.get_script_history_with_retry(&script, 30).await?;
|
||||||
|
let lockup_tx_history = script_history.iter().find(|h| h.txid.to_hex().eq(tx_id));
|
||||||
|
|
||||||
|
match lockup_tx_history {
|
||||||
|
Some(history) => {
|
||||||
|
info!("Liquid transaction found, verifying transaction content...");
|
||||||
|
let tx: Transaction = utils::deserialize_tx_hex(tx_hex)?;
|
||||||
|
if !tx.txid().to_hex().eq(&history.txid.to_hex()) {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Liquid transaction id and hex do not match: {} vs {}",
|
||||||
|
tx_id,
|
||||||
|
tx.txid().to_hex()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if verify_confirmation && history.height <= 0 {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Liquid transaction was not confirmed, txid={} waiting for confirmation",
|
||||||
|
tx_id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(tx)
|
||||||
|
}
|
||||||
|
None => Err(anyhow!(
|
||||||
|
"Liquid transaction was not found, txid={} waiting for broadcast",
|
||||||
|
tx_id,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
199
lib/core/src/chain/liquid/esplora.rs
Normal file
199
lib/core/src/chain/liquid/esplora.rs
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
use std::{sync::OnceLock, time::Duration};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, bail, Context as _, Result};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
elements::{Address, OutPoint, Script, Transaction, Txid},
|
||||||
|
model::{BlockchainExplorer, Config, Utxo},
|
||||||
|
utils,
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::info;
|
||||||
|
use lwk_wollet::{
|
||||||
|
asyncr::EsploraClientBuilder, clients::asyncr::EsploraClient, elements::hex::FromHex as _,
|
||||||
|
};
|
||||||
|
use sdk_common::bitcoin::hashes::hex::ToHex as _;
|
||||||
|
|
||||||
|
use super::{History, LiquidChainService};
|
||||||
|
|
||||||
|
pub(crate) struct EsploraLiquidChainService {
|
||||||
|
config: Config,
|
||||||
|
client: OnceLock<RwLock<EsploraClient>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EsploraLiquidChainService {
|
||||||
|
pub(crate) fn new(config: Config) -> Self {
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
client: OnceLock::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_client(&self) -> Result<&RwLock<EsploraClient>> {
|
||||||
|
if let Some(c) = self.client.get() {
|
||||||
|
return Ok(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = match &self.config.liquid_explorer {
|
||||||
|
BlockchainExplorer::Esplora {
|
||||||
|
url,
|
||||||
|
use_waterfalls,
|
||||||
|
} => EsploraClientBuilder::new(url, self.config.network.into())
|
||||||
|
.timeout(3)
|
||||||
|
.waterfalls(*use_waterfalls)
|
||||||
|
.build(),
|
||||||
|
_ => bail!("Cannot start Liquid Esplroa chain service without an Esplora url"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = self.client.get_or_init(|| RwLock::new(client));
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::async_trait]
|
||||||
|
impl LiquidChainService for EsploraLiquidChainService {
|
||||||
|
async fn tip(&self) -> Result<u32> {
|
||||||
|
Ok(self
|
||||||
|
.get_client()?
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.tip()
|
||||||
|
.await
|
||||||
|
.map(|header| header.height)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
|
||||||
|
Ok(self.get_client()?.read().await.broadcast(tx).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_transaction_hex(&self, txid: &Txid) -> Result<Option<Transaction>> {
|
||||||
|
Ok(self.get_transactions(&[*txid]).await?.first().cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_transactions(&self, txids: &[Txid]) -> Result<Vec<Transaction>> {
|
||||||
|
Ok(self
|
||||||
|
.get_client()?
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get_transactions(txids)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script_history(&self, script: &Script) -> Result<Vec<History>> {
|
||||||
|
self.get_scripts_history(&[script.clone()])
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.nth(0)
|
||||||
|
.context("History not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_scripts_history(&self, scripts: &[Script]) -> Result<Vec<Vec<History>>> {
|
||||||
|
let scripts: Vec<&Script> = scripts.iter().collect();
|
||||||
|
Ok(self
|
||||||
|
.get_client()?
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get_scripts_history(&scripts)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|h| h.into_iter().map(Into::into).collect())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script_history_with_retry(
|
||||||
|
&self,
|
||||||
|
script: &Script,
|
||||||
|
retries: u64,
|
||||||
|
) -> Result<Vec<History>> {
|
||||||
|
info!("Fetching script history for {script:x}");
|
||||||
|
let mut script_history = vec![];
|
||||||
|
|
||||||
|
let mut retry = 0;
|
||||||
|
while retry <= retries {
|
||||||
|
script_history = self.get_script_history(script).await?;
|
||||||
|
match script_history.is_empty() {
|
||||||
|
true => {
|
||||||
|
retry += 1;
|
||||||
|
info!("Script history for {script:x} is empty, retrying in 1 second... ({retry} of {retries})");
|
||||||
|
// Waiting 1s between retries, so we detect the new tx as soon as possible
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
false => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(script_history)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>> {
|
||||||
|
let history = self.get_script_history_with_retry(script, 10).await?;
|
||||||
|
|
||||||
|
let mut utxos: Vec<Utxo> = vec![];
|
||||||
|
for history_item in history {
|
||||||
|
match self.get_transaction_hex(&history_item.txid).await {
|
||||||
|
Ok(Some(tx)) => {
|
||||||
|
let mut new_utxos = tx
|
||||||
|
.output
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(vout, output)| {
|
||||||
|
Utxo::Liquid(Box::new((
|
||||||
|
OutPoint::new(history_item.txid, vout as u32),
|
||||||
|
output.clone(),
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
utxos.append(&mut new_utxos);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::warn!("Could not retrieve transaction from history item");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(utxos);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_tx(
|
||||||
|
&self,
|
||||||
|
address: &Address,
|
||||||
|
tx_id: &str,
|
||||||
|
tx_hex: &str,
|
||||||
|
verify_confirmation: bool,
|
||||||
|
) -> Result<Transaction> {
|
||||||
|
let script = Script::from_hex(
|
||||||
|
hex::encode(address.to_unconfidential().script_pubkey().as_bytes()).as_str(),
|
||||||
|
)
|
||||||
|
.map_err(|e| anyhow!("Failed to get script from address {e:?}"))?;
|
||||||
|
|
||||||
|
let script_history = self.get_script_history_with_retry(&script, 30).await?;
|
||||||
|
let lockup_tx_history = script_history.iter().find(|h| h.txid.to_hex().eq(tx_id));
|
||||||
|
|
||||||
|
match lockup_tx_history {
|
||||||
|
Some(history) => {
|
||||||
|
info!("Liquid transaction found, verifying transaction content...");
|
||||||
|
let tx: Transaction = utils::deserialize_tx_hex(tx_hex)?;
|
||||||
|
if !tx.txid().to_hex().eq(&history.txid.to_hex()) {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Liquid transaction id and hex do not match: {} vs {}",
|
||||||
|
tx_id,
|
||||||
|
tx.txid().to_hex()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if verify_confirmation && history.height <= 0 {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Liquid transaction was not confirmed, txid={} waiting for confirmation",
|
||||||
|
tx_id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(tx)
|
||||||
|
}
|
||||||
|
None => Err(anyhow!(
|
||||||
|
"Liquid transaction was not found, txid={} waiting for broadcast",
|
||||||
|
tx_id,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lib/core/src/chain/liquid/mod.rs
Normal file
56
lib/core/src/chain/liquid/mod.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
pub(crate) mod electrum;
|
||||||
|
pub(crate) mod esplora;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use mockall::automock;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
elements,
|
||||||
|
model::{LBtcHistory, Utxo},
|
||||||
|
};
|
||||||
|
use elements::{Address, Script, Transaction, Txid};
|
||||||
|
|
||||||
|
pub(crate) type History = LBtcHistory;
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
#[sdk_macros::async_trait]
|
||||||
|
pub trait LiquidChainService: Send + Sync {
|
||||||
|
/// Get the blockchain latest block
|
||||||
|
async fn tip(&self) -> Result<u32>;
|
||||||
|
|
||||||
|
/// Broadcast a transaction
|
||||||
|
async fn broadcast(&self, tx: &Transaction) -> Result<Txid>;
|
||||||
|
|
||||||
|
/// Get a single transaction from its raw hash
|
||||||
|
async fn get_transaction_hex(&self, txid: &Txid) -> Result<Option<Transaction>>;
|
||||||
|
|
||||||
|
/// Get a list of transactions
|
||||||
|
async fn get_transactions(&self, txids: &[Txid]) -> Result<Vec<Transaction>>;
|
||||||
|
|
||||||
|
/// Get the transactions involved in a script
|
||||||
|
async fn get_script_history(&self, scripts: &Script) -> Result<Vec<History>>;
|
||||||
|
|
||||||
|
/// Get the transactions involved in a list of scripts.
|
||||||
|
///
|
||||||
|
/// The data is fetched in a single call from the Electrum endpoint.
|
||||||
|
async fn get_scripts_history(&self, scripts: &[Script]) -> Result<Vec<Vec<History>>>;
|
||||||
|
|
||||||
|
/// Get the transactions involved in a list of scripts
|
||||||
|
async fn get_script_history_with_retry(
|
||||||
|
&self,
|
||||||
|
script: &Script,
|
||||||
|
retries: u64,
|
||||||
|
) -> Result<Vec<History>>;
|
||||||
|
|
||||||
|
/// Get the utxos associated with a script pubkey
|
||||||
|
async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>>;
|
||||||
|
|
||||||
|
/// Verify that a transaction appears in the address script history
|
||||||
|
async fn verify_tx(
|
||||||
|
&self,
|
||||||
|
address: &Address,
|
||||||
|
tx_id: &str,
|
||||||
|
tx_hex: &str,
|
||||||
|
verify_confirmation: bool,
|
||||||
|
) -> Result<Transaction>;
|
||||||
|
}
|
||||||
@@ -6,24 +6,21 @@ use boltz_client::{
|
|||||||
swaps::boltz::{ChainSwapStates, CreateChainResponse, TransactionInfo},
|
swaps::boltz::{ChainSwapStates, CreateChainResponse, TransactionInfo},
|
||||||
ElementsLockTime, Secp256k1, Serialize, ToHex,
|
ElementsLockTime, Secp256k1, Serialize, ToHex,
|
||||||
};
|
};
|
||||||
|
use elements::{hex::FromHex, Script, Transaction};
|
||||||
use futures_util::TryFutureExt;
|
use futures_util::TryFutureExt;
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use lwk_wollet::{
|
use lwk_wollet::hashes::hex::DisplayHex;
|
||||||
elements::{hex::FromHex, Script, Transaction},
|
|
||||||
hashes::hex::DisplayHex,
|
|
||||||
History,
|
|
||||||
};
|
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
use crate::model::{BlockListener, ChainSwapUpdate, LIQUID_FEE_RATE_MSAT_PER_VBYTE};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService},
|
chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService},
|
||||||
ensure_sdk,
|
elements, ensure_sdk,
|
||||||
error::{PaymentError, SdkError, SdkResult},
|
error::{PaymentError, SdkError, SdkResult},
|
||||||
model::{
|
model::{
|
||||||
ChainSwap, Config, Direction,
|
BlockListener, BtcHistory, ChainSwap, ChainSwapUpdate, Config, Direction, LBtcHistory,
|
||||||
PaymentState::{self, *},
|
PaymentState::{self, *},
|
||||||
PaymentTxData, PaymentType, Swap, SwapScriptV2, Transaction as SdkTransaction,
|
PaymentTxData, PaymentType, Swap, SwapScriptV2, Transaction as SdkTransaction,
|
||||||
|
LIQUID_FEE_RATE_MSAT_PER_VBYTE,
|
||||||
},
|
},
|
||||||
persist::Persister,
|
persist::Persister,
|
||||||
swapper::Swapper,
|
swapper::Swapper,
|
||||||
@@ -165,6 +162,24 @@ impl ChainSwapHandler {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn fetch_script_history(&self, swap_script: &SwapScriptV2) -> Result<Vec<(String, i32)>> {
|
||||||
|
let history = match swap_script {
|
||||||
|
SwapScriptV2::Liquid(_) => self
|
||||||
|
.fetch_liquid_script_history(swap_script)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|h| (h.txid.to_hex(), h.height))
|
||||||
|
.collect(),
|
||||||
|
SwapScriptV2::Bitcoin(_) => self
|
||||||
|
.fetch_bitcoin_script_history(swap_script)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|h| (h.txid.to_hex(), h.height))
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
Ok(history)
|
||||||
|
}
|
||||||
|
|
||||||
async fn claim_confirmed_server_lockup(&self, swap: &ChainSwap) -> Result<()> {
|
async fn claim_confirmed_server_lockup(&self, swap: &ChainSwap) -> Result<()> {
|
||||||
let Some(tx_id) = swap.server_lockup_tx_id.clone() else {
|
let Some(tx_id) = swap.server_lockup_tx_id.clone() else {
|
||||||
// Skip the rescan if there is no server_lockup_tx_id yet
|
// Skip the rescan if there is no server_lockup_tx_id yet
|
||||||
@@ -172,17 +187,15 @@ impl ChainSwapHandler {
|
|||||||
};
|
};
|
||||||
let swap_id = &swap.id;
|
let swap_id = &swap.id;
|
||||||
let swap_script = swap.get_claim_swap_script()?;
|
let swap_script = swap.get_claim_swap_script()?;
|
||||||
let script_history = match swap.direction {
|
let script_history = self.fetch_script_history(&swap_script).await?;
|
||||||
Direction::Incoming => self.fetch_liquid_script_history(&swap_script).await,
|
let (_tx_history, tx_height) =
|
||||||
Direction::Outgoing => self.fetch_bitcoin_script_history(&swap_script).await,
|
script_history
|
||||||
}?;
|
.iter()
|
||||||
let tx_history = script_history
|
.find(|h| h.0.eq(&tx_id))
|
||||||
.iter()
|
.ok_or(anyhow!(
|
||||||
.find(|h| h.txid.to_hex().eq(&tx_id))
|
"Server lockup tx for Chain Swap {swap_id} was not found, txid={tx_id}"
|
||||||
.ok_or(anyhow!(
|
))?;
|
||||||
"Server lockup tx for Chain Swap {swap_id} was not found, txid={tx_id}"
|
if *tx_height > 0 {
|
||||||
))?;
|
|
||||||
if tx_history.height > 0 {
|
|
||||||
info!("Chain Swap {swap_id} server lockup tx is confirmed");
|
info!("Chain Swap {swap_id} server lockup tx is confirmed");
|
||||||
self.claim(swap_id)
|
self.claim(swap_id)
|
||||||
.await
|
.await
|
||||||
@@ -869,6 +882,7 @@ impl ChainSwapHandler {
|
|||||||
SdkTransaction::Bitcoin(tx) => self
|
SdkTransaction::Bitcoin(tx) => self
|
||||||
.bitcoin_chain_service
|
.bitcoin_chain_service
|
||||||
.broadcast(&tx)
|
.broadcast(&tx)
|
||||||
|
.await
|
||||||
.map(|tx_id| tx_id.to_hex())
|
.map(|tx_id| tx_id.to_hex())
|
||||||
.map_err(|err| PaymentError::Generic {
|
.map_err(|err| PaymentError::Generic {
|
||||||
err: err.to_string(),
|
err: err.to_string(),
|
||||||
@@ -996,7 +1010,10 @@ impl ChainSwapHandler {
|
|||||||
.to_address(self.config.network.as_bitcoin_chain())
|
.to_address(self.config.network.as_bitcoin_chain())
|
||||||
.map_err(|e| anyhow!("Could not retrieve address from swap script: {e:?}"))?
|
.map_err(|e| anyhow!("Could not retrieve address from swap script: {e:?}"))?
|
||||||
.script_pubkey();
|
.script_pubkey();
|
||||||
let utxos = self.bitcoin_chain_service.get_script_utxos(&script_pk)?;
|
let utxos = self
|
||||||
|
.bitcoin_chain_service
|
||||||
|
.get_script_utxos(&script_pk)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let SdkTransaction::Bitcoin(refund_tx) = self
|
let SdkTransaction::Bitcoin(refund_tx) = self
|
||||||
.swapper
|
.swapper
|
||||||
@@ -1015,7 +1032,8 @@ impl ChainSwapHandler {
|
|||||||
};
|
};
|
||||||
let refund_tx_id = self
|
let refund_tx_id = self
|
||||||
.bitcoin_chain_service
|
.bitcoin_chain_service
|
||||||
.broadcast(&refund_tx)?
|
.broadcast(&refund_tx)
|
||||||
|
.await?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
info!("Successfully broadcast refund for incoming Chain Swap {id}, is_cooperative: {is_cooperative}");
|
info!("Successfully broadcast refund for incoming Chain Swap {id}, is_cooperative: {is_cooperative}");
|
||||||
@@ -1229,7 +1247,8 @@ impl ChainSwapHandler {
|
|||||||
// Get full transaction
|
// Get full transaction
|
||||||
let txs = self
|
let txs = self
|
||||||
.bitcoin_chain_service
|
.bitcoin_chain_service
|
||||||
.get_transactions(&[first_tx_id])?;
|
.get_transactions(&[first_tx_id])
|
||||||
|
.await?;
|
||||||
let user_lockup_tx = txs.first().ok_or(anyhow!(
|
let user_lockup_tx = txs.first().ok_or(anyhow!(
|
||||||
"No transactions found for user lockup script for swap {}",
|
"No transactions found for user lockup script for swap {}",
|
||||||
chain_swap.id
|
chain_swap.id
|
||||||
@@ -1375,27 +1394,21 @@ impl ChainSwapHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn user_lockup_tx_exists(&self, chain_swap: &ChainSwap) -> Result<bool> {
|
async fn user_lockup_tx_exists(&self, chain_swap: &ChainSwap) -> Result<bool> {
|
||||||
let swap_script = chain_swap.get_lockup_swap_script()?;
|
let lockup_script = chain_swap.get_lockup_swap_script()?;
|
||||||
let script_history = match chain_swap.direction {
|
let script_history = self.fetch_script_history(&lockup_script).await?;
|
||||||
Direction::Incoming => self.fetch_bitcoin_script_history(&swap_script).await,
|
|
||||||
Direction::Outgoing => self.fetch_liquid_script_history(&swap_script).await,
|
|
||||||
}?;
|
|
||||||
|
|
||||||
match chain_swap.user_lockup_tx_id.clone() {
|
match chain_swap.user_lockup_tx_id.clone() {
|
||||||
Some(user_lockup_tx_id) => {
|
Some(user_lockup_tx_id) => {
|
||||||
if !script_history
|
if !script_history.iter().any(|h| h.0 == user_lockup_tx_id) {
|
||||||
.iter()
|
|
||||||
.any(|h| h.txid.to_hex() == user_lockup_tx_id)
|
|
||||||
{
|
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let txid = match script_history.first() {
|
let (txid, _tx_height) = match script_history.into_iter().nth(0) {
|
||||||
|
Some(h) => h,
|
||||||
None => {
|
None => {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
Some(h) => h.txid.to_hex(),
|
|
||||||
};
|
};
|
||||||
self.update_swap_info(&ChainSwapUpdate {
|
self.update_swap_info(&ChainSwapUpdate {
|
||||||
swap_id: chain_swap.id.clone(),
|
swap_id: chain_swap.id.clone(),
|
||||||
@@ -1441,7 +1454,7 @@ impl ChainSwapHandler {
|
|||||||
async fn fetch_bitcoin_script_history(
|
async fn fetch_bitcoin_script_history(
|
||||||
&self,
|
&self,
|
||||||
swap_script: &SwapScriptV2,
|
swap_script: &SwapScriptV2,
|
||||||
) -> Result<Vec<History>> {
|
) -> Result<Vec<BtcHistory>> {
|
||||||
let address = swap_script
|
let address = swap_script
|
||||||
.as_bitcoin_script()?
|
.as_bitcoin_script()?
|
||||||
.to_address(self.config.network.as_bitcoin_chain())
|
.to_address(self.config.network.as_bitcoin_chain())
|
||||||
@@ -1456,7 +1469,7 @@ impl ChainSwapHandler {
|
|||||||
async fn fetch_liquid_script_history(
|
async fn fetch_liquid_script_history(
|
||||||
&self,
|
&self,
|
||||||
swap_script: &SwapScriptV2,
|
swap_script: &SwapScriptV2,
|
||||||
) -> Result<Vec<History>> {
|
) -> Result<Vec<LBtcHistory>> {
|
||||||
let address = swap_script
|
let address = swap_script
|
||||||
.as_liquid_script()?
|
.as_liquid_script()?
|
||||||
.to_address(self.config.network.into())
|
.to_address(self.config.network.into())
|
||||||
|
|||||||
@@ -233,8 +233,8 @@ impl From<SdkError> for PaymentError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::bitcoin::util::bip32::Error> for PaymentError {
|
impl From<sdk_common::bitcoin::util::bip32::Error> for PaymentError {
|
||||||
fn from(err: crate::bitcoin::util::bip32::Error) -> Self {
|
fn from(err: sdk_common::bitcoin::util::bip32::Error) -> Self {
|
||||||
Self::SignerError {
|
Self::SignerError {
|
||||||
err: err.to_string(),
|
err: err.to_string(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2476,6 +2476,30 @@ impl SseDecode for crate::bindings::BitcoinAddressData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseDecode for crate::model::BlockchainExplorer {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
let mut tag_ = <i32>::sse_decode(deserializer);
|
||||||
|
match tag_ {
|
||||||
|
0 => {
|
||||||
|
let mut var_url = <String>::sse_decode(deserializer);
|
||||||
|
return crate::model::BlockchainExplorer::Electrum { url: var_url };
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let mut var_url = <String>::sse_decode(deserializer);
|
||||||
|
let mut var_useWaterfalls = <bool>::sse_decode(deserializer);
|
||||||
|
return crate::model::BlockchainExplorer::Esplora {
|
||||||
|
url: var_url,
|
||||||
|
use_waterfalls: var_useWaterfalls,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unimplemented!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseDecode for crate::model::BlockchainInfo {
|
impl SseDecode for crate::model::BlockchainInfo {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
@@ -2546,9 +2570,8 @@ impl SseDecode for crate::model::CheckMessageResponse {
|
|||||||
impl SseDecode for crate::model::Config {
|
impl SseDecode for crate::model::Config {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
let mut var_liquidElectrumUrl = <String>::sse_decode(deserializer);
|
let mut var_liquidExplorer = <crate::model::BlockchainExplorer>::sse_decode(deserializer);
|
||||||
let mut var_bitcoinElectrumUrl = <String>::sse_decode(deserializer);
|
let mut var_bitcoinExplorer = <crate::model::BlockchainExplorer>::sse_decode(deserializer);
|
||||||
let mut var_mempoolspaceUrl = <String>::sse_decode(deserializer);
|
|
||||||
let mut var_workingDir = <String>::sse_decode(deserializer);
|
let mut var_workingDir = <String>::sse_decode(deserializer);
|
||||||
let mut var_cacheDir = <Option<String>>::sse_decode(deserializer);
|
let mut var_cacheDir = <Option<String>>::sse_decode(deserializer);
|
||||||
let mut var_network = <crate::model::LiquidNetwork>::sse_decode(deserializer);
|
let mut var_network = <crate::model::LiquidNetwork>::sse_decode(deserializer);
|
||||||
@@ -2563,9 +2586,8 @@ impl SseDecode for crate::model::Config {
|
|||||||
let mut var_assetMetadata =
|
let mut var_assetMetadata =
|
||||||
<Option<Vec<crate::model::AssetMetadata>>>::sse_decode(deserializer);
|
<Option<Vec<crate::model::AssetMetadata>>>::sse_decode(deserializer);
|
||||||
return crate::model::Config {
|
return crate::model::Config {
|
||||||
liquid_electrum_url: var_liquidElectrumUrl,
|
liquid_explorer: var_liquidExplorer,
|
||||||
bitcoin_electrum_url: var_bitcoinElectrumUrl,
|
bitcoin_explorer: var_bitcoinExplorer,
|
||||||
mempoolspace_url: var_mempoolspaceUrl,
|
|
||||||
working_dir: var_workingDir,
|
working_dir: var_workingDir,
|
||||||
cache_dir: var_cacheDir,
|
cache_dir: var_cacheDir,
|
||||||
network: var_network,
|
network: var_network,
|
||||||
@@ -5055,6 +5077,39 @@ impl flutter_rust_bridge::IntoIntoDart<FrbWrapper<crate::bindings::BitcoinAddres
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
|
impl flutter_rust_bridge::IntoDart for crate::model::BlockchainExplorer {
|
||||||
|
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||||
|
match self {
|
||||||
|
crate::model::BlockchainExplorer::Electrum { url } => {
|
||||||
|
[0.into_dart(), url.into_into_dart().into_dart()].into_dart()
|
||||||
|
}
|
||||||
|
crate::model::BlockchainExplorer::Esplora {
|
||||||
|
url,
|
||||||
|
use_waterfalls,
|
||||||
|
} => [
|
||||||
|
1.into_dart(),
|
||||||
|
url.into_into_dart().into_dart(),
|
||||||
|
use_waterfalls.into_into_dart().into_dart(),
|
||||||
|
]
|
||||||
|
.into_dart(),
|
||||||
|
_ => {
|
||||||
|
unimplemented!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
|
||||||
|
for crate::model::BlockchainExplorer
|
||||||
|
{
|
||||||
|
}
|
||||||
|
impl flutter_rust_bridge::IntoIntoDart<crate::model::BlockchainExplorer>
|
||||||
|
for crate::model::BlockchainExplorer
|
||||||
|
{
|
||||||
|
fn into_into_dart(self) -> crate::model::BlockchainExplorer {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
impl flutter_rust_bridge::IntoDart for crate::model::BlockchainInfo {
|
impl flutter_rust_bridge::IntoDart for crate::model::BlockchainInfo {
|
||||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||||
[
|
[
|
||||||
@@ -5156,9 +5211,8 @@ impl flutter_rust_bridge::IntoIntoDart<crate::model::CheckMessageResponse>
|
|||||||
impl flutter_rust_bridge::IntoDart for crate::model::Config {
|
impl flutter_rust_bridge::IntoDart for crate::model::Config {
|
||||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||||
[
|
[
|
||||||
self.liquid_electrum_url.into_into_dart().into_dart(),
|
self.liquid_explorer.into_into_dart().into_dart(),
|
||||||
self.bitcoin_electrum_url.into_into_dart().into_dart(),
|
self.bitcoin_explorer.into_into_dart().into_dart(),
|
||||||
self.mempoolspace_url.into_into_dart().into_dart(),
|
|
||||||
self.working_dir.into_into_dart().into_dart(),
|
self.working_dir.into_into_dart().into_dart(),
|
||||||
self.cache_dir.into_into_dart().into_dart(),
|
self.cache_dir.into_into_dart().into_dart(),
|
||||||
self.network.into_into_dart().into_dart(),
|
self.network.into_into_dart().into_dart(),
|
||||||
@@ -7436,6 +7490,29 @@ impl SseEncode for crate::bindings::BitcoinAddressData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SseEncode for crate::model::BlockchainExplorer {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
match self {
|
||||||
|
crate::model::BlockchainExplorer::Electrum { url } => {
|
||||||
|
<i32>::sse_encode(0, serializer);
|
||||||
|
<String>::sse_encode(url, serializer);
|
||||||
|
}
|
||||||
|
crate::model::BlockchainExplorer::Esplora {
|
||||||
|
url,
|
||||||
|
use_waterfalls,
|
||||||
|
} => {
|
||||||
|
<i32>::sse_encode(1, serializer);
|
||||||
|
<String>::sse_encode(url, serializer);
|
||||||
|
<bool>::sse_encode(use_waterfalls, serializer);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unimplemented!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SseEncode for crate::model::BlockchainInfo {
|
impl SseEncode for crate::model::BlockchainInfo {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
@@ -7493,9 +7570,8 @@ impl SseEncode for crate::model::CheckMessageResponse {
|
|||||||
impl SseEncode for crate::model::Config {
|
impl SseEncode for crate::model::Config {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
<String>::sse_encode(self.liquid_electrum_url, serializer);
|
<crate::model::BlockchainExplorer>::sse_encode(self.liquid_explorer, serializer);
|
||||||
<String>::sse_encode(self.bitcoin_electrum_url, serializer);
|
<crate::model::BlockchainExplorer>::sse_encode(self.bitcoin_explorer, serializer);
|
||||||
<String>::sse_encode(self.mempoolspace_url, serializer);
|
|
||||||
<String>::sse_encode(self.working_dir, serializer);
|
<String>::sse_encode(self.working_dir, serializer);
|
||||||
<Option<String>>::sse_encode(self.cache_dir, serializer);
|
<Option<String>>::sse_encode(self.cache_dir, serializer);
|
||||||
<crate::model::LiquidNetwork>::sse_encode(self.network, serializer);
|
<crate::model::LiquidNetwork>::sse_encode(self.network, serializer);
|
||||||
@@ -9491,6 +9567,27 @@ mod io {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl CstDecode<crate::model::BlockchainExplorer> for wire_cst_blockchain_explorer {
|
||||||
|
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||||
|
fn cst_decode(self) -> crate::model::BlockchainExplorer {
|
||||||
|
match self.tag {
|
||||||
|
0 => {
|
||||||
|
let ans = unsafe { self.kind.Electrum };
|
||||||
|
crate::model::BlockchainExplorer::Electrum {
|
||||||
|
url: ans.url.cst_decode(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let ans = unsafe { self.kind.Esplora };
|
||||||
|
crate::model::BlockchainExplorer::Esplora {
|
||||||
|
url: ans.url.cst_decode(),
|
||||||
|
use_waterfalls: ans.use_waterfalls.cst_decode(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
impl CstDecode<crate::model::BlockchainInfo> for wire_cst_blockchain_info {
|
impl CstDecode<crate::model::BlockchainInfo> for wire_cst_blockchain_info {
|
||||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||||
fn cst_decode(self) -> crate::model::BlockchainInfo {
|
fn cst_decode(self) -> crate::model::BlockchainInfo {
|
||||||
@@ -9930,9 +10027,8 @@ mod io {
|
|||||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||||
fn cst_decode(self) -> crate::model::Config {
|
fn cst_decode(self) -> crate::model::Config {
|
||||||
crate::model::Config {
|
crate::model::Config {
|
||||||
liquid_electrum_url: self.liquid_electrum_url.cst_decode(),
|
liquid_explorer: self.liquid_explorer.cst_decode(),
|
||||||
bitcoin_electrum_url: self.bitcoin_electrum_url.cst_decode(),
|
bitcoin_explorer: self.bitcoin_explorer.cst_decode(),
|
||||||
mempoolspace_url: self.mempoolspace_url.cst_decode(),
|
|
||||||
working_dir: self.working_dir.cst_decode(),
|
working_dir: self.working_dir.cst_decode(),
|
||||||
cache_dir: self.cache_dir.cst_decode(),
|
cache_dir: self.cache_dir.cst_decode(),
|
||||||
network: self.network.cst_decode(),
|
network: self.network.cst_decode(),
|
||||||
@@ -11575,6 +11671,19 @@ mod io {
|
|||||||
Self::new_with_null_ptr()
|
Self::new_with_null_ptr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl NewWithNullPtr for wire_cst_blockchain_explorer {
|
||||||
|
fn new_with_null_ptr() -> Self {
|
||||||
|
Self {
|
||||||
|
tag: -1,
|
||||||
|
kind: BlockchainExplorerKind { nil__: () },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for wire_cst_blockchain_explorer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_with_null_ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
impl NewWithNullPtr for wire_cst_blockchain_info {
|
impl NewWithNullPtr for wire_cst_blockchain_info {
|
||||||
fn new_with_null_ptr() -> Self {
|
fn new_with_null_ptr() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -11630,9 +11739,8 @@ mod io {
|
|||||||
impl NewWithNullPtr for wire_cst_config {
|
impl NewWithNullPtr for wire_cst_config {
|
||||||
fn new_with_null_ptr() -> Self {
|
fn new_with_null_ptr() -> Self {
|
||||||
Self {
|
Self {
|
||||||
liquid_electrum_url: core::ptr::null_mut(),
|
liquid_explorer: Default::default(),
|
||||||
bitcoin_electrum_url: core::ptr::null_mut(),
|
bitcoin_explorer: Default::default(),
|
||||||
mempoolspace_url: core::ptr::null_mut(),
|
|
||||||
working_dir: core::ptr::null_mut(),
|
working_dir: core::ptr::null_mut(),
|
||||||
cache_dir: core::ptr::null_mut(),
|
cache_dir: core::ptr::null_mut(),
|
||||||
network: Default::default(),
|
network: Default::default(),
|
||||||
@@ -13859,6 +13967,30 @@ mod io {
|
|||||||
}
|
}
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct wire_cst_blockchain_explorer {
|
||||||
|
tag: i32,
|
||||||
|
kind: BlockchainExplorerKind,
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub union BlockchainExplorerKind {
|
||||||
|
Electrum: wire_cst_BlockchainExplorer_Electrum,
|
||||||
|
Esplora: wire_cst_BlockchainExplorer_Esplora,
|
||||||
|
nil__: (),
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct wire_cst_BlockchainExplorer_Electrum {
|
||||||
|
url: *mut wire_cst_list_prim_u_8_strict,
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct wire_cst_BlockchainExplorer_Esplora {
|
||||||
|
url: *mut wire_cst_list_prim_u_8_strict,
|
||||||
|
use_waterfalls: bool,
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub struct wire_cst_blockchain_info {
|
pub struct wire_cst_blockchain_info {
|
||||||
liquid_tip: u32,
|
liquid_tip: u32,
|
||||||
bitcoin_tip: u32,
|
bitcoin_tip: u32,
|
||||||
@@ -13884,9 +14016,8 @@ mod io {
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct wire_cst_config {
|
pub struct wire_cst_config {
|
||||||
liquid_electrum_url: *mut wire_cst_list_prim_u_8_strict,
|
liquid_explorer: wire_cst_blockchain_explorer,
|
||||||
bitcoin_electrum_url: *mut wire_cst_list_prim_u_8_strict,
|
bitcoin_explorer: wire_cst_blockchain_explorer,
|
||||||
mempoolspace_url: *mut wire_cst_list_prim_u_8_strict,
|
|
||||||
working_dir: *mut wire_cst_list_prim_u_8_strict,
|
working_dir: *mut wire_cst_list_prim_u_8_strict,
|
||||||
cache_dir: *mut wire_cst_list_prim_u_8_strict,
|
cache_dir: *mut wire_cst_list_prim_u_8_strict,
|
||||||
network: i32,
|
network: i32,
|
||||||
|
|||||||
@@ -189,6 +189,8 @@ pub(crate) mod test_utils;
|
|||||||
pub(crate) mod utils;
|
pub(crate) mod utils;
|
||||||
pub mod wallet;
|
pub mod wallet;
|
||||||
|
|
||||||
|
pub use lwk_wollet::bitcoin;
|
||||||
|
pub use lwk_wollet::elements;
|
||||||
pub use sdk_common::prelude::*;
|
pub use sdk_common::prelude::*;
|
||||||
|
|
||||||
#[allow(ambiguous_glob_reexports)]
|
#[allow(ambiguous_glob_reexports)]
|
||||||
|
|||||||
@@ -1,44 +1,75 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use boltz_client::{
|
use boltz_client::{
|
||||||
bitcoin::ScriptBuf,
|
|
||||||
boltz::{ChainPair, BOLTZ_MAINNET_URL_V2, BOLTZ_REGTEST, BOLTZ_TESTNET_URL_V2},
|
boltz::{ChainPair, BOLTZ_MAINNET_URL_V2, BOLTZ_REGTEST, BOLTZ_TESTNET_URL_V2},
|
||||||
network::{BitcoinChain, Chain, LiquidChain},
|
network::{BitcoinChain, Chain, LiquidChain},
|
||||||
swaps::boltz::{
|
swaps::boltz::{
|
||||||
CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse, Leaf, Side, SwapTree,
|
CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse, Leaf, Side, SwapTree,
|
||||||
},
|
},
|
||||||
ToHex,
|
BtcSwapScript, Keypair, LBtcSwapScript,
|
||||||
};
|
};
|
||||||
use boltz_client::{BtcSwapScript, Keypair, LBtcSwapScript};
|
|
||||||
use derivative::Derivative;
|
use derivative::Derivative;
|
||||||
use lwk_wollet::elements::{script, AssetId};
|
|
||||||
use lwk_wollet::{bitcoin::bip32, ElementsNetwork};
|
|
||||||
use maybe_sync::{MaybeSend, MaybeSync};
|
use maybe_sync::{MaybeSend, MaybeSync};
|
||||||
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
|
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
|
||||||
use rusqlite::ToSql;
|
use rusqlite::ToSql;
|
||||||
use sdk_common::prelude::*;
|
use sdk_common::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::PartialEq;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::{cmp::PartialEq, sync::Arc};
|
||||||
use strum_macros::{Display, EnumString};
|
use strum_macros::{Display, EnumString};
|
||||||
|
|
||||||
use crate::error::{PaymentError, SdkError, SdkResult};
|
|
||||||
use crate::prelude::DEFAULT_EXTERNAL_INPUT_PARSERS;
|
|
||||||
use crate::receive_swap::DEFAULT_ZERO_CONF_MAX_SAT;
|
use crate::receive_swap::DEFAULT_ZERO_CONF_MAX_SAT;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
use crate::{
|
||||||
|
bitcoin,
|
||||||
|
chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService},
|
||||||
|
elements,
|
||||||
|
error::{PaymentError, SdkError, SdkResult},
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
chain::{
|
||||||
|
bitcoin::{electrum::ElectrumBitcoinChainService, esplora::EsploraBitcoinChainService},
|
||||||
|
liquid::{electrum::ElectrumLiquidChainService, esplora::EsploraLiquidChainService},
|
||||||
|
},
|
||||||
|
prelude::DEFAULT_EXTERNAL_INPUT_PARSERS,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bitcoin::{bip32, ScriptBuf};
|
||||||
|
use elements::AssetId;
|
||||||
|
use lwk_wollet::ElementsNetwork;
|
||||||
|
use sdk_common::bitcoin::hashes::hex::ToHex as _;
|
||||||
|
|
||||||
// Uses f64 for the maximum precision when converting between units
|
// Uses f64 for the maximum precision when converting between units
|
||||||
pub const LIQUID_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1;
|
pub const LIQUID_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1;
|
||||||
pub const LIQUID_FEE_RATE_MSAT_PER_VBYTE: f32 = (LIQUID_FEE_RATE_SAT_PER_VBYTE * 1000.0) as f32;
|
pub const LIQUID_FEE_RATE_MSAT_PER_VBYTE: f32 = (LIQUID_FEE_RATE_SAT_PER_VBYTE * 1000.0) as f32;
|
||||||
pub const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology";
|
pub const BREEZ_SYNC_SERVICE_URL: &str = "https://datasync.breez.technology";
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub enum BlockchainExplorer {
|
||||||
|
Electrum {
|
||||||
|
url: String,
|
||||||
|
},
|
||||||
|
Esplora {
|
||||||
|
url: String,
|
||||||
|
/// Whether or not to use the "waterfalls" extension
|
||||||
|
use_waterfalls: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockchainExplorer {
|
||||||
|
pub(crate) fn url(&self) -> &String {
|
||||||
|
match self {
|
||||||
|
BlockchainExplorer::Electrum { url } => url,
|
||||||
|
BlockchainExplorer::Esplora { url, .. } => url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Configuration for the Liquid SDK
|
/// Configuration for the Liquid SDK
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub liquid_electrum_url: String,
|
pub liquid_explorer: BlockchainExplorer,
|
||||||
pub bitcoin_electrum_url: String,
|
pub bitcoin_explorer: BlockchainExplorer,
|
||||||
/// The mempool.space API URL, has to be in the format: `https://mempool.space/api`
|
|
||||||
pub mempoolspace_url: String,
|
|
||||||
/// Directory in which the DB and log files are stored.
|
/// Directory in which the DB and log files are stored.
|
||||||
///
|
///
|
||||||
/// Prefix can be a relative or absolute path to this directory.
|
/// Prefix can be a relative or absolute path to this directory.
|
||||||
@@ -81,9 +112,12 @@ pub struct Config {
|
|||||||
impl Config {
|
impl Config {
|
||||||
pub fn mainnet(breez_api_key: Option<String>) -> Self {
|
pub fn mainnet(breez_api_key: Option<String>) -> Self {
|
||||||
Config {
|
Config {
|
||||||
liquid_electrum_url: "elements-mainnet.breez.technology:50002".to_string(),
|
liquid_explorer: BlockchainExplorer::Electrum {
|
||||||
bitcoin_electrum_url: "bitcoin-mainnet.blockstream.info:50002".to_string(),
|
url: "elements-mainnet.breez.technology:50002".to_string(),
|
||||||
mempoolspace_url: "https://mempool.space/api".to_string(),
|
},
|
||||||
|
bitcoin_explorer: BlockchainExplorer::Electrum {
|
||||||
|
url: "bitcoin-mainnet.blockstream.info:50002".to_string(),
|
||||||
|
},
|
||||||
working_dir: ".".to_string(),
|
working_dir: ".".to_string(),
|
||||||
cache_dir: None,
|
cache_dir: None,
|
||||||
network: LiquidNetwork::Mainnet,
|
network: LiquidNetwork::Mainnet,
|
||||||
@@ -100,9 +134,12 @@ impl Config {
|
|||||||
|
|
||||||
pub fn testnet(breez_api_key: Option<String>) -> Self {
|
pub fn testnet(breez_api_key: Option<String>) -> Self {
|
||||||
Config {
|
Config {
|
||||||
liquid_electrum_url: "elements-testnet.blockstream.info:50002".to_string(),
|
liquid_explorer: BlockchainExplorer::Electrum {
|
||||||
bitcoin_electrum_url: "bitcoin-testnet.blockstream.info:50002".to_string(),
|
url: "elements-testnet.blockstream.info:50002".to_string(),
|
||||||
mempoolspace_url: "https://mempool.space/testnet/api".to_string(),
|
},
|
||||||
|
bitcoin_explorer: BlockchainExplorer::Electrum {
|
||||||
|
url: "bitcoin-testnet.blockstream.info:50002".to_string(),
|
||||||
|
},
|
||||||
working_dir: ".".to_string(),
|
working_dir: ".".to_string(),
|
||||||
cache_dir: None,
|
cache_dir: None,
|
||||||
network: LiquidNetwork::Testnet,
|
network: LiquidNetwork::Testnet,
|
||||||
@@ -119,9 +156,12 @@ impl Config {
|
|||||||
|
|
||||||
pub fn regtest() -> Self {
|
pub fn regtest() -> Self {
|
||||||
Config {
|
Config {
|
||||||
liquid_electrum_url: "localhost:19002".to_string(),
|
bitcoin_explorer: BlockchainExplorer::Electrum {
|
||||||
bitcoin_electrum_url: "localhost:19001".to_string(),
|
url: "localhost:19001".to_string(),
|
||||||
mempoolspace_url: "http://localhost/api".to_string(),
|
},
|
||||||
|
liquid_explorer: BlockchainExplorer::Electrum {
|
||||||
|
url: "localhost:19002".to_string(),
|
||||||
|
},
|
||||||
working_dir: ".".to_string(),
|
working_dir: ".".to_string(),
|
||||||
cache_dir: None,
|
cache_dir: None,
|
||||||
network: LiquidNetwork::Regtest,
|
network: LiquidNetwork::Regtest,
|
||||||
@@ -193,6 +233,28 @@ impl Config {
|
|||||||
pub fn sync_enabled(&self) -> bool {
|
pub fn sync_enabled(&self) -> bool {
|
||||||
self.sync_service_url.is_some()
|
self.sync_service_url.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bitcoin_chain_service(&self) -> Arc<dyn BitcoinChainService> {
|
||||||
|
match self.bitcoin_explorer {
|
||||||
|
BlockchainExplorer::Esplora { .. } => {
|
||||||
|
Arc::new(EsploraBitcoinChainService::new(self.clone()))
|
||||||
|
}
|
||||||
|
BlockchainExplorer::Electrum { .. } => {
|
||||||
|
Arc::new(ElectrumBitcoinChainService::new(self.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn liquid_chain_service(&self) -> Arc<dyn LiquidChainService> {
|
||||||
|
match self.liquid_explorer {
|
||||||
|
BlockchainExplorer::Esplora { .. } => {
|
||||||
|
Arc::new(EsploraLiquidChainService::new(self.clone()))
|
||||||
|
}
|
||||||
|
BlockchainExplorer::Electrum { .. } => {
|
||||||
|
Arc::new(ElectrumLiquidChainService::new(self.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Network chosen for this Liquid SDK instance. Note that it represents both the Liquid and the
|
/// Network chosen for this Liquid SDK instance. Note that it represents both the Liquid and the
|
||||||
@@ -1248,7 +1310,7 @@ impl ReceiveSwap {
|
|||||||
utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
|
utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn claim_script(&self) -> Result<script::Script> {
|
pub(crate) fn claim_script(&self) -> Result<elements::Script> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.get_swap_script()?
|
.get_swap_script()?
|
||||||
.funding_addrs
|
.funding_addrs
|
||||||
@@ -2177,6 +2239,67 @@ pub struct AcceptPaymentProposedFeesRequest {
|
|||||||
pub response: FetchPaymentProposedFeesResponse,
|
pub response: FetchPaymentProposedFeesResponse,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct History<T> {
|
||||||
|
pub txid: T,
|
||||||
|
/// Confirmation height of txid
|
||||||
|
///
|
||||||
|
/// -1 means unconfirmed with unconfirmed parents
|
||||||
|
/// 0 means unconfirmed with confirmed parents
|
||||||
|
pub height: i32,
|
||||||
|
}
|
||||||
|
pub(crate) type LBtcHistory = History<elements::Txid>;
|
||||||
|
pub(crate) type BtcHistory = History<bitcoin::Txid>;
|
||||||
|
|
||||||
|
impl<T> History<T> {
|
||||||
|
pub(crate) fn confirmed(&self) -> bool {
|
||||||
|
self.height > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||||
|
impl From<electrum_client::GetHistoryRes> for BtcHistory {
|
||||||
|
fn from(value: electrum_client::GetHistoryRes) -> Self {
|
||||||
|
Self {
|
||||||
|
txid: value.tx_hash,
|
||||||
|
height: value.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<lwk_wollet::History> for LBtcHistory {
|
||||||
|
fn from(value: lwk_wollet::History) -> Self {
|
||||||
|
Self::from(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&lwk_wollet::History> for LBtcHistory {
|
||||||
|
fn from(value: &lwk_wollet::History) -> Self {
|
||||||
|
Self {
|
||||||
|
txid: value.txid,
|
||||||
|
height: value.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) type BtcScript = bitcoin::ScriptBuf;
|
||||||
|
pub(crate) type LBtcScript = elements::Script;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct BtcScriptBalance {
|
||||||
|
/// Confirmed balance in Satoshis for the address.
|
||||||
|
pub confirmed: u64,
|
||||||
|
/// Unconfirmed balance in Satoshis for the address.
|
||||||
|
///
|
||||||
|
/// Some servers (e.g. `electrs`) return this as a negative value.
|
||||||
|
pub unconfirmed: i64,
|
||||||
|
}
|
||||||
|
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||||
|
impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
|
||||||
|
fn from(val: electrum_client::GetBalanceRes) -> Self {
|
||||||
|
Self {
|
||||||
|
confirmed: val.confirmed,
|
||||||
|
unconfirmed: val.unconfirmed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! get_invoice_amount {
|
macro_rules! get_invoice_amount {
|
||||||
($invoice:expr) => {
|
($invoice:expr) => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use boltz_client::boltz::PairLimits;
|
use boltz_client::boltz::PairLimits;
|
||||||
use boltz_client::ElementsAddress;
|
use boltz_client::ElementsAddress;
|
||||||
use electrum_client::GetBalanceRes;
|
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use lwk_wollet::elements::{secp256k1_zkp, AddressParams};
|
use lwk_wollet::elements::{secp256k1_zkp, AddressParams};
|
||||||
use lwk_wollet::elements_miniscript::slip77::MasterBlindingKey;
|
use lwk_wollet::elements_miniscript::slip77::MasterBlindingKey;
|
||||||
@@ -240,7 +239,7 @@ impl ChainReceiveSwapHandler {
|
|||||||
.to_sat();
|
.to_sat();
|
||||||
|
|
||||||
// Collect outgoing tx IDs
|
// Collect outgoing tx IDs
|
||||||
let btc_outgoing_tx_ids: Vec<HistoryTxId> = btc_lockup_outgoing_txs
|
let btc_outgoing_tx_ids: Vec<BtcHistory> = btc_lockup_outgoing_txs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|tx| {
|
.filter_map(|tx| {
|
||||||
history
|
history
|
||||||
@@ -282,19 +281,19 @@ impl ChainReceiveSwapHandler {
|
|||||||
|
|
||||||
pub(crate) struct RecoveredOnchainDataChainReceive {
|
pub(crate) struct RecoveredOnchainDataChainReceive {
|
||||||
/// LBTC tx locking up funds by the swapper
|
/// LBTC tx locking up funds by the swapper
|
||||||
pub(crate) lbtc_server_lockup_tx_id: Option<HistoryTxId>,
|
pub(crate) lbtc_server_lockup_tx_id: Option<LBtcHistory>,
|
||||||
/// LBTC tx that claims to our wallet. The final step in a successful swap.
|
/// LBTC tx that claims to our wallet. The final step in a successful swap.
|
||||||
pub(crate) lbtc_claim_tx_id: Option<HistoryTxId>,
|
pub(crate) lbtc_claim_tx_id: Option<LBtcHistory>,
|
||||||
/// LBTC tx out address for the claim tx.
|
/// LBTC tx out address for the claim tx.
|
||||||
pub(crate) lbtc_claim_address: Option<String>,
|
pub(crate) lbtc_claim_address: Option<String>,
|
||||||
/// BTC tx initiated by the payer (the "user" as per Boltz), sending funds to the swap funding address.
|
/// BTC tx initiated by the payer (the "user" as per Boltz), sending funds to the swap funding address.
|
||||||
pub(crate) btc_user_lockup_tx_id: Option<HistoryTxId>,
|
pub(crate) btc_user_lockup_tx_id: Option<BtcHistory>,
|
||||||
/// BTC total funds currently available at the swap funding address.
|
/// BTC total funds currently available at the swap funding address.
|
||||||
pub(crate) btc_user_lockup_address_balance_sat: u64,
|
pub(crate) btc_user_lockup_address_balance_sat: u64,
|
||||||
/// BTC sent to lockup address as part of lockup tx.
|
/// BTC sent to lockup address as part of lockup tx.
|
||||||
pub(crate) btc_user_lockup_amount_sat: u64,
|
pub(crate) btc_user_lockup_amount_sat: u64,
|
||||||
/// BTC tx initiated by the SDK to a user-chosen address, in case the initial funds have to be refunded.
|
/// BTC tx initiated by the SDK to a user-chosen address, in case the initial funds have to be refunded.
|
||||||
pub(crate) btc_refund_tx_id: Option<HistoryTxId>,
|
pub(crate) btc_refund_tx_id: Option<BtcHistory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RecoveredOnchainDataChainReceive {
|
impl RecoveredOnchainDataChainReceive {
|
||||||
@@ -363,8 +362,8 @@ impl RecoveredOnchainDataChainReceive {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct ReceiveChainSwapHistory {
|
pub(crate) struct ReceiveChainSwapHistory {
|
||||||
pub(crate) lbtc_claim_script_history: Vec<HistoryTxId>,
|
pub(crate) lbtc_claim_script_history: Vec<LBtcHistory>,
|
||||||
pub(crate) btc_lockup_script_history: Vec<HistoryTxId>,
|
pub(crate) btc_lockup_script_history: Vec<BtcHistory>,
|
||||||
pub(crate) btc_lockup_script_txs: Vec<boltz_client::bitcoin::Transaction>,
|
pub(crate) btc_lockup_script_txs: Vec<bitcoin::Transaction>,
|
||||||
pub(crate) btc_lockup_script_balance: Option<GetBalanceRes>,
|
pub(crate) btc_lockup_script_balance: Option<BtcScriptBalance>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,13 +204,13 @@ impl ChainSendSwapHandler {
|
|||||||
|
|
||||||
pub(crate) struct RecoveredOnchainDataChainSend {
|
pub(crate) struct RecoveredOnchainDataChainSend {
|
||||||
/// LBTC tx initiated by the SDK (the "user" as per Boltz), sending funds to the swap funding address.
|
/// LBTC tx initiated by the SDK (the "user" as per Boltz), sending funds to the swap funding address.
|
||||||
pub(crate) lbtc_user_lockup_tx_id: Option<HistoryTxId>,
|
pub(crate) lbtc_user_lockup_tx_id: Option<LBtcHistory>,
|
||||||
/// LBTC tx initiated by the SDK to itself, in case the initial funds have to be refunded.
|
/// LBTC tx initiated by the SDK to itself, in case the initial funds have to be refunded.
|
||||||
pub(crate) lbtc_refund_tx_id: Option<HistoryTxId>,
|
pub(crate) lbtc_refund_tx_id: Option<LBtcHistory>,
|
||||||
/// BTC tx locking up funds by the swapper
|
/// BTC tx locking up funds by the swapper
|
||||||
pub(crate) btc_server_lockup_tx_id: Option<HistoryTxId>,
|
pub(crate) btc_server_lockup_tx_id: Option<BtcHistory>,
|
||||||
/// BTC tx that claims to the final BTC destination address. The final step in a successful swap.
|
/// BTC tx that claims to the final BTC destination address. The final step in a successful swap.
|
||||||
pub(crate) btc_claim_tx_id: Option<HistoryTxId>,
|
pub(crate) btc_claim_tx_id: Option<BtcHistory>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We have to be careful around overwriting the RefundPending state, as this swap monitored
|
// TODO: We have to be careful around overwriting the RefundPending state, as this swap monitored
|
||||||
@@ -254,7 +254,7 @@ impl RecoveredOnchainDataChainSend {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct SendChainSwapHistory {
|
pub(crate) struct SendChainSwapHistory {
|
||||||
pub(crate) lbtc_lockup_script_history: Vec<HistoryTxId>,
|
pub(crate) lbtc_lockup_script_history: Vec<LBtcHistory>,
|
||||||
pub(crate) btc_claim_script_history: Vec<HistoryTxId>,
|
pub(crate) btc_claim_script_history: Vec<BtcHistory>,
|
||||||
pub(crate) btc_claim_script_txs: Vec<boltz_client::bitcoin::Transaction>,
|
pub(crate) btc_claim_script_txs: Vec<bitcoin::Transaction>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,9 +180,9 @@ impl ReceiveSwapHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct RecoveredOnchainDataReceive {
|
pub(crate) struct RecoveredOnchainDataReceive {
|
||||||
pub(crate) lockup_tx_id: Option<HistoryTxId>,
|
pub(crate) lockup_tx_id: Option<LBtcHistory>,
|
||||||
pub(crate) claim_tx_id: Option<HistoryTxId>,
|
pub(crate) claim_tx_id: Option<LBtcHistory>,
|
||||||
pub(crate) mrh_tx_id: Option<HistoryTxId>,
|
pub(crate) mrh_tx_id: Option<LBtcHistory>,
|
||||||
pub(crate) mrh_amount_sat: Option<u64>,
|
pub(crate) mrh_amount_sat: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +217,6 @@ impl RecoveredOnchainDataReceive {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct ReceiveSwapHistory {
|
pub(crate) struct ReceiveSwapHistory {
|
||||||
pub(crate) lbtc_claim_script_history: Vec<HistoryTxId>,
|
pub(crate) lbtc_claim_script_history: Vec<LBtcHistory>,
|
||||||
pub(crate) lbtc_mrh_script_history: Vec<HistoryTxId>,
|
pub(crate) lbtc_mrh_script_history: Vec<LBtcHistory>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ impl SendSwapHandler {
|
|||||||
.ok_or(anyhow::anyhow!("no funding address found"))?
|
.ok_or(anyhow::anyhow!("no funding address found"))?
|
||||||
.script_pubkey();
|
.script_pubkey();
|
||||||
|
|
||||||
let empty_history = Vec::<HistoryTxId>::new();
|
let empty_history = Vec::<LBtcHistory>::new();
|
||||||
let history = context
|
let history = context
|
||||||
.lbtc_script_to_history_map
|
.lbtc_script_to_history_map
|
||||||
.get(&lockup_script)
|
.get(&lockup_script)
|
||||||
@@ -145,10 +145,10 @@ impl SendSwapHandler {
|
|||||||
fn recover_onchain_data(
|
fn recover_onchain_data(
|
||||||
tx_map: &TxMap,
|
tx_map: &TxMap,
|
||||||
swap_id: &str,
|
swap_id: &str,
|
||||||
history: &[HistoryTxId],
|
wallet_history: &[LBtcHistory],
|
||||||
) -> Result<RecoveredOnchainDataSend> {
|
) -> Result<RecoveredOnchainDataSend> {
|
||||||
// If a history tx is one of our outgoing txs, it's a lockup tx
|
// If a history tx is one of our outgoing txs, it's a lockup tx
|
||||||
let lockup_tx_id = history
|
let lockup_tx_id = wallet_history
|
||||||
.iter()
|
.iter()
|
||||||
.find(|&tx| tx_map.outgoing_tx_map.contains_key::<Txid>(&tx.txid))
|
.find(|&tx| tx_map.outgoing_tx_map.contains_key::<Txid>(&tx.txid))
|
||||||
.cloned();
|
.cloned();
|
||||||
@@ -158,7 +158,7 @@ impl SendSwapHandler {
|
|||||||
//
|
//
|
||||||
// Only find the claim_tx from the history if we find a lockup_tx. Not doing so will select
|
// Only find the claim_tx from the history if we find a lockup_tx. Not doing so will select
|
||||||
// the first tx as the claim, whereas we should check that the claim is not the lockup.
|
// the first tx as the claim, whereas we should check that the claim is not the lockup.
|
||||||
history
|
wallet_history
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&tx| !tx_map.incoming_tx_map.contains_key::<Txid>(&tx.txid))
|
.filter(|&tx| !tx_map.incoming_tx_map.contains_key::<Txid>(&tx.txid))
|
||||||
.find(|&tx| !tx_map.outgoing_tx_map.contains_key::<Txid>(&tx.txid))
|
.find(|&tx| !tx_map.outgoing_tx_map.contains_key::<Txid>(&tx.txid))
|
||||||
@@ -169,7 +169,7 @@ impl SendSwapHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// If a history tx is one of our incoming txs, it's a refund tx
|
// If a history tx is one of our incoming txs, it's a refund tx
|
||||||
let refund_tx_id = history
|
let refund_tx_id = wallet_history
|
||||||
.iter()
|
.iter()
|
||||||
.find(|&tx| tx_map.incoming_tx_map.contains_key::<Txid>(&tx.txid))
|
.find(|&tx| tx_map.incoming_tx_map.contains_key::<Txid>(&tx.txid))
|
||||||
.cloned();
|
.cloned();
|
||||||
@@ -239,9 +239,9 @@ impl SendSwapHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct RecoveredOnchainDataSend {
|
pub(crate) struct RecoveredOnchainDataSend {
|
||||||
pub(crate) lockup_tx_id: Option<HistoryTxId>,
|
pub(crate) lockup_tx_id: Option<LBtcHistory>,
|
||||||
pub(crate) claim_tx_id: Option<HistoryTxId>,
|
pub(crate) claim_tx_id: Option<LBtcHistory>,
|
||||||
pub(crate) refund_tx_id: Option<HistoryTxId>,
|
pub(crate) refund_tx_id: Option<LBtcHistory>,
|
||||||
pub(crate) preimage: Option<String>,
|
pub(crate) preimage: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ pub(crate) use self::handle_chain_send_swap::ChainSendSwapHandler;
|
|||||||
pub(crate) use self::handle_receive_swap::ReceiveSwapHandler;
|
pub(crate) use self::handle_receive_swap::ReceiveSwapHandler;
|
||||||
pub(crate) use self::handle_send_swap::SendSwapHandler;
|
pub(crate) use self::handle_send_swap::SendSwapHandler;
|
||||||
|
|
||||||
use super::model::{HistoryTxId, TxMap};
|
use super::model::TxMap;
|
||||||
|
use crate::model::LBtcHistory;
|
||||||
|
|
||||||
/// Helper function for determining lockup and claim transactions in incoming swaps
|
/// Helper function for determining lockup and claim transactions in incoming swaps
|
||||||
pub(crate) fn determine_incoming_lockup_and_claim_txs(
|
pub(crate) fn determine_incoming_lockup_and_claim_txs(
|
||||||
history: &[HistoryTxId],
|
history: &[LBtcHistory],
|
||||||
tx_map: &TxMap,
|
tx_map: &TxMap,
|
||||||
) -> (Option<HistoryTxId>, Option<HistoryTxId>) {
|
) -> (Option<LBtcHistory>, Option<LBtcHistory>) {
|
||||||
match history.len() {
|
match history.len() {
|
||||||
// Only lockup tx available
|
// Only lockup tx available
|
||||||
1 => (Some(history[0].clone()), None),
|
1 => (Some(history[0].clone()), None),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ mod test {
|
|||||||
model::PaymentState,
|
model::PaymentState,
|
||||||
recover::handlers::{
|
recover::handlers::{
|
||||||
handle_chain_receive_swap::RecoveredOnchainDataChainReceive,
|
handle_chain_receive_swap::RecoveredOnchainDataChainReceive,
|
||||||
tests::test::create_history_txid,
|
tests::{create_btc_history_txid, create_lbtc_history_txid},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use boltz_client::boltz::PairLimits;
|
use boltz_client::boltz::PairLimits;
|
||||||
@@ -15,10 +15,10 @@ mod test {
|
|||||||
#[sdk_macros::test_all]
|
#[sdk_macros::test_all]
|
||||||
fn test_derive_partial_state_with_btc_lockup_and_lbtc_claim() {
|
fn test_derive_partial_state_with_btc_lockup_and_lbtc_claim() {
|
||||||
let recovered_data = RecoveredOnchainDataChainReceive {
|
let recovered_data = RecoveredOnchainDataChainReceive {
|
||||||
lbtc_server_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_server_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_claim_tx_id: Some(create_history_txid("2222", 101)),
|
lbtc_claim_tx_id: Some(create_lbtc_history_txid("2222", 101)),
|
||||||
lbtc_claim_address: Some("lq1qqvynd50t4tajashdguell7nu9gycuqqd869w8vqww9ys9dsz7szdfeu7pwe4yzzme28qsluyfyrtqmq9scl5ydw4lesx3c5qu".to_string()),
|
lbtc_claim_address: Some("lq1qqvynd50t4tajashdguell7nu9gycuqqd869w8vqww9ys9dsz7szdfeu7pwe4yzzme28qsluyfyrtqmq9scl5ydw4lesx3c5qu".to_string()),
|
||||||
btc_user_lockup_tx_id: Some(create_history_txid("3333", 102)),
|
btc_user_lockup_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
btc_user_lockup_address_balance_sat: 0,
|
btc_user_lockup_address_balance_sat: 0,
|
||||||
btc_user_lockup_amount_sat: 100000,
|
btc_user_lockup_amount_sat: 100000,
|
||||||
btc_refund_tx_id: None,
|
btc_refund_tx_id: None,
|
||||||
@@ -36,10 +36,10 @@ mod test {
|
|||||||
|
|
||||||
// Test with unconfirmed claim
|
// Test with unconfirmed claim
|
||||||
let recovered_data = RecoveredOnchainDataChainReceive {
|
let recovered_data = RecoveredOnchainDataChainReceive {
|
||||||
lbtc_server_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_server_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_claim_tx_id: Some(create_history_txid("2222", 0)), // Unconfirmed claim
|
lbtc_claim_tx_id: Some(create_lbtc_history_txid("2222", 0)), // Unconfirmed claim
|
||||||
lbtc_claim_address: Some("lq1qqvynd50t4tajashdguell7nu9gycuqqd869w8vqww9ys9dsz7szdfeu7pwe4yzzme28qsluyfyrtqmq9scl5ydw4lesx3c5qu".to_string()),
|
lbtc_claim_address: Some("lq1qqvynd50t4tajashdguell7nu9gycuqqd869w8vqww9ys9dsz7szdfeu7pwe4yzzme28qsluyfyrtqmq9scl5ydw4lesx3c5qu".to_string()),
|
||||||
btc_user_lockup_tx_id: Some(create_history_txid("3333", 102)),
|
btc_user_lockup_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
btc_user_lockup_address_balance_sat: 0,
|
btc_user_lockup_address_balance_sat: 0,
|
||||||
btc_user_lockup_amount_sat: 100000,
|
btc_user_lockup_amount_sat: 100000,
|
||||||
btc_refund_tx_id: None,
|
btc_refund_tx_id: None,
|
||||||
@@ -60,13 +60,13 @@ mod test {
|
|||||||
fn test_derive_partial_state_with_btc_lockup_and_btc_refund() {
|
fn test_derive_partial_state_with_btc_lockup_and_btc_refund() {
|
||||||
// Test with confirmed refund
|
// Test with confirmed refund
|
||||||
let recovered_data = RecoveredOnchainDataChainReceive {
|
let recovered_data = RecoveredOnchainDataChainReceive {
|
||||||
lbtc_server_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_server_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_claim_tx_id: None,
|
lbtc_claim_tx_id: None,
|
||||||
lbtc_claim_address: None,
|
lbtc_claim_address: None,
|
||||||
btc_user_lockup_tx_id: Some(create_history_txid("3333", 102)),
|
btc_user_lockup_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
btc_user_lockup_address_balance_sat: 0,
|
btc_user_lockup_address_balance_sat: 0,
|
||||||
btc_user_lockup_amount_sat: 100000,
|
btc_user_lockup_amount_sat: 100000,
|
||||||
btc_refund_tx_id: Some(create_history_txid("4444", 103)), // Confirmed refund
|
btc_refund_tx_id: Some(create_btc_history_txid("4444", 103)), // Confirmed refund
|
||||||
};
|
};
|
||||||
|
|
||||||
// When there's a lockup and confirmed refund tx, it should be Failed
|
// When there's a lockup and confirmed refund tx, it should be Failed
|
||||||
@@ -81,13 +81,13 @@ mod test {
|
|||||||
|
|
||||||
// Test with unconfirmed refund
|
// Test with unconfirmed refund
|
||||||
let recovered_data = RecoveredOnchainDataChainReceive {
|
let recovered_data = RecoveredOnchainDataChainReceive {
|
||||||
lbtc_server_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_server_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_claim_tx_id: None,
|
lbtc_claim_tx_id: None,
|
||||||
lbtc_claim_address: None,
|
lbtc_claim_address: None,
|
||||||
btc_user_lockup_tx_id: Some(create_history_txid("3333", 102)),
|
btc_user_lockup_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
btc_user_lockup_address_balance_sat: 0,
|
btc_user_lockup_address_balance_sat: 0,
|
||||||
btc_user_lockup_amount_sat: 100000,
|
btc_user_lockup_amount_sat: 100000,
|
||||||
btc_refund_tx_id: Some(create_history_txid("4444", 0)), // Unconfirmed refund
|
btc_refund_tx_id: Some(create_btc_history_txid("4444", 0)), // Unconfirmed refund
|
||||||
};
|
};
|
||||||
|
|
||||||
// When there's a lockup and unconfirmed refund tx, it should be RefundPending
|
// When there's a lockup and unconfirmed refund tx, it should be RefundPending
|
||||||
@@ -108,7 +108,7 @@ mod test {
|
|||||||
lbtc_server_lockup_tx_id: None,
|
lbtc_server_lockup_tx_id: None,
|
||||||
lbtc_claim_tx_id: None,
|
lbtc_claim_tx_id: None,
|
||||||
lbtc_claim_address: None,
|
lbtc_claim_address: None,
|
||||||
btc_user_lockup_tx_id: Some(create_history_txid("3333", 102)),
|
btc_user_lockup_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
btc_user_lockup_address_balance_sat: 0,
|
btc_user_lockup_address_balance_sat: 0,
|
||||||
btc_user_lockup_amount_sat: 100000,
|
btc_user_lockup_amount_sat: 100000,
|
||||||
btc_refund_tx_id: None,
|
btc_refund_tx_id: None,
|
||||||
@@ -135,7 +135,7 @@ mod test {
|
|||||||
lbtc_server_lockup_tx_id: None,
|
lbtc_server_lockup_tx_id: None,
|
||||||
lbtc_claim_tx_id: None,
|
lbtc_claim_tx_id: None,
|
||||||
lbtc_claim_address: None,
|
lbtc_claim_address: None,
|
||||||
btc_user_lockup_tx_id: Some(create_history_txid("3333", 102)),
|
btc_user_lockup_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
btc_user_lockup_address_balance_sat: 100000, // Funds still in address
|
btc_user_lockup_address_balance_sat: 100000, // Funds still in address
|
||||||
btc_user_lockup_amount_sat: 100000,
|
btc_user_lockup_amount_sat: 100000,
|
||||||
btc_refund_tx_id: None,
|
btc_refund_tx_id: None,
|
||||||
@@ -161,7 +161,7 @@ mod test {
|
|||||||
lbtc_server_lockup_tx_id: None,
|
lbtc_server_lockup_tx_id: None,
|
||||||
lbtc_claim_tx_id: None,
|
lbtc_claim_tx_id: None,
|
||||||
lbtc_claim_address: None,
|
lbtc_claim_address: None,
|
||||||
btc_user_lockup_tx_id: Some(create_history_txid("3333", 102)),
|
btc_user_lockup_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
btc_user_lockup_address_balance_sat: 5000,
|
btc_user_lockup_address_balance_sat: 5000,
|
||||||
btc_user_lockup_amount_sat: 5000, // Below minimum
|
btc_user_lockup_amount_sat: 5000, // Below minimum
|
||||||
btc_refund_tx_id: None,
|
btc_refund_tx_id: None,
|
||||||
@@ -178,7 +178,7 @@ mod test {
|
|||||||
lbtc_server_lockup_tx_id: None,
|
lbtc_server_lockup_tx_id: None,
|
||||||
lbtc_claim_tx_id: None,
|
lbtc_claim_tx_id: None,
|
||||||
lbtc_claim_address: None,
|
lbtc_claim_address: None,
|
||||||
btc_user_lockup_tx_id: Some(create_history_txid("3333", 102)),
|
btc_user_lockup_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
btc_user_lockup_address_balance_sat: 3000000,
|
btc_user_lockup_address_balance_sat: 3000000,
|
||||||
btc_user_lockup_amount_sat: 3000000, // Above maximum
|
btc_user_lockup_amount_sat: 3000000, // Above maximum
|
||||||
btc_refund_tx_id: None,
|
btc_refund_tx_id: None,
|
||||||
@@ -195,7 +195,7 @@ mod test {
|
|||||||
lbtc_server_lockup_tx_id: None,
|
lbtc_server_lockup_tx_id: None,
|
||||||
lbtc_claim_tx_id: None,
|
lbtc_claim_tx_id: None,
|
||||||
lbtc_claim_address: None,
|
lbtc_claim_address: None,
|
||||||
btc_user_lockup_tx_id: Some(create_history_txid("3333", 102)),
|
btc_user_lockup_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
btc_user_lockup_address_balance_sat: 150000,
|
btc_user_lockup_address_balance_sat: 150000,
|
||||||
btc_user_lockup_amount_sat: 150000, // Different from expected
|
btc_user_lockup_amount_sat: 150000, // Different from expected
|
||||||
btc_refund_tx_id: None,
|
btc_refund_tx_id: None,
|
||||||
@@ -237,13 +237,13 @@ mod test {
|
|||||||
fn test_derive_partial_state_with_lockup_claim_refund() {
|
fn test_derive_partial_state_with_lockup_claim_refund() {
|
||||||
// This is an edge case where both claim and refund txs exist
|
// This is an edge case where both claim and refund txs exist
|
||||||
let recovered_data = RecoveredOnchainDataChainReceive {
|
let recovered_data = RecoveredOnchainDataChainReceive {
|
||||||
lbtc_server_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_server_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_claim_tx_id: Some(create_history_txid("2222", 101)),
|
lbtc_claim_tx_id: Some(create_lbtc_history_txid("2222", 101)),
|
||||||
lbtc_claim_address: Some("lq1qqvynd50t4tajashdguell7nu9gycuqqd869w8vqww9ys9dsz7szdfeu7pwe4yzzme28qsluyfyrtqmq9scl5ydw4lesx3c5qu".to_string()),
|
lbtc_claim_address: Some("lq1qqvynd50t4tajashdguell7nu9gycuqqd869w8vqww9ys9dsz7szdfeu7pwe4yzzme28qsluyfyrtqmq9scl5ydw4lesx3c5qu".to_string()),
|
||||||
btc_user_lockup_tx_id: Some(create_history_txid("3333", 102)),
|
btc_user_lockup_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
btc_user_lockup_address_balance_sat: 0,
|
btc_user_lockup_address_balance_sat: 0,
|
||||||
btc_user_lockup_amount_sat: 100000,
|
btc_user_lockup_amount_sat: 100000,
|
||||||
btc_refund_tx_id: Some(create_history_txid("4444", 103)),
|
btc_refund_tx_id: Some(create_btc_history_txid("4444", 103)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Complete state should take precedence over refund
|
// Complete state should take precedence over refund
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::{
|
||||||
chain::liquid::MockLiquidChainService,
|
bitcoin, elements,
|
||||||
model::{ChainSwap, PaymentState, SwapMetadata},
|
model::{BtcHistory, BtcScriptBalance, ChainSwap, LBtcHistory, PaymentState, SwapMetadata},
|
||||||
recover::{
|
recover::{
|
||||||
handlers::{tests::test::create_mock_lbtc_wallet_tx, ChainReceiveSwapHandler},
|
handlers::{tests::create_mock_lbtc_wallet_tx, ChainReceiveSwapHandler},
|
||||||
model::{HistoryTxId, RecoveryContext, TxMap},
|
model::{RecoveryContext, TxMap},
|
||||||
},
|
},
|
||||||
swapper::MockSwapper,
|
swapper::MockSwapper,
|
||||||
|
test_utils::chain::MockLiquidChainService,
|
||||||
};
|
};
|
||||||
use boltz_client::{
|
use bitcoin::{transaction::Version, Sequence};
|
||||||
bitcoin::{self, transaction::Version, Sequence},
|
use boltz_client::{Amount, LockTime};
|
||||||
Amount, LockTime,
|
use lwk_wollet::elements_miniscript::slip77::MasterBlindingKey;
|
||||||
};
|
|
||||||
use electrum_client::GetBalanceRes;
|
|
||||||
use lwk_wollet::{
|
|
||||||
elements::{self, Txid},
|
|
||||||
elements_miniscript::slip77::MasterBlindingKey,
|
|
||||||
};
|
|
||||||
use std::{collections::HashMap, str::FromStr, sync::Arc};
|
use std::{collections::HashMap, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||||
@@ -204,7 +199,7 @@ mod test {
|
|||||||
// Add balance to the lockup address to simulate funds still there
|
// Add balance to the lockup address to simulate funds still there
|
||||||
recovery_context.btc_script_to_balance_map.insert(
|
recovery_context.btc_script_to_balance_map.insert(
|
||||||
btc_lockup_script.clone(),
|
btc_lockup_script.clone(),
|
||||||
GetBalanceRes {
|
BtcScriptBalance {
|
||||||
confirmed: chain_swap.payer_amount_sat,
|
confirmed: chain_swap.payer_amount_sat,
|
||||||
unconfirmed: 0,
|
unconfirmed: 0,
|
||||||
},
|
},
|
||||||
@@ -416,7 +411,7 @@ mod test {
|
|||||||
let computed_txid_str = computed_txid.to_string();
|
let computed_txid_str = computed_txid.to_string();
|
||||||
|
|
||||||
// Create history tx with the computed txid
|
// Create history tx with the computed txid
|
||||||
let history_tx = HistoryTxId {
|
let history_tx = BtcHistory {
|
||||||
txid: computed_txid.to_string().parse().unwrap(),
|
txid: computed_txid.to_string().parse().unwrap(),
|
||||||
height: height as i32,
|
height: height as i32,
|
||||||
};
|
};
|
||||||
@@ -446,7 +441,7 @@ mod test {
|
|||||||
// Set balance to 0 (funds have been used)
|
// Set balance to 0 (funds have been used)
|
||||||
context.btc_script_to_balance_map.insert(
|
context.btc_script_to_balance_map.insert(
|
||||||
lockup_script.clone(),
|
lockup_script.clone(),
|
||||||
GetBalanceRes {
|
BtcScriptBalance {
|
||||||
confirmed: 0,
|
confirmed: 0,
|
||||||
unconfirmed: 0,
|
unconfirmed: 0,
|
||||||
},
|
},
|
||||||
@@ -488,14 +483,8 @@ mod test {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create history tx for refund
|
// Create history tx for refund
|
||||||
let refund_bitcoin_txid: Txid = refund_tx
|
let refund_history_tx = BtcHistory {
|
||||||
.clone()
|
txid: refund_tx.compute_txid(),
|
||||||
.compute_txid()
|
|
||||||
.to_string()
|
|
||||||
.parse()
|
|
||||||
.unwrap();
|
|
||||||
let refund_history_tx = HistoryTxId {
|
|
||||||
txid: refund_bitcoin_txid,
|
|
||||||
height: refund_height as i32,
|
height: refund_height as i32,
|
||||||
};
|
};
|
||||||
// Add refund tx to script history
|
// Add refund tx to script history
|
||||||
@@ -536,7 +525,7 @@ mod test {
|
|||||||
let mut history = Vec::new();
|
let mut history = Vec::new();
|
||||||
for (tx_id_hex, height) in tx_ids {
|
for (tx_id_hex, height) in tx_ids {
|
||||||
let tx_id = elements::Txid::from_str(tx_id_hex).unwrap();
|
let tx_id = elements::Txid::from_str(tx_id_hex).unwrap();
|
||||||
history.push(HistoryTxId {
|
history.push(LBtcHistory {
|
||||||
txid: tx_id,
|
txid: tx_id,
|
||||||
height: *height as i32,
|
height: *height as i32,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ mod test {
|
|||||||
use crate::{
|
use crate::{
|
||||||
model::PaymentState,
|
model::PaymentState,
|
||||||
recover::handlers::{
|
recover::handlers::{
|
||||||
handle_chain_send_swap::RecoveredOnchainDataChainSend, tests::test::create_history_txid,
|
handle_chain_send_swap::RecoveredOnchainDataChainSend,
|
||||||
|
tests::{create_btc_history_txid, create_lbtc_history_txid},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -13,10 +14,10 @@ mod test {
|
|||||||
#[sdk_macros::test_all]
|
#[sdk_macros::test_all]
|
||||||
fn test_derive_partial_state_with_lbtc_lockup_and_btc_claim() {
|
fn test_derive_partial_state_with_lbtc_lockup_and_btc_claim() {
|
||||||
let recovered_data = RecoveredOnchainDataChainSend {
|
let recovered_data = RecoveredOnchainDataChainSend {
|
||||||
lbtc_user_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_user_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_refund_tx_id: None,
|
lbtc_refund_tx_id: None,
|
||||||
btc_server_lockup_tx_id: Some(create_history_txid("2222", 101)),
|
btc_server_lockup_tx_id: Some(create_btc_history_txid("2222", 101)),
|
||||||
btc_claim_tx_id: Some(create_history_txid("3333", 102)),
|
btc_claim_tx_id: Some(create_btc_history_txid("3333", 102)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// When there's a lockup and confirmed claim tx, it should be Complete
|
// When there's a lockup and confirmed claim tx, it should be Complete
|
||||||
@@ -31,10 +32,10 @@ mod test {
|
|||||||
|
|
||||||
// Test with unconfirmed claim
|
// Test with unconfirmed claim
|
||||||
let recovered_data = RecoveredOnchainDataChainSend {
|
let recovered_data = RecoveredOnchainDataChainSend {
|
||||||
lbtc_user_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_user_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_refund_tx_id: None,
|
lbtc_refund_tx_id: None,
|
||||||
btc_server_lockup_tx_id: Some(create_history_txid("2222", 101)),
|
btc_server_lockup_tx_id: Some(create_btc_history_txid("2222", 101)),
|
||||||
btc_claim_tx_id: Some(create_history_txid("3333", 0)), // Unconfirmed claim
|
btc_claim_tx_id: Some(create_btc_history_txid("3333", 0)), // Unconfirmed claim
|
||||||
};
|
};
|
||||||
|
|
||||||
// When there's a lockup and unconfirmed claim tx, it should be Pending
|
// When there's a lockup and unconfirmed claim tx, it should be Pending
|
||||||
@@ -52,9 +53,9 @@ mod test {
|
|||||||
fn test_derive_partial_state_with_lockup_and_refund() {
|
fn test_derive_partial_state_with_lockup_and_refund() {
|
||||||
// Test with confirmed refund
|
// Test with confirmed refund
|
||||||
let recovered_data = RecoveredOnchainDataChainSend {
|
let recovered_data = RecoveredOnchainDataChainSend {
|
||||||
lbtc_user_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_user_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_refund_tx_id: Some(create_history_txid("4444", 102)),
|
lbtc_refund_tx_id: Some(create_lbtc_history_txid("4444", 102)),
|
||||||
btc_server_lockup_tx_id: Some(create_history_txid("2222", 101)),
|
btc_server_lockup_tx_id: Some(create_btc_history_txid("2222", 101)),
|
||||||
btc_claim_tx_id: None,
|
btc_claim_tx_id: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,9 +71,9 @@ mod test {
|
|||||||
|
|
||||||
// Test with unconfirmed refund
|
// Test with unconfirmed refund
|
||||||
let recovered_data = RecoveredOnchainDataChainSend {
|
let recovered_data = RecoveredOnchainDataChainSend {
|
||||||
lbtc_user_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_user_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_refund_tx_id: Some(create_history_txid("4444", 0)), // Unconfirmed refund
|
lbtc_refund_tx_id: Some(create_lbtc_history_txid("4444", 0)), // Unconfirmed refund
|
||||||
btc_server_lockup_tx_id: Some(create_history_txid("2222", 101)),
|
btc_server_lockup_tx_id: Some(create_btc_history_txid("2222", 101)),
|
||||||
btc_claim_tx_id: None,
|
btc_claim_tx_id: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ mod test {
|
|||||||
#[sdk_macros::test_all]
|
#[sdk_macros::test_all]
|
||||||
fn test_derive_partial_state_with_lockup_only() {
|
fn test_derive_partial_state_with_lockup_only() {
|
||||||
let recovered_data = RecoveredOnchainDataChainSend {
|
let recovered_data = RecoveredOnchainDataChainSend {
|
||||||
lbtc_user_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_user_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_refund_tx_id: None,
|
lbtc_refund_tx_id: None,
|
||||||
btc_server_lockup_tx_id: None,
|
btc_server_lockup_tx_id: None,
|
||||||
btc_claim_tx_id: None,
|
btc_claim_tx_id: None,
|
||||||
@@ -132,10 +133,10 @@ mod test {
|
|||||||
fn test_derive_partial_state_with_lockup_claim_refund() {
|
fn test_derive_partial_state_with_lockup_claim_refund() {
|
||||||
// This is an edge case where both claim and refund txs exist
|
// This is an edge case where both claim and refund txs exist
|
||||||
let recovered_data = RecoveredOnchainDataChainSend {
|
let recovered_data = RecoveredOnchainDataChainSend {
|
||||||
lbtc_user_lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lbtc_user_lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
lbtc_refund_tx_id: Some(create_history_txid("4444", 102)),
|
lbtc_refund_tx_id: Some(create_lbtc_history_txid("4444", 102)),
|
||||||
btc_server_lockup_tx_id: Some(create_history_txid("2222", 101)),
|
btc_server_lockup_tx_id: Some(create_btc_history_txid("2222", 101)),
|
||||||
btc_claim_tx_id: Some(create_history_txid("3333", 103)),
|
btc_claim_tx_id: Some(create_btc_history_txid("3333", 103)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Complete state should take precedence over refund
|
// Complete state should take precedence over refund
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::{
|
||||||
|
bitcoin,
|
||||||
chain::liquid::MockLiquidChainService,
|
chain::liquid::MockLiquidChainService,
|
||||||
model::{ChainSwap, PaymentState, SwapMetadata},
|
elements,
|
||||||
|
model::{BtcHistory, ChainSwap, LBtcHistory, PaymentState, SwapMetadata},
|
||||||
recover::{
|
recover::{
|
||||||
handlers::{tests::test::create_mock_lbtc_wallet_tx, ChainSendSwapHandler},
|
handlers::{tests::create_mock_lbtc_wallet_tx, ChainSendSwapHandler},
|
||||||
model::{HistoryTxId, RecoveryContext, TxMap},
|
model::{RecoveryContext, TxMap},
|
||||||
},
|
},
|
||||||
swapper::MockSwapper,
|
swapper::MockSwapper,
|
||||||
};
|
};
|
||||||
use boltz_client::{
|
use bitcoin::OutPoint;
|
||||||
bitcoin::{self, OutPoint},
|
use bitcoin::{transaction::Version, ScriptBuf, Sequence};
|
||||||
Amount, LockTime,
|
use boltz_client::{Amount, LockTime};
|
||||||
};
|
use lwk_wollet::elements_miniscript::slip77::MasterBlindingKey;
|
||||||
use lwk_wollet::{
|
|
||||||
bitcoin::{transaction::Version, ScriptBuf, Sequence},
|
|
||||||
elements::{self},
|
|
||||||
elements_miniscript::slip77::MasterBlindingKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::{collections::HashMap, str::FromStr, sync::Arc};
|
use std::{collections::HashMap, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
@@ -364,7 +361,7 @@ mod test {
|
|||||||
let tx_id = elements::Txid::from_str(tx_id_hex).unwrap();
|
let tx_id = elements::Txid::from_str(tx_id_hex).unwrap();
|
||||||
|
|
||||||
// Create history tx
|
// Create history tx
|
||||||
let history_tx = HistoryTxId {
|
let history_tx = LBtcHistory {
|
||||||
txid: tx_id,
|
txid: tx_id,
|
||||||
height: height as i32,
|
height: height as i32,
|
||||||
};
|
};
|
||||||
@@ -400,7 +397,7 @@ mod test {
|
|||||||
let tx_id = elements::Txid::from_str(tx_id_hex).unwrap();
|
let tx_id = elements::Txid::from_str(tx_id_hex).unwrap();
|
||||||
|
|
||||||
// Create history tx
|
// Create history tx
|
||||||
let history_tx = HistoryTxId {
|
let history_tx = LBtcHistory {
|
||||||
txid: tx_id,
|
txid: tx_id,
|
||||||
height: height as i32,
|
height: height as i32,
|
||||||
};
|
};
|
||||||
@@ -436,7 +433,7 @@ mod test {
|
|||||||
let mut history = Vec::new();
|
let mut history = Vec::new();
|
||||||
for (tx_id_hex, height) in tx_ids {
|
for (tx_id_hex, height) in tx_ids {
|
||||||
let tx_id = bitcoin::Txid::from_str(tx_id_hex).unwrap();
|
let tx_id = bitcoin::Txid::from_str(tx_id_hex).unwrap();
|
||||||
history.push(HistoryTxId {
|
history.push(BtcHistory {
|
||||||
txid: tx_id.to_string().parse().unwrap(),
|
txid: tx_id.to_string().parse().unwrap(),
|
||||||
height: *height as i32,
|
height: *height as i32,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ mod test {
|
|||||||
use crate::{
|
use crate::{
|
||||||
model::PaymentState,
|
model::PaymentState,
|
||||||
recover::handlers::{
|
recover::handlers::{
|
||||||
handle_receive_swap::RecoveredOnchainDataReceive, tests::test::create_history_txid,
|
handle_receive_swap::RecoveredOnchainDataReceive, tests::create_lbtc_history_txid,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,8 +14,8 @@ mod test {
|
|||||||
fn test_derive_partial_state_with_lockup_and_claim() {
|
fn test_derive_partial_state_with_lockup_and_claim() {
|
||||||
// Test with confirmed claim
|
// Test with confirmed claim
|
||||||
let recovered_data = RecoveredOnchainDataReceive {
|
let recovered_data = RecoveredOnchainDataReceive {
|
||||||
lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
claim_tx_id: Some(create_history_txid("2222", 101)), // Confirmed claim
|
claim_tx_id: Some(create_lbtc_history_txid("2222", 101)), // Confirmed claim
|
||||||
mrh_tx_id: None,
|
mrh_tx_id: None,
|
||||||
mrh_amount_sat: None,
|
mrh_amount_sat: None,
|
||||||
};
|
};
|
||||||
@@ -32,8 +32,8 @@ mod test {
|
|||||||
|
|
||||||
// Test with unconfirmed claim
|
// Test with unconfirmed claim
|
||||||
let recovered_data = RecoveredOnchainDataReceive {
|
let recovered_data = RecoveredOnchainDataReceive {
|
||||||
lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
claim_tx_id: Some(create_history_txid("2222", 0)), // Unconfirmed claim
|
claim_tx_id: Some(create_lbtc_history_txid("2222", 0)), // Unconfirmed claim
|
||||||
mrh_tx_id: None,
|
mrh_tx_id: None,
|
||||||
mrh_amount_sat: None,
|
mrh_amount_sat: None,
|
||||||
};
|
};
|
||||||
@@ -52,7 +52,7 @@ mod test {
|
|||||||
#[sdk_macros::test_all]
|
#[sdk_macros::test_all]
|
||||||
fn test_derive_partial_state_with_lockup_only() {
|
fn test_derive_partial_state_with_lockup_only() {
|
||||||
let recovered_data = RecoveredOnchainDataReceive {
|
let recovered_data = RecoveredOnchainDataReceive {
|
||||||
lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
claim_tx_id: None,
|
claim_tx_id: None,
|
||||||
mrh_tx_id: None,
|
mrh_tx_id: None,
|
||||||
mrh_amount_sat: None,
|
mrh_amount_sat: None,
|
||||||
@@ -77,7 +77,7 @@ mod test {
|
|||||||
let recovered_data = RecoveredOnchainDataReceive {
|
let recovered_data = RecoveredOnchainDataReceive {
|
||||||
lockup_tx_id: None,
|
lockup_tx_id: None,
|
||||||
claim_tx_id: None,
|
claim_tx_id: None,
|
||||||
mrh_tx_id: Some(create_history_txid("3333", 103)),
|
mrh_tx_id: Some(create_lbtc_history_txid("3333", 103)),
|
||||||
mrh_amount_sat: Some(100000),
|
mrh_amount_sat: Some(100000),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ mod test {
|
|||||||
let recovered_data = RecoveredOnchainDataReceive {
|
let recovered_data = RecoveredOnchainDataReceive {
|
||||||
lockup_tx_id: None,
|
lockup_tx_id: None,
|
||||||
claim_tx_id: None,
|
claim_tx_id: None,
|
||||||
mrh_tx_id: Some(create_history_txid("3333", 0)), // Unconfirmed MRH tx
|
mrh_tx_id: Some(create_lbtc_history_txid("3333", 0)), // Unconfirmed MRH tx
|
||||||
mrh_amount_sat: Some(100000),
|
mrh_amount_sat: Some(100000),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,16 @@
|
|||||||
mod test {
|
mod test {
|
||||||
use crate::{
|
use crate::{
|
||||||
chain::liquid::MockLiquidChainService,
|
chain::liquid::MockLiquidChainService,
|
||||||
model::{PaymentState, ReceiveSwap, SwapMetadata},
|
elements,
|
||||||
|
model::{LBtcHistory, PaymentState, ReceiveSwap, SwapMetadata},
|
||||||
recover::{
|
recover::{
|
||||||
handlers::{tests::test::create_mock_lbtc_wallet_tx, ReceiveSwapHandler},
|
handlers::{tests::create_mock_lbtc_wallet_tx, ReceiveSwapHandler},
|
||||||
model::{HistoryTxId, RecoveryContext, TxMap},
|
model::{RecoveryContext, TxMap},
|
||||||
},
|
},
|
||||||
swapper::MockSwapper,
|
swapper::MockSwapper,
|
||||||
};
|
};
|
||||||
use boltz_client::ElementsAddress;
|
use elements::{Address as ElementsAddress, Script, Txid};
|
||||||
use lwk_wollet::elements::{Script, Txid};
|
use lwk_wollet::{elements_miniscript::slip77::MasterBlindingKey, WalletTx};
|
||||||
use lwk_wollet::elements_miniscript::slip77::MasterBlindingKey;
|
|
||||||
use lwk_wollet::WalletTx;
|
|
||||||
use std::{collections::HashMap, str::FromStr, sync::Arc};
|
use std::{collections::HashMap, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||||
@@ -324,7 +323,7 @@ mod test {
|
|||||||
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
||||||
|
|
||||||
// Create history tx
|
// Create history tx
|
||||||
let history_tx = HistoryTxId {
|
let history_tx = LBtcHistory {
|
||||||
txid: tx_id,
|
txid: tx_id,
|
||||||
height: height as i32,
|
height: height as i32,
|
||||||
};
|
};
|
||||||
@@ -361,7 +360,7 @@ mod test {
|
|||||||
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
||||||
|
|
||||||
// Create history tx
|
// Create history tx
|
||||||
let history_tx = HistoryTxId {
|
let history_tx = LBtcHistory {
|
||||||
txid: tx_id,
|
txid: tx_id,
|
||||||
height: height as i32,
|
height: height as i32,
|
||||||
};
|
};
|
||||||
@@ -391,7 +390,7 @@ mod test {
|
|||||||
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
||||||
|
|
||||||
// Create history tx
|
// Create history tx
|
||||||
let history_tx = HistoryTxId {
|
let history_tx = LBtcHistory {
|
||||||
txid: tx_id,
|
txid: tx_id,
|
||||||
height: height as i32,
|
height: height as i32,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ mod test {
|
|||||||
use crate::{
|
use crate::{
|
||||||
model::PaymentState,
|
model::PaymentState,
|
||||||
recover::handlers::{
|
recover::handlers::{
|
||||||
handle_send_swap::RecoveredOnchainDataSend, tests::test::create_history_txid,
|
handle_send_swap::RecoveredOnchainDataSend, tests::create_lbtc_history_txid,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -13,8 +13,8 @@ mod test {
|
|||||||
#[sdk_macros::test_all]
|
#[sdk_macros::test_all]
|
||||||
fn test_derive_partial_state_with_lockup_and_claim() {
|
fn test_derive_partial_state_with_lockup_and_claim() {
|
||||||
let recovered_data = RecoveredOnchainDataSend {
|
let recovered_data = RecoveredOnchainDataSend {
|
||||||
lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
claim_tx_id: Some(create_history_txid("2222", 101)),
|
claim_tx_id: Some(create_lbtc_history_txid("2222", 101)),
|
||||||
refund_tx_id: None,
|
refund_tx_id: None,
|
||||||
preimage: None,
|
preimage: None,
|
||||||
};
|
};
|
||||||
@@ -34,9 +34,9 @@ mod test {
|
|||||||
fn test_derive_partial_state_with_lockup_and_refund() {
|
fn test_derive_partial_state_with_lockup_and_refund() {
|
||||||
// Test with confirmed refund
|
// Test with confirmed refund
|
||||||
let recovered_data = RecoveredOnchainDataSend {
|
let recovered_data = RecoveredOnchainDataSend {
|
||||||
lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
claim_tx_id: None,
|
claim_tx_id: None,
|
||||||
refund_tx_id: Some(create_history_txid("3333", 102)),
|
refund_tx_id: Some(create_lbtc_history_txid("3333", 102)),
|
||||||
preimage: None,
|
preimage: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,9 +52,9 @@ mod test {
|
|||||||
|
|
||||||
// Test with unconfirmed refund
|
// Test with unconfirmed refund
|
||||||
let recovered_data = RecoveredOnchainDataSend {
|
let recovered_data = RecoveredOnchainDataSend {
|
||||||
lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
claim_tx_id: None,
|
claim_tx_id: None,
|
||||||
refund_tx_id: Some(create_history_txid("3333", 0)), // Unconfirmed tx
|
refund_tx_id: Some(create_lbtc_history_txid("3333", 0)), // Unconfirmed tx
|
||||||
preimage: None,
|
preimage: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ mod test {
|
|||||||
#[sdk_macros::test_all]
|
#[sdk_macros::test_all]
|
||||||
fn test_derive_partial_state_with_lockup_only() {
|
fn test_derive_partial_state_with_lockup_only() {
|
||||||
let recovered_data = RecoveredOnchainDataSend {
|
let recovered_data = RecoveredOnchainDataSend {
|
||||||
lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
claim_tx_id: None,
|
claim_tx_id: None,
|
||||||
refund_tx_id: None,
|
refund_tx_id: None,
|
||||||
preimage: None,
|
preimage: None,
|
||||||
@@ -114,9 +114,9 @@ mod test {
|
|||||||
fn test_derive_partial_state_with_lockup_claim_refund() {
|
fn test_derive_partial_state_with_lockup_claim_refund() {
|
||||||
// This is an edge case where both claim and refund txs exist
|
// This is an edge case where both claim and refund txs exist
|
||||||
let recovered_data = RecoveredOnchainDataSend {
|
let recovered_data = RecoveredOnchainDataSend {
|
||||||
lockup_tx_id: Some(create_history_txid("1111", 100)),
|
lockup_tx_id: Some(create_lbtc_history_txid("1111", 100)),
|
||||||
claim_tx_id: Some(create_history_txid("2222", 101)),
|
claim_tx_id: Some(create_lbtc_history_txid("2222", 101)),
|
||||||
refund_tx_id: Some(create_history_txid("3333", 102)),
|
refund_tx_id: Some(create_lbtc_history_txid("3333", 102)),
|
||||||
preimage: None,
|
preimage: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
mod test {
|
mod test {
|
||||||
use crate::chain::liquid::MockLiquidChainService;
|
use crate::chain::liquid::MockLiquidChainService;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::recover::handlers::tests::test::{
|
use crate::recover::handlers::tests::{
|
||||||
create_empty_lbtc_transaction, create_mock_lbtc_wallet_tx,
|
create_empty_lbtc_transaction, create_mock_lbtc_wallet_tx,
|
||||||
};
|
};
|
||||||
use crate::recover::handlers::SendSwapHandler;
|
use crate::recover::handlers::SendSwapHandler;
|
||||||
@@ -345,7 +345,7 @@ mod test {
|
|||||||
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
||||||
|
|
||||||
// Create history tx
|
// Create history tx
|
||||||
let history_tx = HistoryTxId {
|
let history_tx = LBtcHistory {
|
||||||
txid: tx_id,
|
txid: tx_id,
|
||||||
height: height as i32,
|
height: height as i32,
|
||||||
};
|
};
|
||||||
@@ -380,7 +380,7 @@ mod test {
|
|||||||
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
||||||
|
|
||||||
// Create history tx
|
// Create history tx
|
||||||
let history_tx = HistoryTxId {
|
let history_tx = LBtcHistory {
|
||||||
txid: tx_id,
|
txid: tx_id,
|
||||||
height: height as i32,
|
height: height as i32,
|
||||||
};
|
};
|
||||||
@@ -410,7 +410,7 @@ mod test {
|
|||||||
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
||||||
|
|
||||||
// Create history tx
|
// Create history tx
|
||||||
let history_tx = HistoryTxId {
|
let history_tx = LBtcHistory {
|
||||||
txid: tx_id,
|
txid: tx_id,
|
||||||
height: height as i32,
|
height: height as i32,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,70 +9,73 @@ pub mod handle_receive_swap_tests_integration;
|
|||||||
pub mod handle_send_swap_tests;
|
pub mod handle_send_swap_tests;
|
||||||
pub mod handle_send_swap_tests_integration;
|
pub mod handle_send_swap_tests_integration;
|
||||||
|
|
||||||
// Helper function to create a HistoryTxId for testing
|
// Helper function to create a History txid for testing
|
||||||
mod test {
|
use std::{collections::BTreeMap, str::FromStr};
|
||||||
use std::{collections::BTreeMap, str::FromStr};
|
|
||||||
|
|
||||||
use crate::recover::model::HistoryTxId;
|
use crate::model::{BtcHistory, LBtcHistory};
|
||||||
use lwk_wollet::{
|
use crate::{bitcoin, elements};
|
||||||
elements::{self, AssetId, Transaction, TxIn, TxInWitness, Txid},
|
use elements::{AssetId, Transaction, TxIn, TxInWitness, Txid};
|
||||||
hashes::Hash,
|
use lwk_wollet::{hashes::Hash, WalletTx};
|
||||||
WalletTx,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) fn create_history_txid(hex_id: &str, height: i32) -> HistoryTxId {
|
pub(crate) fn create_lbtc_history_txid(hex_id: &str, height: i32) -> LBtcHistory {
|
||||||
let txid_bytes = hex::decode(format!("{:0>64}", hex_id)).unwrap();
|
let txid_bytes = hex::decode(format!("{:0>64}", hex_id)).unwrap();
|
||||||
let mut txid_array = [0u8; 32];
|
let mut txid_array = [0u8; 32];
|
||||||
txid_array.copy_from_slice(&txid_bytes);
|
txid_array.copy_from_slice(&txid_bytes);
|
||||||
|
|
||||||
HistoryTxId {
|
LBtcHistory {
|
||||||
txid: Txid::from_slice(&txid_array).unwrap(),
|
txid: elements::Txid::from_slice(&txid_array).unwrap(),
|
||||||
height,
|
height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an empty LBTC transaction
|
pub(crate) fn create_btc_history_txid(hex_id: &str, height: i32) -> BtcHistory {
|
||||||
pub(crate) fn create_empty_lbtc_transaction() -> Transaction {
|
let txid_bytes = hex::decode(format!("{:0>64}", hex_id)).unwrap();
|
||||||
Transaction {
|
let mut txid_array = [0u8; 32];
|
||||||
version: 2,
|
txid_array.copy_from_slice(&txid_bytes);
|
||||||
lock_time: elements::LockTime::from_height(0).unwrap(),
|
|
||||||
input: vec![TxIn {
|
BtcHistory {
|
||||||
previous_output: Default::default(),
|
txid: bitcoin::Txid::from_slice(&txid_array).unwrap(),
|
||||||
is_pegin: false,
|
height,
|
||||||
script_sig: elements::Script::new(),
|
}
|
||||||
sequence: elements::Sequence::default(),
|
}
|
||||||
asset_issuance: Default::default(),
|
|
||||||
witness: TxInWitness::empty(),
|
// Create an empty LBTC transaction
|
||||||
}],
|
pub(crate) fn create_empty_lbtc_transaction() -> Transaction {
|
||||||
output: vec![],
|
Transaction {
|
||||||
}
|
version: 2,
|
||||||
}
|
lock_time: elements::LockTime::from_height(0).unwrap(),
|
||||||
|
input: vec![TxIn {
|
||||||
// Create a mock LBTC wallet transaction
|
previous_output: Default::default(),
|
||||||
pub(crate) fn create_mock_lbtc_wallet_tx(
|
is_pegin: false,
|
||||||
tx_id_hex: &str,
|
script_sig: elements::Script::new(),
|
||||||
height: u32,
|
sequence: elements::Sequence::default(),
|
||||||
amount: i64,
|
asset_issuance: Default::default(),
|
||||||
) -> WalletTx {
|
witness: TxInWitness::empty(),
|
||||||
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
}],
|
||||||
|
output: vec![],
|
||||||
WalletTx {
|
}
|
||||||
txid: tx_id,
|
}
|
||||||
tx: create_empty_lbtc_transaction(),
|
|
||||||
height: Some(height),
|
// Create a mock LBTC wallet transaction
|
||||||
fee: 1000,
|
pub(crate) fn create_mock_lbtc_wallet_tx(tx_id_hex: &str, height: u32, amount: i64) -> WalletTx {
|
||||||
timestamp: Some(1001), // Just after swap creation time
|
let tx_id = Txid::from_str(tx_id_hex).unwrap();
|
||||||
balance: {
|
|
||||||
let mut map = BTreeMap::new();
|
WalletTx {
|
||||||
map.insert(
|
txid: tx_id,
|
||||||
AssetId::from_slice(&[0; 32]).unwrap(), // Default asset ID
|
tx: create_empty_lbtc_transaction(),
|
||||||
amount,
|
height: Some(height),
|
||||||
);
|
fee: 1000,
|
||||||
map
|
timestamp: Some(1001), // Just after swap creation time
|
||||||
},
|
balance: {
|
||||||
outputs: vec![],
|
let mut map = BTreeMap::new();
|
||||||
inputs: Vec::new(),
|
map.insert(
|
||||||
type_: "".to_string(),
|
AssetId::from_slice(&[0; 32]).unwrap(), // Default asset ID
|
||||||
}
|
amount,
|
||||||
|
);
|
||||||
|
map
|
||||||
|
},
|
||||||
|
outputs: vec![],
|
||||||
|
inputs: Vec::new(),
|
||||||
|
type_: "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,59 +2,26 @@ use std::collections::HashMap;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use boltz_client::ElementsAddress;
|
|
||||||
use electrum_client::GetBalanceRes;
|
|
||||||
use lwk_wollet::elements::Txid;
|
|
||||||
use lwk_wollet::elements_miniscript::slip77::MasterBlindingKey;
|
use lwk_wollet::elements_miniscript::slip77::MasterBlindingKey;
|
||||||
use lwk_wollet::History;
|
|
||||||
use lwk_wollet::WalletTx;
|
use lwk_wollet::WalletTx;
|
||||||
|
|
||||||
use crate::chain::liquid::LiquidChainService;
|
use crate::chain::liquid::LiquidChainService;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::swapper::Swapper;
|
use crate::swapper::Swapper;
|
||||||
|
|
||||||
pub(crate) type BtcScript = lwk_wollet::bitcoin::ScriptBuf;
|
|
||||||
pub(crate) type LBtcScript = lwk_wollet::elements::Script;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub(crate) struct HistoryTxId {
|
|
||||||
pub txid: Txid,
|
|
||||||
/// Confirmation height of txid
|
|
||||||
///
|
|
||||||
/// -1 means unconfirmed with unconfirmed parents
|
|
||||||
/// 0 means unconfirmed with confirmed parents
|
|
||||||
pub height: i32,
|
|
||||||
}
|
|
||||||
impl HistoryTxId {
|
|
||||||
pub(crate) fn confirmed(&self) -> bool {
|
|
||||||
self.height > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<History> for HistoryTxId {
|
|
||||||
fn from(value: History) -> Self {
|
|
||||||
Self::from(&value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<&History> for HistoryTxId {
|
|
||||||
fn from(value: &History) -> Self {
|
|
||||||
Self {
|
|
||||||
txid: value.txid,
|
|
||||||
height: value.height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A map of all our known LWK onchain txs, indexed by tx ID. Essentially our own cache of the LWK txs.
|
/// A map of all our known LWK onchain txs, indexed by tx ID. Essentially our own cache of the LWK txs.
|
||||||
pub(crate) struct TxMap {
|
pub(crate) struct TxMap {
|
||||||
pub(crate) outgoing_tx_map: HashMap<Txid, WalletTx>,
|
pub(crate) outgoing_tx_map: HashMap<elements::Txid, WalletTx>,
|
||||||
pub(crate) incoming_tx_map: HashMap<Txid, WalletTx>,
|
pub(crate) incoming_tx_map: HashMap<elements::Txid, WalletTx>,
|
||||||
}
|
}
|
||||||
impl TxMap {
|
impl TxMap {
|
||||||
pub(crate) fn from_raw_tx_map(raw_tx_map: HashMap<Txid, WalletTx>) -> Self {
|
pub(crate) fn from_raw_tx_map(raw_tx_map: HashMap<elements::Txid, WalletTx>) -> Self {
|
||||||
let (outgoing_tx_map, incoming_tx_map): (HashMap<Txid, WalletTx>, HashMap<Txid, WalletTx>) =
|
let (outgoing_tx_map, incoming_tx_map): (
|
||||||
raw_tx_map
|
HashMap<elements::Txid, WalletTx>,
|
||||||
.into_iter()
|
HashMap<elements::Txid, WalletTx>,
|
||||||
.partition(|(_, tx)| tx.balance.values().sum::<i64>() < 0);
|
) = raw_tx_map
|
||||||
|
.into_iter()
|
||||||
|
.partition(|(_, tx)| tx.balance.values().sum::<i64>() < 0);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
outgoing_tx_map,
|
outgoing_tx_map,
|
||||||
@@ -107,7 +74,8 @@ impl SwapsList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add MRH script if available
|
// Add MRH script if available
|
||||||
if let Ok(mrh_address) = ElementsAddress::from_str(&receive_swap.mrh_address) {
|
if let Ok(mrh_address) = elements::Address::from_str(&receive_swap.mrh_address)
|
||||||
|
{
|
||||||
swap_scripts.push(mrh_address.script_pubkey());
|
swap_scripts.push(mrh_address.script_pubkey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,10 +142,10 @@ impl SwapsList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct RecoveryContext {
|
pub(crate) struct RecoveryContext {
|
||||||
pub(crate) lbtc_script_to_history_map: HashMap<LBtcScript, Vec<HistoryTxId>>,
|
pub(crate) lbtc_script_to_history_map: HashMap<LBtcScript, Vec<LBtcHistory>>,
|
||||||
pub(crate) btc_script_to_history_map: HashMap<BtcScript, Vec<HistoryTxId>>,
|
pub(crate) btc_script_to_history_map: HashMap<BtcScript, Vec<BtcHistory>>,
|
||||||
pub(crate) btc_script_to_txs_map: HashMap<BtcScript, Vec<boltz_client::bitcoin::Transaction>>,
|
pub(crate) btc_script_to_txs_map: HashMap<BtcScript, Vec<bitcoin::Transaction>>,
|
||||||
pub(crate) btc_script_to_balance_map: HashMap<BtcScript, GetBalanceRes>,
|
pub(crate) btc_script_to_balance_map: HashMap<BtcScript, BtcScriptBalance>,
|
||||||
pub(crate) liquid_chain_service: Arc<dyn LiquidChainService>,
|
pub(crate) liquid_chain_service: Arc<dyn LiquidChainService>,
|
||||||
pub(crate) swapper: Arc<dyn Swapper>,
|
pub(crate) swapper: Arc<dyn Swapper>,
|
||||||
pub(crate) tx_map: TxMap,
|
pub(crate) tx_map: TxMap,
|
||||||
|
|||||||
@@ -1,27 +1,29 @@
|
|||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use anyhow::{anyhow, ensure, Result};
|
use anyhow::{anyhow, ensure, Result};
|
||||||
use electrum_client::GetBalanceRes;
|
|
||||||
use log::{debug, info, warn};
|
use log::{debug, info, warn};
|
||||||
use lwk_wollet::elements::Txid;
|
|
||||||
use lwk_wollet::elements_miniscript::slip77::MasterBlindingKey;
|
|
||||||
use lwk_wollet::hashes::hex::{DisplayHex, FromHex};
|
|
||||||
use lwk_wollet::WalletTx;
|
|
||||||
|
|
||||||
use super::handlers::{
|
use super::handlers::{
|
||||||
ChainReceiveSwapHandler, ChainSendSwapHandler, ReceiveSwapHandler, SendSwapHandler,
|
ChainReceiveSwapHandler, ChainSendSwapHandler, ReceiveSwapHandler, SendSwapHandler,
|
||||||
};
|
};
|
||||||
use super::model::*;
|
use super::model::*;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use elements::Txid;
|
||||||
|
use lwk_wollet::{
|
||||||
|
elements_miniscript::slip77::MasterBlindingKey,
|
||||||
|
hashes::hex::{DisplayHex, FromHex},
|
||||||
|
WalletTx,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::model::Direction;
|
|
||||||
use crate::persist::Persister;
|
|
||||||
use crate::prelude::Swap;
|
|
||||||
use crate::sdk::NETWORK_PROPAGATION_GRACE_PERIOD;
|
use crate::sdk::NETWORK_PROPAGATION_GRACE_PERIOD;
|
||||||
use crate::swapper::Swapper;
|
use crate::swapper::Swapper;
|
||||||
use crate::wallet::OnchainWallet;
|
use crate::wallet::OnchainWallet;
|
||||||
use crate::{
|
use crate::{
|
||||||
chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService},
|
chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService},
|
||||||
recover::model::{BtcScript, HistoryTxId, LBtcScript},
|
model::{BtcScript, Direction, LBtcScript},
|
||||||
|
persist::Persister,
|
||||||
|
prelude::Swap,
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ impl Recoverer {
|
|||||||
let raw_tx_map = self.onchain_wallet.transactions_by_tx_id().await?;
|
let raw_tx_map = self.onchain_wallet.transactions_by_tx_id().await?;
|
||||||
|
|
||||||
// Fetch chain tips for expiration checks
|
// Fetch chain tips for expiration checks
|
||||||
let bitcoin_tip = self.bitcoin_chain_service.tip()?;
|
let bitcoin_tip = self.bitcoin_chain_service.tip().await?;
|
||||||
let liquid_tip = self.liquid_chain_service.tip().await?;
|
let liquid_tip = self.liquid_chain_service.tip().await?;
|
||||||
|
|
||||||
// Convert swaps to SwapsList and fetch history data
|
// Convert swaps to SwapsList and fetch history data
|
||||||
@@ -91,7 +93,7 @@ impl Recoverer {
|
|||||||
&swaps_list,
|
&swaps_list,
|
||||||
TxMap::from_raw_tx_map(raw_tx_map.clone()),
|
TxMap::from_raw_tx_map(raw_tx_map.clone()),
|
||||||
liquid_tip,
|
liquid_tip,
|
||||||
bitcoin_tip.height as u32,
|
bitcoin_tip,
|
||||||
self.master_blinding_key,
|
self.master_blinding_key,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -221,11 +223,11 @@ impl Recoverer {
|
|||||||
async fn fetch_lbtc_history_map(
|
async fn fetch_lbtc_history_map(
|
||||||
&self,
|
&self,
|
||||||
swap_lbtc_scripts: Vec<LBtcScript>,
|
swap_lbtc_scripts: Vec<LBtcScript>,
|
||||||
) -> Result<HashMap<LBtcScript, Vec<HistoryTxId>>> {
|
) -> Result<HashMap<LBtcScript, Vec<LBtcHistory>>> {
|
||||||
let t0 = web_time::Instant::now();
|
let t0 = web_time::Instant::now();
|
||||||
let lbtc_script_histories = self
|
let lbtc_script_histories = self
|
||||||
.liquid_chain_service
|
.liquid_chain_service
|
||||||
.get_scripts_history(&swap_lbtc_scripts.to_vec())
|
.get_scripts_history(&swap_lbtc_scripts)
|
||||||
.await?;
|
.await?;
|
||||||
info!(
|
info!(
|
||||||
"Recoverer executed liquid get_scripts_history for {} scripts in {} milliseconds",
|
"Recoverer executed liquid get_scripts_history for {} scripts in {} milliseconds",
|
||||||
@@ -236,13 +238,12 @@ impl Recoverer {
|
|||||||
let lbtc_swap_scripts_len = swap_lbtc_scripts.len();
|
let lbtc_swap_scripts_len = swap_lbtc_scripts.len();
|
||||||
let lbtc_script_histories_len = lbtc_script_histories.len();
|
let lbtc_script_histories_len = lbtc_script_histories.len();
|
||||||
ensure!(
|
ensure!(
|
||||||
lbtc_swap_scripts_len == lbtc_script_histories_len,
|
lbtc_swap_scripts_len == lbtc_script_histories_len,
|
||||||
anyhow!("Got {lbtc_script_histories_len} L-BTC script histories, expected {lbtc_swap_scripts_len}")
|
anyhow!("Got {lbtc_script_histories_len} L-BTC script histories, expected {lbtc_swap_scripts_len}")
|
||||||
);
|
);
|
||||||
let lbtc_script_to_history_map: HashMap<LBtcScript, Vec<HistoryTxId>> = swap_lbtc_scripts
|
let lbtc_script_to_history_map: HashMap<LBtcScript, Vec<LBtcHistory>> = swap_lbtc_scripts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(lbtc_script_histories.into_iter())
|
.zip(lbtc_script_histories.into_iter())
|
||||||
.map(|(k, v)| (k, v.into_iter().map(HistoryTxId::from).collect()))
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(lbtc_script_to_history_map)
|
Ok(lbtc_script_to_history_map)
|
||||||
@@ -252,9 +253,9 @@ impl Recoverer {
|
|||||||
&self,
|
&self,
|
||||||
swap_btc_script_bufs: Vec<BtcScript>,
|
swap_btc_script_bufs: Vec<BtcScript>,
|
||||||
) -> Result<(
|
) -> Result<(
|
||||||
HashMap<BtcScript, Vec<HistoryTxId>>,
|
HashMap<BtcScript, Vec<BtcHistory>>,
|
||||||
HashMap<BtcScript, Vec<boltz_client::bitcoin::Transaction>>,
|
HashMap<BtcScript, Vec<bitcoin::Transaction>>,
|
||||||
HashMap<BtcScript, GetBalanceRes>,
|
HashMap<BtcScript, BtcScriptBalance>,
|
||||||
)> {
|
)> {
|
||||||
let swap_btc_scripts = swap_btc_script_bufs
|
let swap_btc_scripts = swap_btc_script_bufs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -264,7 +265,8 @@ impl Recoverer {
|
|||||||
let t0 = web_time::Instant::now();
|
let t0 = web_time::Instant::now();
|
||||||
let btc_script_histories = self
|
let btc_script_histories = self
|
||||||
.bitcoin_chain_service
|
.bitcoin_chain_service
|
||||||
.get_scripts_history(&swap_btc_scripts)?;
|
.get_scripts_history(&swap_btc_scripts)
|
||||||
|
.await?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Recoverer executed bitcoin get_scripts_history for {} scripts in {} milliseconds",
|
"Recoverer executed bitcoin get_scripts_history for {} scripts in {} milliseconds",
|
||||||
@@ -285,17 +287,17 @@ impl Recoverer {
|
|||||||
btc_swap_scripts_len == btc_script_histories_len,
|
btc_swap_scripts_len == btc_script_histories_len,
|
||||||
anyhow!("Got {btc_script_histories_len} BTC script histories, expected {btc_swap_scripts_len}")
|
anyhow!("Got {btc_script_histories_len} BTC script histories, expected {btc_swap_scripts_len}")
|
||||||
);
|
);
|
||||||
let btc_script_to_history_map: HashMap<BtcScript, Vec<HistoryTxId>> = swap_btc_script_bufs
|
let btc_script_to_history_map: HashMap<BtcScript, Vec<BtcHistory>> = swap_btc_script_bufs
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(btc_script_histories.iter())
|
.zip(btc_script_histories.clone())
|
||||||
.map(|(k, v)| (k, v.iter().map(HistoryTxId::from).collect()))
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let t0 = web_time::Instant::now();
|
let t0 = web_time::Instant::now();
|
||||||
let btc_script_txs = self
|
let btc_script_txs = self
|
||||||
.bitcoin_chain_service
|
.bitcoin_chain_service
|
||||||
.get_transactions(&btx_script_tx_ids)?;
|
.get_transactions(&btx_script_tx_ids)
|
||||||
|
.await?;
|
||||||
info!(
|
info!(
|
||||||
"Recoverer executed bitcoin get_transactions for {} transactions in {} milliseconds",
|
"Recoverer executed bitcoin get_transactions for {} transactions in {} milliseconds",
|
||||||
btx_script_tx_ids.len(),
|
btx_script_tx_ids.len(),
|
||||||
@@ -305,7 +307,8 @@ impl Recoverer {
|
|||||||
let t0 = web_time::Instant::now();
|
let t0 = web_time::Instant::now();
|
||||||
let btc_script_balances = self
|
let btc_script_balances = self
|
||||||
.bitcoin_chain_service
|
.bitcoin_chain_service
|
||||||
.scripts_get_balance(&swap_btc_scripts)?;
|
.scripts_get_balance(&swap_btc_scripts)
|
||||||
|
.await?;
|
||||||
info!(
|
info!(
|
||||||
"Recoverer executed bitcoin scripts_get_balance for {} scripts in {} milliseconds",
|
"Recoverer executed bitcoin scripts_get_balance for {} scripts in {} milliseconds",
|
||||||
swap_btc_scripts.len(),
|
swap_btc_scripts.len(),
|
||||||
@@ -318,8 +321,9 @@ impl Recoverer {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(btc_script_histories.iter())
|
.zip(btc_script_histories.iter())
|
||||||
.map(|(script, history)| {
|
.map(|(script, history)| {
|
||||||
let relevant_tx_ids: Vec<Txid> = history.iter().map(|h| h.txid).collect();
|
let relevant_tx_ids: Vec<bitcoin::Txid> =
|
||||||
let relevant_txs: Vec<boltz_client::bitcoin::Transaction> = btc_script_txs
|
history.iter().map(|h| h.txid).collect();
|
||||||
|
let relevant_txs: Vec<bitcoin::Transaction> = btc_script_txs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&tx| {
|
.filter(|&tx| {
|
||||||
relevant_tx_ids.contains(&tx.compute_txid().to_raw_hash().into())
|
relevant_tx_ids.contains(&tx.compute_txid().to_raw_hash().into())
|
||||||
@@ -331,7 +335,7 @@ impl Recoverer {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let btc_script_to_balance_map: HashMap<BtcScript, GetBalanceRes> = swap_btc_script_bufs
|
let btc_script_to_balance_map: HashMap<BtcScript, BtcScriptBalance> = swap_btc_script_bufs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(btc_script_balances)
|
.zip(btc_script_balances)
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ use std::{path::PathBuf, str::FromStr, sync::Arc, time::Duration};
|
|||||||
use anyhow::{anyhow, ensure, Result};
|
use anyhow::{anyhow, ensure, Result};
|
||||||
use boltz_client::{swaps::boltz::*, util::secrets::Preimage};
|
use boltz_client::{swaps::boltz::*, util::secrets::Preimage};
|
||||||
use buy::{BuyBitcoinApi, BuyBitcoinService};
|
use buy::{BuyBitcoinApi, BuyBitcoinService};
|
||||||
use chain::bitcoin::HybridBitcoinChainService;
|
use chain::{bitcoin::BitcoinChainService, liquid::LiquidChainService};
|
||||||
use chain::liquid::{HybridLiquidChainService, LiquidChainService};
|
|
||||||
use chain_swap::ESTIMATED_BTC_CLAIM_TX_VSIZE;
|
use chain_swap::ESTIMATED_BTC_CLAIM_TX_VSIZE;
|
||||||
use futures_util::stream::select_all;
|
use futures_util::stream::select_all;
|
||||||
use futures_util::{StreamExt, TryFutureExt};
|
use futures_util::{StreamExt, TryFutureExt};
|
||||||
@@ -31,7 +30,6 @@ use tokio_with_wasm::alias as tokio;
|
|||||||
use web_time::Instant;
|
use web_time::Instant;
|
||||||
use x509_parser::parse_x509_certificate;
|
use x509_parser::parse_x509_certificate;
|
||||||
|
|
||||||
use crate::chain::bitcoin::BitcoinChainService;
|
|
||||||
use crate::chain_swap::ChainSwapHandler;
|
use crate::chain_swap::ChainSwapHandler;
|
||||||
use crate::ensure_sdk;
|
use crate::ensure_sdk;
|
||||||
use crate::error::SdkError;
|
use crate::error::SdkError;
|
||||||
@@ -208,16 +206,13 @@ impl LiquidSdkBuilder {
|
|||||||
let bitcoin_chain_service: Arc<dyn BitcoinChainService> =
|
let bitcoin_chain_service: Arc<dyn BitcoinChainService> =
|
||||||
match self.bitcoin_chain_service.clone() {
|
match self.bitcoin_chain_service.clone() {
|
||||||
Some(bitcoin_chain_service) => bitcoin_chain_service,
|
Some(bitcoin_chain_service) => bitcoin_chain_service,
|
||||||
None => Arc::new(HybridBitcoinChainService::new(
|
None => self.config.bitcoin_chain_service(),
|
||||||
self.config.clone(),
|
|
||||||
rest_client.clone(),
|
|
||||||
)?),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let liquid_chain_service: Arc<dyn LiquidChainService> =
|
let liquid_chain_service: Arc<dyn LiquidChainService> =
|
||||||
match self.liquid_chain_service.clone() {
|
match self.liquid_chain_service.clone() {
|
||||||
Some(liquid_chain_service) => liquid_chain_service,
|
Some(liquid_chain_service) => liquid_chain_service,
|
||||||
None => Arc::new(HybridLiquidChainService::new(self.config.clone())?),
|
None => self.config.liquid_chain_service(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let onchain_wallet: Arc<dyn OnchainWallet> = match self.onchain_wallet.clone() {
|
let onchain_wallet: Arc<dyn OnchainWallet> = match self.onchain_wallet.clone() {
|
||||||
@@ -580,7 +575,7 @@ impl LiquidSdk {
|
|||||||
};
|
};
|
||||||
// Get the Bitcoin tip and process a new block
|
// Get the Bitcoin tip and process a new block
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let bitcoin_tip_res = cloned.bitcoin_chain_service.tip().map(|tip| tip.height as u32);
|
let bitcoin_tip_res = cloned.bitcoin_chain_service.tip().await;
|
||||||
let duration_ms = Instant::now().duration_since(t0).as_millis();
|
let duration_ms = Instant::now().duration_since(t0).as_millis();
|
||||||
info!("Fetched bitcoin tip at ({duration_ms} ms)");
|
info!("Fetched bitcoin tip at ({duration_ms} ms)");
|
||||||
let is_new_bitcoin_block = match &bitcoin_tip_res {
|
let is_new_bitcoin_block = match &bitcoin_tip_res {
|
||||||
@@ -2684,7 +2679,8 @@ impl LiquidSdk {
|
|||||||
.collect();
|
.collect();
|
||||||
let scripts_utxos = self
|
let scripts_utxos = self
|
||||||
.bitcoin_chain_service
|
.bitcoin_chain_service
|
||||||
.get_scripts_utxos(&lockup_scripts)?;
|
.get_scripts_utxos(&lockup_scripts)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut refundables = vec![];
|
let mut refundables = vec![];
|
||||||
for (chain_swap, script_utxos) in chain_swaps.into_iter().zip(scripts_utxos) {
|
for (chain_swap, script_utxos) in chain_swaps.into_iter().zip(scripts_utxos) {
|
||||||
@@ -2900,7 +2896,7 @@ impl LiquidSdk {
|
|||||||
.collect();
|
.collect();
|
||||||
match partial_sync {
|
match partial_sync {
|
||||||
false => {
|
false => {
|
||||||
let bitcoin_height = self.bitcoin_chain_service.tip()?.height as u32;
|
let bitcoin_height = self.bitcoin_chain_service.tip().await?;
|
||||||
let liquid_height = self.liquid_chain_service.tip().await?;
|
let liquid_height = self.liquid_chain_service.tip().await?;
|
||||||
let final_swap_states = [PaymentState::Complete, PaymentState::Failed];
|
let final_swap_states = [PaymentState::Complete, PaymentState::Failed];
|
||||||
|
|
||||||
@@ -3859,7 +3855,7 @@ mod tests {
|
|||||||
boltz::{self, TransactionInfo},
|
boltz::{self, TransactionInfo},
|
||||||
swaps::boltz::{ChainSwapStates, RevSwapStates, SubSwapStates},
|
swaps::boltz::{ChainSwapStates, RevSwapStates, SubSwapStates},
|
||||||
};
|
};
|
||||||
use lwk_wollet::{elements::Txid, hashes::hex::DisplayHex};
|
use lwk_wollet::hashes::hex::DisplayHex as _;
|
||||||
|
|
||||||
use crate::chain_swap::ESTIMATED_BTC_LOCKUP_TX_VSIZE;
|
use crate::chain_swap::ESTIMATED_BTC_LOCKUP_TX_VSIZE;
|
||||||
use crate::test_utils::chain_swap::{
|
use crate::test_utils::chain_swap::{
|
||||||
@@ -3869,10 +3865,11 @@ mod tests {
|
|||||||
use crate::test_utils::swapper::ZeroAmountSwapMockConfig;
|
use crate::test_utils::swapper::ZeroAmountSwapMockConfig;
|
||||||
use crate::test_utils::wallet::TEST_LIQUID_RECEIVE_LOCKUP_TX;
|
use crate::test_utils::wallet::TEST_LIQUID_RECEIVE_LOCKUP_TX;
|
||||||
use crate::{
|
use crate::{
|
||||||
model::{Direction, PaymentState, Swap},
|
bitcoin, elements,
|
||||||
|
model::{BtcHistory, Direction, LBtcHistory, PaymentState, Swap},
|
||||||
sdk::LiquidSdk,
|
sdk::LiquidSdk,
|
||||||
test_utils::{
|
test_utils::{
|
||||||
chain::{MockBitcoinChainService, MockHistory, MockLiquidChainService},
|
chain::{MockBitcoinChainService, MockLiquidChainService},
|
||||||
chain_swap::{new_chain_swap, TEST_BITCOIN_INCOMING_USER_LOCKUP_TX},
|
chain_swap::{new_chain_swap, TEST_BITCOIN_INCOMING_USER_LOCKUP_TX},
|
||||||
persist::{create_persister, new_receive_swap, new_send_swap},
|
persist::{create_persister, new_receive_swap, new_send_swap},
|
||||||
sdk::{new_liquid_sdk, new_liquid_sdk_with_chain_services},
|
sdk::{new_liquid_sdk, new_liquid_sdk_with_chain_services},
|
||||||
@@ -4059,11 +4056,9 @@ mod tests {
|
|||||||
let height = (serde_json::to_string(&status).unwrap()
|
let height = (serde_json::to_string(&status).unwrap()
|
||||||
== serde_json::to_string(&RevSwapStates::TransactionConfirmed).unwrap())
|
== serde_json::to_string(&RevSwapStates::TransactionConfirmed).unwrap())
|
||||||
as i32;
|
as i32;
|
||||||
liquid_chain_service.set_history(vec![MockHistory {
|
liquid_chain_service.set_history(vec![LBtcHistory {
|
||||||
txid: mock_tx_id,
|
txid: mock_tx_id,
|
||||||
height,
|
height,
|
||||||
block_hash: None,
|
|
||||||
block_timestamp: None,
|
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
let persisted_swap = trigger_swap_update!(
|
let persisted_swap = trigger_swap_update!(
|
||||||
@@ -4095,11 +4090,9 @@ mod tests {
|
|||||||
let height = (serde_json::to_string(&status).unwrap()
|
let height = (serde_json::to_string(&status).unwrap()
|
||||||
== serde_json::to_string(&RevSwapStates::TransactionConfirmed).unwrap())
|
== serde_json::to_string(&RevSwapStates::TransactionConfirmed).unwrap())
|
||||||
as i32;
|
as i32;
|
||||||
liquid_chain_service.set_history(vec![MockHistory {
|
liquid_chain_service.set_history(vec![LBtcHistory {
|
||||||
txid: mock_tx_id,
|
txid: mock_tx_id,
|
||||||
height,
|
height,
|
||||||
block_hash: None,
|
|
||||||
block_timestamp: None,
|
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
let persisted_swap = trigger_swap_update!(
|
let persisted_swap = trigger_swap_update!(
|
||||||
@@ -4266,19 +4259,15 @@ mod tests {
|
|||||||
if let Some(user_lockup_tx_id) = user_lockup_tx_id {
|
if let Some(user_lockup_tx_id) = user_lockup_tx_id {
|
||||||
match direction {
|
match direction {
|
||||||
Direction::Incoming => {
|
Direction::Incoming => {
|
||||||
bitcoin_chain_service.set_history(vec![MockHistory {
|
bitcoin_chain_service.set_history(vec![BtcHistory {
|
||||||
txid: Txid::from_str(user_lockup_tx_id).unwrap(),
|
txid: bitcoin::Txid::from_str(user_lockup_tx_id).unwrap(),
|
||||||
height: 0,
|
height: 0,
|
||||||
block_hash: None,
|
|
||||||
block_timestamp: None,
|
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
Direction::Outgoing => {
|
Direction::Outgoing => {
|
||||||
liquid_chain_service.set_history(vec![MockHistory {
|
liquid_chain_service.set_history(vec![LBtcHistory {
|
||||||
txid: Txid::from_str(user_lockup_tx_id).unwrap(),
|
txid: elements::Txid::from_str(user_lockup_tx_id).unwrap(),
|
||||||
height: 0,
|
height: 0,
|
||||||
block_hash: None,
|
|
||||||
block_timestamp: None,
|
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4313,11 +4302,9 @@ mod tests {
|
|||||||
ChainSwapStates::TransactionConfirmed,
|
ChainSwapStates::TransactionConfirmed,
|
||||||
] {
|
] {
|
||||||
if direction == Direction::Incoming {
|
if direction == Direction::Incoming {
|
||||||
bitcoin_chain_service.set_history(vec![MockHistory {
|
bitcoin_chain_service.set_history(vec![BtcHistory {
|
||||||
txid: Txid::from_str(&mock_user_lockup_tx_id).unwrap(),
|
txid: bitcoin::Txid::from_str(&mock_user_lockup_tx_id).unwrap(),
|
||||||
height: 0,
|
height: 0,
|
||||||
block_hash: None,
|
|
||||||
block_timestamp: None,
|
|
||||||
}]);
|
}]);
|
||||||
bitcoin_chain_service.set_transactions(&[&mock_user_lockup_tx_hex]);
|
bitcoin_chain_service.set_transactions(&[&mock_user_lockup_tx_hex]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,14 +59,14 @@ impl<P: ProxyUrlFetcher> BoltzSwapper<P> {
|
|||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
liquid_electrum_client: ElectrumLiquidClient::new(
|
liquid_electrum_client: ElectrumLiquidClient::new(
|
||||||
config.network.into(),
|
config.network.into(),
|
||||||
&config.liquid_electrum_url,
|
config.liquid_explorer.url(),
|
||||||
tls,
|
tls,
|
||||||
validate_domain,
|
validate_domain,
|
||||||
100,
|
100,
|
||||||
)?,
|
)?,
|
||||||
bitcoin_electrum_client: ElectrumBitcoinClient::new(
|
bitcoin_electrum_client: ElectrumBitcoinClient::new(
|
||||||
config.network.as_bitcoin_chain(),
|
config.network.as_bitcoin_chain(),
|
||||||
&config.bitcoin_electrum_url,
|
config.bitcoin_explorer.url(),
|
||||||
tls,
|
tls,
|
||||||
validate_domain,
|
validate_domain,
|
||||||
100,
|
100,
|
||||||
|
|||||||
@@ -2,23 +2,15 @@
|
|||||||
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bitcoin, elements,
|
||||||
|
model::{BtcHistory, BtcScriptBalance, LBtcHistory},
|
||||||
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use boltz_client::{
|
use bitcoin::{consensus::deserialize, OutPoint, Script, TxOut};
|
||||||
elements::{
|
use boltz_client::Amount;
|
||||||
hex::FromHex, OutPoint as ElementsOutPoint, Script as ElementsScript,
|
use elements::{
|
||||||
TxOut as ElementsTxOut,
|
hex::FromHex, OutPoint as ElementsOutPoint, Script as ElementsScript, TxOut as ElementsTxOut,
|
||||||
},
|
|
||||||
Amount,
|
|
||||||
};
|
|
||||||
use electrum_client::GetBalanceRes;
|
|
||||||
use electrum_client::{
|
|
||||||
bitcoin::{consensus::deserialize, OutPoint, Script, TxOut},
|
|
||||||
HeaderNotification,
|
|
||||||
};
|
|
||||||
use lwk_wollet::{
|
|
||||||
bitcoin::constants::genesis_block,
|
|
||||||
elements::{BlockHash, Txid as ElementsTxid},
|
|
||||||
History,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -27,28 +19,9 @@ use crate::{
|
|||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct MockHistory {
|
|
||||||
pub txid: ElementsTxid,
|
|
||||||
pub height: i32,
|
|
||||||
pub block_hash: Option<BlockHash>,
|
|
||||||
pub block_timestamp: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MockHistory> for lwk_wollet::History {
|
|
||||||
fn from(h: MockHistory) -> Self {
|
|
||||||
lwk_wollet::History {
|
|
||||||
txid: h.txid,
|
|
||||||
height: h.height,
|
|
||||||
block_hash: h.block_hash,
|
|
||||||
block_timestamp: h.block_timestamp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct MockLiquidChainService {
|
pub(crate) struct MockLiquidChainService {
|
||||||
history: Mutex<Vec<MockHistory>>,
|
history: Mutex<Vec<LBtcHistory>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockLiquidChainService {
|
impl MockLiquidChainService {
|
||||||
@@ -56,12 +29,12 @@ impl MockLiquidChainService {
|
|||||||
MockLiquidChainService::default()
|
MockLiquidChainService::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_history(&self, history: Vec<MockHistory>) -> &Self {
|
pub(crate) fn set_history(&self, history: Vec<LBtcHistory>) -> &Self {
|
||||||
*self.history.lock().unwrap() = history;
|
*self.history.lock().unwrap() = history;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_history(&self) -> Vec<MockHistory> {
|
pub(crate) fn get_history(&self) -> Vec<LBtcHistory> {
|
||||||
self.history.lock().unwrap().clone()
|
self.history.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,43 +45,40 @@ impl LiquidChainService for MockLiquidChainService {
|
|||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn broadcast(
|
async fn broadcast(&self, tx: &elements::Transaction) -> Result<elements::Txid> {
|
||||||
&self,
|
|
||||||
tx: &lwk_wollet::elements::Transaction,
|
|
||||||
) -> Result<lwk_wollet::elements::Txid> {
|
|
||||||
Ok(tx.txid())
|
Ok(tx.txid())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_transaction_hex(
|
async fn get_transaction_hex(
|
||||||
&self,
|
&self,
|
||||||
_txid: &lwk_wollet::elements::Txid,
|
_txid: &elements::Txid,
|
||||||
) -> Result<Option<lwk_wollet::elements::Transaction>> {
|
) -> Result<Option<elements::Transaction>> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_transactions(
|
async fn get_transactions(
|
||||||
&self,
|
&self,
|
||||||
_txids: &[lwk_wollet::elements::Txid],
|
_txids: &[elements::Txid],
|
||||||
) -> Result<Vec<lwk_wollet::elements::Transaction>> {
|
) -> Result<Vec<elements::Transaction>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_script_history(
|
|
||||||
&self,
|
|
||||||
_scripts: &ElementsScript,
|
|
||||||
) -> Result<Vec<lwk_wollet::History>> {
|
|
||||||
Ok(self.get_history().into_iter().map(Into::into).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_script_history_with_retry(
|
async fn get_script_history_with_retry(
|
||||||
&self,
|
&self,
|
||||||
_script: &ElementsScript,
|
_script: &ElementsScript,
|
||||||
_retries: u64,
|
_retries: u64,
|
||||||
) -> Result<Vec<lwk_wollet::History>> {
|
) -> Result<Vec<LBtcHistory>> {
|
||||||
Ok(self.get_history().into_iter().map(Into::into).collect())
|
Ok(self.get_history().into_iter().map(Into::into).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_scripts_history(&self, _scripts: &[ElementsScript]) -> Result<Vec<Vec<History>>> {
|
async fn get_script_history(&self, _script: &ElementsScript) -> Result<Vec<LBtcHistory>> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_scripts_history(
|
||||||
|
&self,
|
||||||
|
_scripts: &[ElementsScript],
|
||||||
|
) -> Result<Vec<Vec<LBtcHistory>>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,18 +91,18 @@ impl LiquidChainService for MockLiquidChainService {
|
|||||||
|
|
||||||
async fn verify_tx(
|
async fn verify_tx(
|
||||||
&self,
|
&self,
|
||||||
_address: &boltz_client::ElementsAddress,
|
_address: &elements::Address,
|
||||||
_tx_id: &str,
|
_tx_id: &str,
|
||||||
tx_hex: &str,
|
tx_hex: &str,
|
||||||
_verify_confirmation: bool,
|
_verify_confirmation: bool,
|
||||||
) -> Result<lwk_wollet::elements::Transaction> {
|
) -> Result<elements::Transaction> {
|
||||||
utils::deserialize_tx_hex(tx_hex)
|
utils::deserialize_tx_hex(tx_hex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct MockBitcoinChainService {
|
pub(crate) struct MockBitcoinChainService {
|
||||||
history: Mutex<Vec<MockHistory>>,
|
history: Mutex<Vec<BtcHistory>>,
|
||||||
txs: Mutex<Vec<boltz_client::bitcoin::Transaction>>,
|
txs: Mutex<Vec<bitcoin::Transaction>>,
|
||||||
script_balance_sat: Mutex<u64>,
|
script_balance_sat: Mutex<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +115,7 @@ impl MockBitcoinChainService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_history(&self, history: Vec<MockHistory>) -> &Self {
|
pub(crate) fn set_history(&self, history: Vec<BtcHistory>) -> &Self {
|
||||||
*self.history.lock().unwrap() = history;
|
*self.history.lock().unwrap() = history;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -166,43 +136,26 @@ impl MockBitcoinChainService {
|
|||||||
|
|
||||||
#[sdk_macros::async_trait]
|
#[sdk_macros::async_trait]
|
||||||
impl BitcoinChainService for MockBitcoinChainService {
|
impl BitcoinChainService for MockBitcoinChainService {
|
||||||
fn tip(&self) -> Result<HeaderNotification> {
|
async fn tip(&self) -> Result<u32> {
|
||||||
Ok(HeaderNotification {
|
Ok(0)
|
||||||
height: 0,
|
|
||||||
header: genesis_block(lwk_wollet::bitcoin::Network::Testnet).header,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn broadcast(
|
async fn broadcast(&self, tx: &bitcoin::Transaction) -> Result<bitcoin::Txid, anyhow::Error> {
|
||||||
&self,
|
|
||||||
tx: &boltz_client::bitcoin::Transaction,
|
|
||||||
) -> Result<boltz_client::bitcoin::Txid, anyhow::Error> {
|
|
||||||
Ok(tx.compute_txid())
|
Ok(tx.compute_txid())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_transactions(
|
async fn get_transactions(
|
||||||
&self,
|
&self,
|
||||||
_txids: &[boltz_client::bitcoin::Txid],
|
_txids: &[bitcoin::Txid],
|
||||||
) -> Result<Vec<boltz_client::bitcoin::Transaction>> {
|
) -> Result<Vec<bitcoin::Transaction>> {
|
||||||
Ok(self.txs.lock().unwrap().clone())
|
Ok(self.txs.lock().unwrap().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_script_history(&self, _script: &Script) -> Result<Vec<lwk_wollet::History>> {
|
|
||||||
Ok(self
|
|
||||||
.history
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_script_history_with_retry(
|
async fn get_script_history_with_retry(
|
||||||
&self,
|
&self,
|
||||||
_script: &Script,
|
_script: &Script,
|
||||||
_retries: u64,
|
_retries: u64,
|
||||||
) -> Result<Vec<lwk_wollet::History>> {
|
) -> Result<Vec<BtcHistory>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.history
|
.history
|
||||||
.lock()
|
.lock()
|
||||||
@@ -213,19 +166,24 @@ impl BitcoinChainService for MockBitcoinChainService {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_scripts_history(&self, _scripts: &[&Script]) -> Result<Vec<Vec<History>>> {
|
async fn get_script_history(&self, _scripts: &Script) -> Result<Vec<BtcHistory>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>> {
|
async fn get_scripts_history(&self, _scripts: &[&Script]) -> Result<Vec<Vec<BtcHistory>>> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script_utxos(&self, script: &Script) -> Result<Vec<Utxo>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.get_scripts_utxos(&[script])?
|
.get_scripts_utxos(&[script])
|
||||||
|
.await?
|
||||||
.first()
|
.first()
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default())
|
.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_scripts_utxos(&self, scripts: &[&Script]) -> Result<Vec<Vec<Utxo>>> {
|
async fn get_scripts_utxos(&self, scripts: &[&Script]) -> Result<Vec<Vec<Utxo>>> {
|
||||||
let scripts_utxos = scripts
|
let scripts_utxos = scripts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
@@ -241,17 +199,17 @@ impl BitcoinChainService for MockBitcoinChainService {
|
|||||||
Ok(scripts_utxos)
|
Ok(scripts_utxos)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn script_get_balance(
|
async fn script_get_balance(
|
||||||
&self,
|
&self,
|
||||||
_script: &boltz_client::bitcoin::Script,
|
_script: &boltz_client::bitcoin::Script,
|
||||||
) -> Result<electrum_client::GetBalanceRes> {
|
) -> Result<BtcScriptBalance> {
|
||||||
Ok(GetBalanceRes {
|
Ok(BtcScriptBalance {
|
||||||
confirmed: 0,
|
confirmed: 0,
|
||||||
unconfirmed: 0,
|
unconfirmed: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scripts_get_balance(&self, _scripts: &[&Script]) -> Result<Vec<GetBalanceRes>> {
|
async fn scripts_get_balance(&self, _scripts: &[&Script]) -> Result<Vec<BtcScriptBalance>> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,8 +217,8 @@ impl BitcoinChainService for MockBitcoinChainService {
|
|||||||
&self,
|
&self,
|
||||||
_script: &boltz_client::bitcoin::Script,
|
_script: &boltz_client::bitcoin::Script,
|
||||||
_retries: u64,
|
_retries: u64,
|
||||||
) -> Result<electrum_client::GetBalanceRes> {
|
) -> Result<BtcScriptBalance> {
|
||||||
Ok(GetBalanceRes {
|
Ok(BtcScriptBalance {
|
||||||
confirmed: *self.script_balance_sat.lock().unwrap(),
|
confirmed: *self.script_balance_sat.lock().unwrap(),
|
||||||
unconfirmed: 0,
|
unconfirmed: 0,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ impl OnchainWallet for LiquidOnchainWallet {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let electrum_url =
|
let electrum_url =
|
||||||
ElectrumUrl::new(&self.config.liquid_electrum_url, tls, validate_domain)?;
|
ElectrumUrl::new(self.config.liquid_explorer.url(), tls, validate_domain)?;
|
||||||
*electrum_client = Some(ElectrumClient::with_options(
|
*electrum_client = Some(ElectrumClient::with_options(
|
||||||
&electrum_url,
|
&electrum_url,
|
||||||
ElectrumOptions { timeout: Some(3) },
|
ElectrumOptions { timeout: Some(3) },
|
||||||
|
|||||||
@@ -293,12 +293,17 @@ pub struct Symbol {
|
|||||||
pub position: Option<u32>,
|
pub position: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::BlockchainExplorer)]
|
||||||
|
pub enum BlockchainExplorer {
|
||||||
|
Electrum { url: String },
|
||||||
|
Esplora { url: String, use_waterfalls: bool },
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::Config)]
|
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::Config)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub liquid_electrum_url: String,
|
pub liquid_explorer: BlockchainExplorer,
|
||||||
pub bitcoin_electrum_url: String,
|
pub bitcoin_explorer: BlockchainExplorer,
|
||||||
pub mempoolspace_url: String,
|
|
||||||
pub working_dir: String,
|
pub working_dir: String,
|
||||||
pub cache_dir: Option<String>,
|
pub cache_dir: Option<String>,
|
||||||
pub network: LiquidNetwork,
|
pub network: LiquidNetwork,
|
||||||
|
|||||||
@@ -1527,6 +1527,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BlockchainExplorer dco_decode_blockchain_explorer(dynamic raw) {
|
||||||
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
|
switch (raw[0]) {
|
||||||
|
case 0:
|
||||||
|
return BlockchainExplorer_Electrum(url: dco_decode_String(raw[1]));
|
||||||
|
case 1:
|
||||||
|
return BlockchainExplorer_Esplora(
|
||||||
|
url: dco_decode_String(raw[1]),
|
||||||
|
useWaterfalls: dco_decode_bool(raw[2]),
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw Exception("unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
BlockchainInfo dco_decode_blockchain_info(dynamic raw) {
|
BlockchainInfo dco_decode_blockchain_info(dynamic raw) {
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
@@ -1912,22 +1928,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
Config dco_decode_config(dynamic raw) {
|
Config dco_decode_config(dynamic raw) {
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
final arr = raw as List<dynamic>;
|
final arr = raw as List<dynamic>;
|
||||||
if (arr.length != 14) throw Exception('unexpected arr length: expect 14 but see ${arr.length}');
|
if (arr.length != 13) throw Exception('unexpected arr length: expect 13 but see ${arr.length}');
|
||||||
return Config(
|
return Config(
|
||||||
liquidElectrumUrl: dco_decode_String(arr[0]),
|
liquidExplorer: dco_decode_blockchain_explorer(arr[0]),
|
||||||
bitcoinElectrumUrl: dco_decode_String(arr[1]),
|
bitcoinExplorer: dco_decode_blockchain_explorer(arr[1]),
|
||||||
mempoolspaceUrl: dco_decode_String(arr[2]),
|
workingDir: dco_decode_String(arr[2]),
|
||||||
workingDir: dco_decode_String(arr[3]),
|
cacheDir: dco_decode_opt_String(arr[3]),
|
||||||
cacheDir: dco_decode_opt_String(arr[4]),
|
network: dco_decode_liquid_network(arr[4]),
|
||||||
network: dco_decode_liquid_network(arr[5]),
|
paymentTimeoutSec: dco_decode_u_64(arr[5]),
|
||||||
paymentTimeoutSec: dco_decode_u_64(arr[6]),
|
syncServiceUrl: dco_decode_opt_String(arr[6]),
|
||||||
syncServiceUrl: dco_decode_opt_String(arr[7]),
|
zeroConfMaxAmountSat: dco_decode_opt_box_autoadd_u_64(arr[7]),
|
||||||
zeroConfMaxAmountSat: dco_decode_opt_box_autoadd_u_64(arr[8]),
|
breezApiKey: dco_decode_opt_String(arr[8]),
|
||||||
breezApiKey: dco_decode_opt_String(arr[9]),
|
externalInputParsers: dco_decode_opt_list_external_input_parser(arr[9]),
|
||||||
externalInputParsers: dco_decode_opt_list_external_input_parser(arr[10]),
|
useDefaultExternalInputParsers: dco_decode_bool(arr[10]),
|
||||||
useDefaultExternalInputParsers: dco_decode_bool(arr[11]),
|
onchainFeeRateLeewaySatPerVbyte: dco_decode_opt_box_autoadd_u_32(arr[11]),
|
||||||
onchainFeeRateLeewaySatPerVbyte: dco_decode_opt_box_autoadd_u_32(arr[12]),
|
assetMetadata: dco_decode_opt_list_asset_metadata(arr[12]),
|
||||||
assetMetadata: dco_decode_opt_list_asset_metadata(arr[13]),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3532,6 +3547,24 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BlockchainExplorer sse_decode_blockchain_explorer(SseDeserializer deserializer) {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
|
||||||
|
var tag_ = sse_decode_i_32(deserializer);
|
||||||
|
switch (tag_) {
|
||||||
|
case 0:
|
||||||
|
var var_url = sse_decode_String(deserializer);
|
||||||
|
return BlockchainExplorer_Electrum(url: var_url);
|
||||||
|
case 1:
|
||||||
|
var var_url = sse_decode_String(deserializer);
|
||||||
|
var var_useWaterfalls = sse_decode_bool(deserializer);
|
||||||
|
return BlockchainExplorer_Esplora(url: var_url, useWaterfalls: var_useWaterfalls);
|
||||||
|
default:
|
||||||
|
throw UnimplementedError('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
BlockchainInfo sse_decode_blockchain_info(SseDeserializer deserializer) {
|
BlockchainInfo sse_decode_blockchain_info(SseDeserializer deserializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
@@ -3918,9 +3951,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
@protected
|
@protected
|
||||||
Config sse_decode_config(SseDeserializer deserializer) {
|
Config sse_decode_config(SseDeserializer deserializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
var var_liquidElectrumUrl = sse_decode_String(deserializer);
|
var var_liquidExplorer = sse_decode_blockchain_explorer(deserializer);
|
||||||
var var_bitcoinElectrumUrl = sse_decode_String(deserializer);
|
var var_bitcoinExplorer = sse_decode_blockchain_explorer(deserializer);
|
||||||
var var_mempoolspaceUrl = sse_decode_String(deserializer);
|
|
||||||
var var_workingDir = sse_decode_String(deserializer);
|
var var_workingDir = sse_decode_String(deserializer);
|
||||||
var var_cacheDir = sse_decode_opt_String(deserializer);
|
var var_cacheDir = sse_decode_opt_String(deserializer);
|
||||||
var var_network = sse_decode_liquid_network(deserializer);
|
var var_network = sse_decode_liquid_network(deserializer);
|
||||||
@@ -3933,9 +3965,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
var var_onchainFeeRateLeewaySatPerVbyte = sse_decode_opt_box_autoadd_u_32(deserializer);
|
var var_onchainFeeRateLeewaySatPerVbyte = sse_decode_opt_box_autoadd_u_32(deserializer);
|
||||||
var var_assetMetadata = sse_decode_opt_list_asset_metadata(deserializer);
|
var var_assetMetadata = sse_decode_opt_list_asset_metadata(deserializer);
|
||||||
return Config(
|
return Config(
|
||||||
liquidElectrumUrl: var_liquidElectrumUrl,
|
liquidExplorer: var_liquidExplorer,
|
||||||
bitcoinElectrumUrl: var_bitcoinElectrumUrl,
|
bitcoinExplorer: var_bitcoinExplorer,
|
||||||
mempoolspaceUrl: var_mempoolspaceUrl,
|
|
||||||
workingDir: var_workingDir,
|
workingDir: var_workingDir,
|
||||||
cacheDir: var_cacheDir,
|
cacheDir: var_cacheDir,
|
||||||
network: var_network,
|
network: var_network,
|
||||||
@@ -5992,6 +6023,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
sse_encode_opt_String(self.message, serializer);
|
sse_encode_opt_String(self.message, serializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_blockchain_explorer(BlockchainExplorer self, SseSerializer serializer) {
|
||||||
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
|
switch (self) {
|
||||||
|
case BlockchainExplorer_Electrum(url: final url):
|
||||||
|
sse_encode_i_32(0, serializer);
|
||||||
|
sse_encode_String(url, serializer);
|
||||||
|
case BlockchainExplorer_Esplora(url: final url, useWaterfalls: final useWaterfalls):
|
||||||
|
sse_encode_i_32(1, serializer);
|
||||||
|
sse_encode_String(url, serializer);
|
||||||
|
sse_encode_bool(useWaterfalls, serializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_blockchain_info(BlockchainInfo self, SseSerializer serializer) {
|
void sse_encode_blockchain_info(BlockchainInfo self, SseSerializer serializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
@@ -6398,9 +6443,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||||||
@protected
|
@protected
|
||||||
void sse_encode_config(Config self, SseSerializer serializer) {
|
void sse_encode_config(Config self, SseSerializer serializer) {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
sse_encode_String(self.liquidElectrumUrl, serializer);
|
sse_encode_blockchain_explorer(self.liquidExplorer, serializer);
|
||||||
sse_encode_String(self.bitcoinElectrumUrl, serializer);
|
sse_encode_blockchain_explorer(self.bitcoinExplorer, serializer);
|
||||||
sse_encode_String(self.mempoolspaceUrl, serializer);
|
|
||||||
sse_encode_String(self.workingDir, serializer);
|
sse_encode_String(self.workingDir, serializer);
|
||||||
sse_encode_opt_String(self.cacheDir, serializer);
|
sse_encode_opt_String(self.cacheDir, serializer);
|
||||||
sse_encode_liquid_network(self.network, serializer);
|
sse_encode_liquid_network(self.network, serializer);
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
BitcoinAddressData dco_decode_bitcoin_address_data(dynamic raw);
|
BitcoinAddressData dco_decode_bitcoin_address_data(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BlockchainExplorer dco_decode_blockchain_explorer(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
BlockchainInfo dco_decode_blockchain_info(dynamic raw);
|
BlockchainInfo dco_decode_blockchain_info(dynamic raw);
|
||||||
|
|
||||||
@@ -723,6 +726,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
BitcoinAddressData sse_decode_bitcoin_address_data(SseDeserializer deserializer);
|
BitcoinAddressData sse_decode_bitcoin_address_data(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BlockchainExplorer sse_decode_blockchain_explorer(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
BlockchainInfo sse_decode_blockchain_info(SseDeserializer deserializer);
|
BlockchainInfo sse_decode_blockchain_info(SseDeserializer deserializer);
|
||||||
|
|
||||||
@@ -2262,6 +2268,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
wireObj.message = cst_encode_opt_String(apiObj.message);
|
wireObj.message = cst_encode_opt_String(apiObj.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void cst_api_fill_to_wire_blockchain_explorer(
|
||||||
|
BlockchainExplorer apiObj,
|
||||||
|
wire_cst_blockchain_explorer wireObj,
|
||||||
|
) {
|
||||||
|
if (apiObj is BlockchainExplorer_Electrum) {
|
||||||
|
var pre_url = cst_encode_String(apiObj.url);
|
||||||
|
wireObj.tag = 0;
|
||||||
|
wireObj.kind.Electrum.url = pre_url;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (apiObj is BlockchainExplorer_Esplora) {
|
||||||
|
var pre_url = cst_encode_String(apiObj.url);
|
||||||
|
var pre_use_waterfalls = cst_encode_bool(apiObj.useWaterfalls);
|
||||||
|
wireObj.tag = 1;
|
||||||
|
wireObj.kind.Esplora.url = pre_url;
|
||||||
|
wireObj.kind.Esplora.use_waterfalls = pre_use_waterfalls;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void cst_api_fill_to_wire_blockchain_info(BlockchainInfo apiObj, wire_cst_blockchain_info wireObj) {
|
void cst_api_fill_to_wire_blockchain_info(BlockchainInfo apiObj, wire_cst_blockchain_info wireObj) {
|
||||||
wireObj.liquid_tip = cst_encode_u_32(apiObj.liquidTip);
|
wireObj.liquid_tip = cst_encode_u_32(apiObj.liquidTip);
|
||||||
@@ -2682,9 +2709,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
|
|
||||||
@protected
|
@protected
|
||||||
void cst_api_fill_to_wire_config(Config apiObj, wire_cst_config wireObj) {
|
void cst_api_fill_to_wire_config(Config apiObj, wire_cst_config wireObj) {
|
||||||
wireObj.liquid_electrum_url = cst_encode_String(apiObj.liquidElectrumUrl);
|
cst_api_fill_to_wire_blockchain_explorer(apiObj.liquidExplorer, wireObj.liquid_explorer);
|
||||||
wireObj.bitcoin_electrum_url = cst_encode_String(apiObj.bitcoinElectrumUrl);
|
cst_api_fill_to_wire_blockchain_explorer(apiObj.bitcoinExplorer, wireObj.bitcoin_explorer);
|
||||||
wireObj.mempoolspace_url = cst_encode_String(apiObj.mempoolspaceUrl);
|
|
||||||
wireObj.working_dir = cst_encode_String(apiObj.workingDir);
|
wireObj.working_dir = cst_encode_String(apiObj.workingDir);
|
||||||
wireObj.cache_dir = cst_encode_opt_String(apiObj.cacheDir);
|
wireObj.cache_dir = cst_encode_opt_String(apiObj.cacheDir);
|
||||||
wireObj.network = cst_encode_liquid_network(apiObj.network);
|
wireObj.network = cst_encode_liquid_network(apiObj.network);
|
||||||
@@ -4077,6 +4103,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||||||
@protected
|
@protected
|
||||||
void sse_encode_bitcoin_address_data(BitcoinAddressData self, SseSerializer serializer);
|
void sse_encode_bitcoin_address_data(BitcoinAddressData self, SseSerializer serializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_blockchain_explorer(BlockchainExplorer self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_blockchain_info(BlockchainInfo self, SseSerializer serializer);
|
void sse_encode_blockchain_info(BlockchainInfo self, SseSerializer serializer);
|
||||||
|
|
||||||
@@ -7056,6 +7085,30 @@ final class wire_cst_sdk_event extends ffi.Struct {
|
|||||||
external SdkEventKind kind;
|
external SdkEventKind kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class wire_cst_BlockchainExplorer_Electrum extends ffi.Struct {
|
||||||
|
external ffi.Pointer<wire_cst_list_prim_u_8_strict> url;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class wire_cst_BlockchainExplorer_Esplora extends ffi.Struct {
|
||||||
|
external ffi.Pointer<wire_cst_list_prim_u_8_strict> url;
|
||||||
|
|
||||||
|
@ffi.Bool()
|
||||||
|
external bool use_waterfalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class BlockchainExplorerKind extends ffi.Union {
|
||||||
|
external wire_cst_BlockchainExplorer_Electrum Electrum;
|
||||||
|
|
||||||
|
external wire_cst_BlockchainExplorer_Esplora Esplora;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class wire_cst_blockchain_explorer extends ffi.Struct {
|
||||||
|
@ffi.Int32()
|
||||||
|
external int tag;
|
||||||
|
|
||||||
|
external BlockchainExplorerKind kind;
|
||||||
|
}
|
||||||
|
|
||||||
final class wire_cst_external_input_parser extends ffi.Struct {
|
final class wire_cst_external_input_parser extends ffi.Struct {
|
||||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> provider_id;
|
external ffi.Pointer<wire_cst_list_prim_u_8_strict> provider_id;
|
||||||
|
|
||||||
@@ -7090,11 +7143,9 @@ final class wire_cst_list_asset_metadata extends ffi.Struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class wire_cst_config extends ffi.Struct {
|
final class wire_cst_config extends ffi.Struct {
|
||||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> liquid_electrum_url;
|
external wire_cst_blockchain_explorer liquid_explorer;
|
||||||
|
|
||||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> bitcoin_electrum_url;
|
external wire_cst_blockchain_explorer bitcoin_explorer;
|
||||||
|
|
||||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> mempoolspace_url;
|
|
||||||
|
|
||||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> working_dir;
|
external ffi.Pointer<wire_cst_list_prim_u_8_strict> working_dir;
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,19 @@ class BackupRequest {
|
|||||||
other is BackupRequest && runtimeType == other.runtimeType && backupPath == other.backupPath;
|
other is BackupRequest && runtimeType == other.runtimeType && backupPath == other.backupPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class BlockchainExplorer with _$BlockchainExplorer {
|
||||||
|
const BlockchainExplorer._();
|
||||||
|
|
||||||
|
const factory BlockchainExplorer.electrum({required String url}) = BlockchainExplorer_Electrum;
|
||||||
|
const factory BlockchainExplorer.esplora({
|
||||||
|
required String url,
|
||||||
|
|
||||||
|
/// Whether or not to use the "waterfalls" extension
|
||||||
|
required bool useWaterfalls,
|
||||||
|
}) = BlockchainExplorer_Esplora;
|
||||||
|
}
|
||||||
|
|
||||||
class BlockchainInfo {
|
class BlockchainInfo {
|
||||||
final int liquidTip;
|
final int liquidTip;
|
||||||
final int bitcoinTip;
|
final int bitcoinTip;
|
||||||
@@ -228,11 +241,8 @@ class CheckMessageResponse {
|
|||||||
|
|
||||||
/// Configuration for the Liquid SDK
|
/// Configuration for the Liquid SDK
|
||||||
class Config {
|
class Config {
|
||||||
final String liquidElectrumUrl;
|
final BlockchainExplorer liquidExplorer;
|
||||||
final String bitcoinElectrumUrl;
|
final BlockchainExplorer bitcoinExplorer;
|
||||||
|
|
||||||
/// The mempool.space API URL, has to be in the format: `https://mempool.space/api`
|
|
||||||
final String mempoolspaceUrl;
|
|
||||||
|
|
||||||
/// Directory in which the DB and log files are stored.
|
/// Directory in which the DB and log files are stored.
|
||||||
///
|
///
|
||||||
@@ -282,9 +292,8 @@ class Config {
|
|||||||
final List<AssetMetadata>? assetMetadata;
|
final List<AssetMetadata>? assetMetadata;
|
||||||
|
|
||||||
const Config({
|
const Config({
|
||||||
required this.liquidElectrumUrl,
|
required this.liquidExplorer,
|
||||||
required this.bitcoinElectrumUrl,
|
required this.bitcoinExplorer,
|
||||||
required this.mempoolspaceUrl,
|
|
||||||
required this.workingDir,
|
required this.workingDir,
|
||||||
this.cacheDir,
|
this.cacheDir,
|
||||||
required this.network,
|
required this.network,
|
||||||
@@ -300,9 +309,8 @@ class Config {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
liquidElectrumUrl.hashCode ^
|
liquidExplorer.hashCode ^
|
||||||
bitcoinElectrumUrl.hashCode ^
|
bitcoinExplorer.hashCode ^
|
||||||
mempoolspaceUrl.hashCode ^
|
|
||||||
workingDir.hashCode ^
|
workingDir.hashCode ^
|
||||||
cacheDir.hashCode ^
|
cacheDir.hashCode ^
|
||||||
network.hashCode ^
|
network.hashCode ^
|
||||||
@@ -320,9 +328,8 @@ class Config {
|
|||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is Config &&
|
other is Config &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
liquidElectrumUrl == other.liquidElectrumUrl &&
|
liquidExplorer == other.liquidExplorer &&
|
||||||
bitcoinElectrumUrl == other.bitcoinElectrumUrl &&
|
bitcoinExplorer == other.bitcoinExplorer &&
|
||||||
mempoolspaceUrl == other.mempoolspaceUrl &&
|
|
||||||
workingDir == other.workingDir &&
|
workingDir == other.workingDir &&
|
||||||
cacheDir == other.cacheDir &&
|
cacheDir == other.cacheDir &&
|
||||||
network == other.network &&
|
network == other.network &&
|
||||||
|
|||||||
@@ -12,6 +12,202 @@ part of 'model.dart';
|
|||||||
|
|
||||||
// dart format off
|
// dart format off
|
||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$BlockchainExplorer {
|
||||||
|
|
||||||
|
String get url;
|
||||||
|
/// Create a copy of BlockchainExplorer
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$BlockchainExplorerCopyWith<BlockchainExplorer> get copyWith => _$BlockchainExplorerCopyWithImpl<BlockchainExplorer>(this as BlockchainExplorer, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is BlockchainExplorer&&(identical(other.url, url) || other.url == url));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'BlockchainExplorer(url: $url)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $BlockchainExplorerCopyWith<$Res> {
|
||||||
|
factory $BlockchainExplorerCopyWith(BlockchainExplorer value, $Res Function(BlockchainExplorer) _then) = _$BlockchainExplorerCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String url
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$BlockchainExplorerCopyWithImpl<$Res>
|
||||||
|
implements $BlockchainExplorerCopyWith<$Res> {
|
||||||
|
_$BlockchainExplorerCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final BlockchainExplorer _self;
|
||||||
|
final $Res Function(BlockchainExplorer) _then;
|
||||||
|
|
||||||
|
/// Create a copy of BlockchainExplorer
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? url = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class BlockchainExplorer_Electrum extends BlockchainExplorer {
|
||||||
|
const BlockchainExplorer_Electrum({required this.url}): super._();
|
||||||
|
|
||||||
|
|
||||||
|
@override final String url;
|
||||||
|
|
||||||
|
/// Create a copy of BlockchainExplorer
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$BlockchainExplorer_ElectrumCopyWith<BlockchainExplorer_Electrum> get copyWith => _$BlockchainExplorer_ElectrumCopyWithImpl<BlockchainExplorer_Electrum>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is BlockchainExplorer_Electrum&&(identical(other.url, url) || other.url == url));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'BlockchainExplorer.electrum(url: $url)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $BlockchainExplorer_ElectrumCopyWith<$Res> implements $BlockchainExplorerCopyWith<$Res> {
|
||||||
|
factory $BlockchainExplorer_ElectrumCopyWith(BlockchainExplorer_Electrum value, $Res Function(BlockchainExplorer_Electrum) _then) = _$BlockchainExplorer_ElectrumCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String url
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$BlockchainExplorer_ElectrumCopyWithImpl<$Res>
|
||||||
|
implements $BlockchainExplorer_ElectrumCopyWith<$Res> {
|
||||||
|
_$BlockchainExplorer_ElectrumCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final BlockchainExplorer_Electrum _self;
|
||||||
|
final $Res Function(BlockchainExplorer_Electrum) _then;
|
||||||
|
|
||||||
|
/// Create a copy of BlockchainExplorer
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? url = null,}) {
|
||||||
|
return _then(BlockchainExplorer_Electrum(
|
||||||
|
url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
|
||||||
|
class BlockchainExplorer_Esplora extends BlockchainExplorer {
|
||||||
|
const BlockchainExplorer_Esplora({required this.url, required this.useWaterfalls}): super._();
|
||||||
|
|
||||||
|
|
||||||
|
@override final String url;
|
||||||
|
/// Whether or not to use the "waterfalls" extension
|
||||||
|
final bool useWaterfalls;
|
||||||
|
|
||||||
|
/// Create a copy of BlockchainExplorer
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$BlockchainExplorer_EsploraCopyWith<BlockchainExplorer_Esplora> get copyWith => _$BlockchainExplorer_EsploraCopyWithImpl<BlockchainExplorer_Esplora>(this, _$identity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is BlockchainExplorer_Esplora&&(identical(other.url, url) || other.url == url)&&(identical(other.useWaterfalls, useWaterfalls) || other.useWaterfalls == useWaterfalls));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,url,useWaterfalls);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'BlockchainExplorer.esplora(url: $url, useWaterfalls: $useWaterfalls)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $BlockchainExplorer_EsploraCopyWith<$Res> implements $BlockchainExplorerCopyWith<$Res> {
|
||||||
|
factory $BlockchainExplorer_EsploraCopyWith(BlockchainExplorer_Esplora value, $Res Function(BlockchainExplorer_Esplora) _then) = _$BlockchainExplorer_EsploraCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String url, bool useWaterfalls
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$BlockchainExplorer_EsploraCopyWithImpl<$Res>
|
||||||
|
implements $BlockchainExplorer_EsploraCopyWith<$Res> {
|
||||||
|
_$BlockchainExplorer_EsploraCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final BlockchainExplorer_Esplora _self;
|
||||||
|
final $Res Function(BlockchainExplorer_Esplora) _then;
|
||||||
|
|
||||||
|
/// Create a copy of BlockchainExplorer
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? url = null,Object? useWaterfalls = null,}) {
|
||||||
|
return _then(BlockchainExplorer_Esplora(
|
||||||
|
url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,useWaterfalls: null == useWaterfalls ? _self.useWaterfalls : useWaterfalls // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$GetPaymentRequest {
|
mixin _$GetPaymentRequest {
|
||||||
|
|
||||||
|
|||||||
@@ -4985,6 +4985,30 @@ final class wire_cst_sdk_event extends ffi.Struct {
|
|||||||
external SdkEventKind kind;
|
external SdkEventKind kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class wire_cst_BlockchainExplorer_Electrum extends ffi.Struct {
|
||||||
|
external ffi.Pointer<wire_cst_list_prim_u_8_strict> url;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class wire_cst_BlockchainExplorer_Esplora extends ffi.Struct {
|
||||||
|
external ffi.Pointer<wire_cst_list_prim_u_8_strict> url;
|
||||||
|
|
||||||
|
@ffi.Bool()
|
||||||
|
external bool use_waterfalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class BlockchainExplorerKind extends ffi.Union {
|
||||||
|
external wire_cst_BlockchainExplorer_Electrum Electrum;
|
||||||
|
|
||||||
|
external wire_cst_BlockchainExplorer_Esplora Esplora;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class wire_cst_blockchain_explorer extends ffi.Struct {
|
||||||
|
@ffi.Int32()
|
||||||
|
external int tag;
|
||||||
|
|
||||||
|
external BlockchainExplorerKind kind;
|
||||||
|
}
|
||||||
|
|
||||||
final class wire_cst_external_input_parser extends ffi.Struct {
|
final class wire_cst_external_input_parser extends ffi.Struct {
|
||||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> provider_id;
|
external ffi.Pointer<wire_cst_list_prim_u_8_strict> provider_id;
|
||||||
|
|
||||||
@@ -5019,11 +5043,9 @@ final class wire_cst_list_asset_metadata extends ffi.Struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class wire_cst_config extends ffi.Struct {
|
final class wire_cst_config extends ffi.Struct {
|
||||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> liquid_electrum_url;
|
external wire_cst_blockchain_explorer liquid_explorer;
|
||||||
|
|
||||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> bitcoin_electrum_url;
|
external wire_cst_blockchain_explorer bitcoin_explorer;
|
||||||
|
|
||||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> mempoolspace_url;
|
|
||||||
|
|
||||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> working_dir;
|
external ffi.Pointer<wire_cst_list_prim_u_8_strict> working_dir;
|
||||||
|
|
||||||
|
|||||||
@@ -418,9 +418,8 @@ fun asConfig(config: ReadableMap): Config? {
|
|||||||
if (!validateMandatoryFields(
|
if (!validateMandatoryFields(
|
||||||
config,
|
config,
|
||||||
arrayOf(
|
arrayOf(
|
||||||
"liquidElectrumUrl",
|
"liquidExplorer",
|
||||||
"bitcoinElectrumUrl",
|
"bitcoinExplorer",
|
||||||
"mempoolspaceUrl",
|
|
||||||
"workingDir",
|
"workingDir",
|
||||||
"network",
|
"network",
|
||||||
"paymentTimeoutSec",
|
"paymentTimeoutSec",
|
||||||
@@ -430,9 +429,8 @@ fun asConfig(config: ReadableMap): Config? {
|
|||||||
) {
|
) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val liquidElectrumUrl = config.getString("liquidElectrumUrl")!!
|
val liquidExplorer = config.getMap("liquidExplorer")?.let { asBlockchainExplorer(it) }!!
|
||||||
val bitcoinElectrumUrl = config.getString("bitcoinElectrumUrl")!!
|
val bitcoinExplorer = config.getMap("bitcoinExplorer")?.let { asBlockchainExplorer(it) }!!
|
||||||
val mempoolspaceUrl = config.getString("mempoolspaceUrl")!!
|
|
||||||
val workingDir = config.getString("workingDir")!!
|
val workingDir = config.getString("workingDir")!!
|
||||||
val network = config.getString("network")?.let { asLiquidNetwork(it) }!!
|
val network = config.getString("network")?.let { asLiquidNetwork(it) }!!
|
||||||
val paymentTimeoutSec = config.getDouble("paymentTimeoutSec").toULong()
|
val paymentTimeoutSec = config.getDouble("paymentTimeoutSec").toULong()
|
||||||
@@ -479,9 +477,8 @@ fun asConfig(config: ReadableMap): Config? {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
return Config(
|
return Config(
|
||||||
liquidElectrumUrl,
|
liquidExplorer,
|
||||||
bitcoinElectrumUrl,
|
bitcoinExplorer,
|
||||||
mempoolspaceUrl,
|
|
||||||
workingDir,
|
workingDir,
|
||||||
network,
|
network,
|
||||||
paymentTimeoutSec,
|
paymentTimeoutSec,
|
||||||
@@ -498,9 +495,8 @@ fun asConfig(config: ReadableMap): Config? {
|
|||||||
|
|
||||||
fun readableMapOf(config: Config): ReadableMap =
|
fun readableMapOf(config: Config): ReadableMap =
|
||||||
readableMapOf(
|
readableMapOf(
|
||||||
"liquidElectrumUrl" to config.liquidElectrumUrl,
|
"liquidExplorer" to readableMapOf(config.liquidExplorer),
|
||||||
"bitcoinElectrumUrl" to config.bitcoinElectrumUrl,
|
"bitcoinExplorer" to readableMapOf(config.bitcoinExplorer),
|
||||||
"mempoolspaceUrl" to config.mempoolspaceUrl,
|
|
||||||
"workingDir" to config.workingDir,
|
"workingDir" to config.workingDir,
|
||||||
"network" to config.network.name.lowercase(),
|
"network" to config.network.name.lowercase(),
|
||||||
"paymentTimeoutSec" to config.paymentTimeoutSec,
|
"paymentTimeoutSec" to config.paymentTimeoutSec,
|
||||||
@@ -2994,6 +2990,48 @@ fun asAmountList(arr: ReadableArray): List<Amount> {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun asBlockchainExplorer(blockchainExplorer: ReadableMap): BlockchainExplorer? {
|
||||||
|
val type = blockchainExplorer.getString("type")
|
||||||
|
|
||||||
|
if (type == "electrum") {
|
||||||
|
val url = blockchainExplorer.getString("url")!!
|
||||||
|
return BlockchainExplorer.Electrum(url)
|
||||||
|
}
|
||||||
|
if (type == "esplora") {
|
||||||
|
val url = blockchainExplorer.getString("url")!!
|
||||||
|
val useWaterfalls = blockchainExplorer.getBoolean("useWaterfalls")
|
||||||
|
return BlockchainExplorer.Esplora(url, useWaterfalls)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readableMapOf(blockchainExplorer: BlockchainExplorer): ReadableMap? {
|
||||||
|
val map = Arguments.createMap()
|
||||||
|
when (blockchainExplorer) {
|
||||||
|
is BlockchainExplorer.Electrum -> {
|
||||||
|
pushToMap(map, "type", "electrum")
|
||||||
|
pushToMap(map, "url", blockchainExplorer.url)
|
||||||
|
}
|
||||||
|
is BlockchainExplorer.Esplora -> {
|
||||||
|
pushToMap(map, "type", "esplora")
|
||||||
|
pushToMap(map, "url", blockchainExplorer.url)
|
||||||
|
pushToMap(map, "useWaterfalls", blockchainExplorer.useWaterfalls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
fun asBlockchainExplorerList(arr: ReadableArray): List<BlockchainExplorer> {
|
||||||
|
val list = ArrayList<BlockchainExplorer>()
|
||||||
|
for (value in arr.toList()) {
|
||||||
|
when (value) {
|
||||||
|
is ReadableMap -> list.add(asBlockchainExplorer(value)!!)
|
||||||
|
else -> throw SdkException.Generic(errUnexpectedType(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
fun asBuyBitcoinProvider(type: String): BuyBitcoinProvider = BuyBitcoinProvider.valueOf(camelToUpperSnakeCase(type))
|
fun asBuyBitcoinProvider(type: String): BuyBitcoinProvider = BuyBitcoinProvider.valueOf(camelToUpperSnakeCase(type))
|
||||||
|
|
||||||
fun asBuyBitcoinProviderList(arr: ReadableArray): List<BuyBitcoinProvider> {
|
fun asBuyBitcoinProviderList(arr: ReadableArray): List<BuyBitcoinProvider> {
|
||||||
|
|||||||
@@ -492,15 +492,16 @@ enum BreezSDKLiquidMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func asConfig(config: [String: Any?]) throws -> Config {
|
static func asConfig(config: [String: Any?]) throws -> Config {
|
||||||
guard let liquidElectrumUrl = config["liquidElectrumUrl"] as? String else {
|
guard let liquidExplorerTmp = config["liquidExplorer"] as? [String: Any?] else {
|
||||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "liquidElectrumUrl", typeName: "Config"))
|
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "liquidExplorer", typeName: "Config"))
|
||||||
}
|
}
|
||||||
guard let bitcoinElectrumUrl = config["bitcoinElectrumUrl"] as? String else {
|
let liquidExplorer = try asBlockchainExplorer(blockchainExplorer: liquidExplorerTmp)
|
||||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "bitcoinElectrumUrl", typeName: "Config"))
|
|
||||||
}
|
guard let bitcoinExplorerTmp = config["bitcoinExplorer"] as? [String: Any?] else {
|
||||||
guard let mempoolspaceUrl = config["mempoolspaceUrl"] as? String else {
|
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "bitcoinExplorer", typeName: "Config"))
|
||||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "mempoolspaceUrl", typeName: "Config"))
|
|
||||||
}
|
}
|
||||||
|
let bitcoinExplorer = try asBlockchainExplorer(blockchainExplorer: bitcoinExplorerTmp)
|
||||||
|
|
||||||
guard let workingDir = config["workingDir"] as? String else {
|
guard let workingDir = config["workingDir"] as? String else {
|
||||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "workingDir", typeName: "Config"))
|
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "workingDir", typeName: "Config"))
|
||||||
}
|
}
|
||||||
@@ -560,14 +561,13 @@ enum BreezSDKLiquidMapper {
|
|||||||
assetMetadata = try asAssetMetadataList(arr: assetMetadataTmp)
|
assetMetadata = try asAssetMetadataList(arr: assetMetadataTmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Config(liquidElectrumUrl: liquidElectrumUrl, bitcoinElectrumUrl: bitcoinElectrumUrl, mempoolspaceUrl: mempoolspaceUrl, workingDir: workingDir, network: network, paymentTimeoutSec: paymentTimeoutSec, syncServiceUrl: syncServiceUrl, breezApiKey: breezApiKey, cacheDir: cacheDir, zeroConfMaxAmountSat: zeroConfMaxAmountSat, useDefaultExternalInputParsers: useDefaultExternalInputParsers, externalInputParsers: externalInputParsers, onchainFeeRateLeewaySatPerVbyte: onchainFeeRateLeewaySatPerVbyte, assetMetadata: assetMetadata)
|
return Config(liquidExplorer: liquidExplorer, bitcoinExplorer: bitcoinExplorer, workingDir: workingDir, network: network, paymentTimeoutSec: paymentTimeoutSec, syncServiceUrl: syncServiceUrl, breezApiKey: breezApiKey, cacheDir: cacheDir, zeroConfMaxAmountSat: zeroConfMaxAmountSat, useDefaultExternalInputParsers: useDefaultExternalInputParsers, externalInputParsers: externalInputParsers, onchainFeeRateLeewaySatPerVbyte: onchainFeeRateLeewaySatPerVbyte, assetMetadata: assetMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func dictionaryOf(config: Config) -> [String: Any?] {
|
static func dictionaryOf(config: Config) -> [String: Any?] {
|
||||||
return [
|
return [
|
||||||
"liquidElectrumUrl": config.liquidElectrumUrl,
|
"liquidExplorer": dictionaryOf(blockchainExplorer: config.liquidExplorer),
|
||||||
"bitcoinElectrumUrl": config.bitcoinElectrumUrl,
|
"bitcoinExplorer": dictionaryOf(blockchainExplorer: config.bitcoinExplorer),
|
||||||
"mempoolspaceUrl": config.mempoolspaceUrl,
|
|
||||||
"workingDir": config.workingDir,
|
"workingDir": config.workingDir,
|
||||||
"network": valueOf(liquidNetwork: config.network),
|
"network": valueOf(liquidNetwork: config.network),
|
||||||
"paymentTimeoutSec": config.paymentTimeoutSec,
|
"paymentTimeoutSec": config.paymentTimeoutSec,
|
||||||
@@ -3484,6 +3484,65 @@ enum BreezSDKLiquidMapper {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func asBlockchainExplorer(blockchainExplorer: [String: Any?]) throws -> BlockchainExplorer {
|
||||||
|
let type = blockchainExplorer["type"] as! String
|
||||||
|
if type == "electrum" {
|
||||||
|
guard let _url = blockchainExplorer["url"] as? String else {
|
||||||
|
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "url", typeName: "BlockchainExplorer"))
|
||||||
|
}
|
||||||
|
return BlockchainExplorer.electrum(url: _url)
|
||||||
|
}
|
||||||
|
if type == "esplora" {
|
||||||
|
guard let _url = blockchainExplorer["url"] as? String else {
|
||||||
|
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "url", typeName: "BlockchainExplorer"))
|
||||||
|
}
|
||||||
|
guard let _useWaterfalls = blockchainExplorer["useWaterfalls"] as? Bool else {
|
||||||
|
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "useWaterfalls", typeName: "BlockchainExplorer"))
|
||||||
|
}
|
||||||
|
return BlockchainExplorer.esplora(url: _url, useWaterfalls: _useWaterfalls)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw SdkError.Generic(message: "Unexpected type \(type) for enum BlockchainExplorer")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func dictionaryOf(blockchainExplorer: BlockchainExplorer) -> [String: Any?] {
|
||||||
|
switch blockchainExplorer {
|
||||||
|
case let .electrum(
|
||||||
|
url
|
||||||
|
):
|
||||||
|
return [
|
||||||
|
"type": "electrum",
|
||||||
|
"url": url,
|
||||||
|
]
|
||||||
|
|
||||||
|
case let .esplora(
|
||||||
|
url, useWaterfalls
|
||||||
|
):
|
||||||
|
return [
|
||||||
|
"type": "esplora",
|
||||||
|
"url": url,
|
||||||
|
"useWaterfalls": useWaterfalls,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func arrayOf(blockchainExplorerList: [BlockchainExplorer]) -> [Any] {
|
||||||
|
return blockchainExplorerList.map { v -> [String: Any?] in return dictionaryOf(blockchainExplorer: v) }
|
||||||
|
}
|
||||||
|
|
||||||
|
static func asBlockchainExplorerList(arr: [Any]) throws -> [BlockchainExplorer] {
|
||||||
|
var list = [BlockchainExplorer]()
|
||||||
|
for value in arr {
|
||||||
|
if let val = value as? [String: Any?] {
|
||||||
|
var blockchainExplorer = try asBlockchainExplorer(blockchainExplorer: val)
|
||||||
|
list.append(blockchainExplorer)
|
||||||
|
} else {
|
||||||
|
throw SdkError.Generic(message: errUnexpectedType(typeName: "BlockchainExplorer"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
static func asBuyBitcoinProvider(buyBitcoinProvider: String) throws -> BuyBitcoinProvider {
|
static func asBuyBitcoinProvider(buyBitcoinProvider: String) throws -> BuyBitcoinProvider {
|
||||||
switch buyBitcoinProvider {
|
switch buyBitcoinProvider {
|
||||||
case "moonpay":
|
case "moonpay":
|
||||||
|
|||||||
@@ -88,9 +88,8 @@ export interface CheckMessageResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
liquidElectrumUrl: string
|
liquidExplorer: BlockchainExplorer
|
||||||
bitcoinElectrumUrl: string
|
bitcoinExplorer: BlockchainExplorer
|
||||||
mempoolspaceUrl: string
|
|
||||||
workingDir: string
|
workingDir: string
|
||||||
network: LiquidNetwork
|
network: LiquidNetwork
|
||||||
paymentTimeoutSec: number
|
paymentTimeoutSec: number
|
||||||
@@ -519,6 +518,20 @@ export type Amount = {
|
|||||||
fractionalAmount: number
|
fractionalAmount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum BlockchainExplorerVariant {
|
||||||
|
ELECTRUM = "electrum",
|
||||||
|
ESPLORA = "esplora"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BlockchainExplorer = {
|
||||||
|
type: BlockchainExplorerVariant.ELECTRUM,
|
||||||
|
url: string
|
||||||
|
} | {
|
||||||
|
type: BlockchainExplorerVariant.ESPLORA,
|
||||||
|
url: string
|
||||||
|
useWaterfalls: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export enum BuyBitcoinProvider {
|
export enum BuyBitcoinProvider {
|
||||||
MOONPAY = "moonpay"
|
MOONPAY = "moonpay"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1 @@
|
|||||||
MEMPOOL_WEB_IMAGE=mempool/frontend:latest
|
|
||||||
MEMPOOL_API_IMAGE=mempool/backend:latest
|
|
||||||
MEMPOOL_DB_IMAGE=mariadb:10.5.21
|
|
||||||
RT_SYNC_IMAGE=danielgranhao/data-sync:latest
|
RT_SYNC_IMAGE=danielgranhao/data-sync:latest
|
||||||
|
|||||||
4
regtest/.gitignore
vendored
4
regtest/.gitignore
vendored
@@ -1,8 +1,4 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.idea/
|
.idea/
|
||||||
data/mempool/*
|
|
||||||
!data/mempool/.gitkeep
|
|
||||||
data/mempool-db/*
|
|
||||||
!data/mempool-db/.gitkeep
|
|
||||||
data/rt-sync/*
|
data/rt-sync/*
|
||||||
!data/rt-sync/.gitkeep
|
!data/rt-sync/.gitkeep
|
||||||
|
|||||||
@@ -1,51 +1,4 @@
|
|||||||
services:
|
services:
|
||||||
mempool-web:
|
|
||||||
environment:
|
|
||||||
FRONTEND_HTTP_PORT: "8080"
|
|
||||||
BACKEND_MAINNET_HTTP_HOST: "mempool-api"
|
|
||||||
image: ${MEMPOOL_WEB_IMAGE}
|
|
||||||
user: "0:0"
|
|
||||||
restart: on-failure
|
|
||||||
command: "./wait-for mempool-db:3306 --timeout=720 -- nginx -g 'daemon off;'"
|
|
||||||
ports:
|
|
||||||
- 80:8080
|
|
||||||
mempool-api:
|
|
||||||
environment:
|
|
||||||
MEMPOOL_NETWORK: "regtest"
|
|
||||||
MEMPOOL_BACKEND: "electrum"
|
|
||||||
CORE_RPC_HOST: "bitcoind"
|
|
||||||
CORE_RPC_PORT: "18443"
|
|
||||||
CORE_RPC_COOKIE: "true"
|
|
||||||
CORE_RPC_COOKIE_PATH: "/root/.bitcoin/regtest/.cookie"
|
|
||||||
CORE_RPC_TIMEOUT: "60000"
|
|
||||||
ELECTRUM_HOST: "electrs"
|
|
||||||
ELECTRUM_PORT: "19001"
|
|
||||||
ELECTRUM_TLS_ENABLED: "false"
|
|
||||||
DATABASE_ENABLED: "true"
|
|
||||||
DATABASE_HOST: "mempool-db"
|
|
||||||
DATABASE_DATABASE: "mempool"
|
|
||||||
DATABASE_USERNAME: "mempool"
|
|
||||||
DATABASE_PASSWORD: "mempool"
|
|
||||||
STATISTICS_ENABLED: "true"
|
|
||||||
image: ${MEMPOOL_API_IMAGE}
|
|
||||||
user: "0:0"
|
|
||||||
restart: on-failure
|
|
||||||
command: "./wait-for-it.sh mempool-db:3306 --timeout=720 --strict -- ./start.sh"
|
|
||||||
volumes:
|
|
||||||
- mempool-data:/backend/cache
|
|
||||||
- bitcoin-data:/root/.bitcoin
|
|
||||||
mempool-db:
|
|
||||||
environment:
|
|
||||||
MYSQL_DATABASE: "mempool"
|
|
||||||
MYSQL_USER: "mempool"
|
|
||||||
MYSQL_PASSWORD: "mempool"
|
|
||||||
MYSQL_ROOT_PASSWORD: "admin"
|
|
||||||
image: ${MEMPOOL_DB_IMAGE}
|
|
||||||
user: "0:0"
|
|
||||||
restart: on-failure
|
|
||||||
volumes:
|
|
||||||
- mempool-db-data:/var/lib/mysql
|
|
||||||
|
|
||||||
rt-sync:
|
rt-sync:
|
||||||
environment:
|
environment:
|
||||||
SQLITE_DIR_PATH: /app/db
|
SQLITE_DIR_PATH: /app/db
|
||||||
|
|||||||
Reference in New Issue
Block a user