Implement Chain Swaps for sending (#298)

This commit is contained in:
Ross Savage
2024-06-19 12:48:13 +02:00
committed by GitHub
parent 6224c5133c
commit 428d69729b
38 changed files with 3915 additions and 331 deletions

176
cli/Cargo.lock generated
View File

@@ -381,7 +381,7 @@ checksum = "829a082bd3761fde7476dc2ed85ca56c11628948460ece621e4f56fef5046567"
[[package]] [[package]]
name = "boltz-client" name = "boltz-client"
version = "0.1.3" version = "0.1.3"
source = "git+https://github.com/hydra-yse/boltz-rust?branch=yse-breez-latest#66cdf65ba889a25a5274af3d27f5f52a2d4e3cc9" source = "git+https://github.com/dangeross/boltz-rust?rev=cb2cb02d44fb81cc8ce5d8000346f52cc26b3fc1#cb2cb02d44fb81cc8ce5d8000346f52cc26b3fc1"
dependencies = [ dependencies = [
"bip39", "bip39",
"bitcoin 0.31.2", "bitcoin 0.31.2",
@@ -423,15 +423,18 @@ dependencies = [
"bip39", "bip39",
"boltz-client", "boltz-client",
"chrono", "chrono",
"electrum-client",
"env_logger 0.11.3", "env_logger 0.11.3",
"flutter_rust_bridge", "flutter_rust_bridge",
"futures-util", "futures-util",
"glob", "glob",
"hex",
"log", "log",
"lwk_common", "lwk_common",
"lwk_signer", "lwk_signer",
"lwk_wollet", "lwk_wollet",
"openssl", "openssl",
"reqwest 0.11.20",
"rusqlite", "rusqlite",
"rusqlite_migration", "rusqlite_migration",
"security-framework", "security-framework",
@@ -1041,6 +1044,25 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "h2"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.5" version = "0.4.5"
@@ -1052,7 +1074,7 @@ dependencies = [
"fnv", "fnv",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"http", "http 1.1.0",
"indexmap", "indexmap",
"slab", "slab",
"tokio", "tokio",
@@ -1133,6 +1155,17 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]] [[package]]
name = "http" name = "http"
version = "1.1.0" version = "1.1.0"
@@ -1144,6 +1177,17 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "1.0.0" version = "1.0.0"
@@ -1151,7 +1195,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http 1.1.0",
] ]
[[package]] [[package]]
@@ -1162,8 +1206,8 @@ checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"http", "http 1.1.0",
"http-body", "http-body 1.0.0",
"pin-project-lite", "pin-project-lite",
] ]
@@ -1173,6 +1217,12 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "1.3.0" version = "1.3.0"
@@ -1188,6 +1238,30 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.3.1" version = "1.3.1"
@@ -1197,9 +1271,9 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"h2", "h2 0.4.5",
"http", "http 1.1.0",
"http-body", "http-body 1.0.0",
"httparse", "httparse",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
@@ -1215,8 +1289,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http", "http 1.1.0",
"hyper", "hyper 1.3.1",
"hyper-util", "hyper-util",
"rustls 0.22.4", "rustls 0.22.4",
"rustls-pki-types", "rustls-pki-types",
@@ -1225,6 +1299,19 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[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.29",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.3" version = "0.1.3"
@@ -1234,9 +1321,9 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"http", "http 1.1.0",
"http-body", "http-body 1.0.0",
"hyper", "hyper 1.3.1",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio", "tokio",
@@ -1428,7 +1515,7 @@ dependencies = [
"hex", "hex",
"lwk_common", "lwk_common",
"rand", "rand",
"reqwest", "reqwest 0.12.4",
"serde", "serde",
"serde_bytes", "serde_bytes",
"serde_cbor", "serde_cbor",
@@ -1469,7 +1556,7 @@ dependencies = [
"once_cell", "once_cell",
"rand", "rand",
"regex-lite", "regex-lite",
"reqwest", "reqwest 0.12.4",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@@ -1882,6 +1969,43 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "reqwest"
version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
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.29",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg 0.50.0",
]
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.4" version = "0.12.4"
@@ -1894,11 +2018,11 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2", "h2 0.4.5",
"http", "http 1.1.0",
"http-body", "http-body 1.0.0",
"http-body-util", "http-body-util",
"hyper", "hyper 1.3.1",
"hyper-rustls", "hyper-rustls",
"hyper-util", "hyper-util",
"ipnet", "ipnet",
@@ -1924,7 +2048,7 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"webpki-roots 0.26.1", "webpki-roots 0.26.1",
"winreg", "winreg 0.52.0",
] ]
[[package]] [[package]]
@@ -2579,7 +2703,7 @@ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
"data-encoding", "data-encoding",
"http", "http 1.1.0",
"httparse", "httparse",
"log", "log",
"native-tls", "native-tls",
@@ -3000,6 +3124,16 @@ version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.52.0" version = "0.52.0"

View File

@@ -20,7 +20,7 @@ use serde_json::to_string_pretty;
#[derive(Parser, Debug, Clone, PartialEq)] #[derive(Parser, Debug, Clone, PartialEq)]
pub(crate) enum Command { pub(crate) enum Command {
/// Send lbtc and receive btc through a swap /// Send lbtc and receive btc lightning through a swap
SendPayment { SendPayment {
/// Invoice which has to be paid /// Invoice which has to be paid
bolt11: String, bolt11: String,
@@ -29,6 +29,14 @@ pub(crate) enum Command {
#[arg(short, long)] #[arg(short, long)]
delay: Option<u64>, delay: Option<u64>,
}, },
/// Send lbtc and receive btc onchain through a swap
SendOnchainPayment {
/// Btc onchain address to send to
address: String,
/// Amount that will be received, in satoshi
amount_sat: u64,
},
/// Receive lbtc and send btc through a swap /// Receive lbtc and send btc through a swap
ReceivePayment { ReceivePayment {
/// Amount the payer will send, in satoshi /// Amount the payer will send, in satoshi
@@ -150,6 +158,30 @@ pub(crate) async fn handle_command(
command_result!(response) command_result!(response)
} }
} }
Command::SendOnchainPayment {
address,
amount_sat,
} => {
let prepare_res = sdk
.prepare_pay_onchain(&PreparePayOnchainRequest { amount_sat })
.await?;
wait_confirmation!(
format!(
"Fees: {} sat. Are the fees acceptable? (y/N) ",
prepare_res.fees_sat
),
"Payment send halted"
);
let response = sdk
.pay_onchain(&PayOnchainRequest {
address,
prepare_res,
})
.await?;
command_result!(response)
}
Command::GetInfo => { Command::GetInfo => {
command_result!(sdk.get_info().await?) command_result!(sdk.get_info().await?)
} }

176
lib/Cargo.lock generated
View File

@@ -501,7 +501,7 @@ checksum = "829a082bd3761fde7476dc2ed85ca56c11628948460ece621e4f56fef5046567"
[[package]] [[package]]
name = "boltz-client" name = "boltz-client"
version = "0.1.3" version = "0.1.3"
source = "git+https://github.com/hydra-yse/boltz-rust?branch=yse-breez-latest#66cdf65ba889a25a5274af3d27f5f52a2d4e3cc9" source = "git+https://github.com/dangeross/boltz-rust?rev=cb2cb02d44fb81cc8ce5d8000346f52cc26b3fc1#cb2cb02d44fb81cc8ce5d8000346f52cc26b3fc1"
dependencies = [ dependencies = [
"bip39", "bip39",
"bitcoin 0.31.2", "bitcoin 0.31.2",
@@ -527,15 +527,18 @@ dependencies = [
"bip39", "bip39",
"boltz-client", "boltz-client",
"chrono", "chrono",
"electrum-client",
"env_logger 0.11.3", "env_logger 0.11.3",
"flutter_rust_bridge", "flutter_rust_bridge",
"futures-util", "futures-util",
"glob", "glob",
"hex",
"log", "log",
"lwk_common", "lwk_common",
"lwk_signer", "lwk_signer",
"lwk_wollet", "lwk_wollet",
"openssl", "openssl",
"reqwest 0.11.20",
"rusqlite", "rusqlite",
"rusqlite_migration", "rusqlite_migration",
"security-framework", "security-framework",
@@ -1240,6 +1243,25 @@ dependencies = [
"scroll 0.12.0", "scroll 0.12.0",
] ]
[[package]]
name = "h2"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.2.6",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.5" version = "0.4.5"
@@ -1251,7 +1273,7 @@ dependencies = [
"fnv", "fnv",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"http", "http 1.1.0",
"indexmap 2.2.6", "indexmap 2.2.6",
"slab", "slab",
"tokio", "tokio",
@@ -1335,6 +1357,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]] [[package]]
name = "http" name = "http"
version = "1.1.0" version = "1.1.0"
@@ -1346,6 +1379,17 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "1.0.0" version = "1.0.0"
@@ -1353,7 +1397,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http 1.1.0",
] ]
[[package]] [[package]]
@@ -1364,8 +1408,8 @@ checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"http", "http 1.1.0",
"http-body", "http-body 1.0.0",
"pin-project-lite", "pin-project-lite",
] ]
@@ -1375,6 +1419,12 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "1.3.0" version = "1.3.0"
@@ -1390,6 +1440,30 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.3.1" version = "1.3.1"
@@ -1399,9 +1473,9 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"h2", "h2 0.4.5",
"http", "http 1.1.0",
"http-body", "http-body 1.0.0",
"httparse", "httparse",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
@@ -1417,8 +1491,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http", "http 1.1.0",
"hyper", "hyper 1.3.1",
"hyper-util", "hyper-util",
"rustls 0.22.4", "rustls 0.22.4",
"rustls-pki-types", "rustls-pki-types",
@@ -1427,6 +1501,19 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[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.29",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.3" version = "0.1.3"
@@ -1436,9 +1523,9 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"http", "http 1.1.0",
"http-body", "http-body 1.0.0",
"hyper", "hyper 1.3.1",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio", "tokio",
@@ -1659,7 +1746,7 @@ dependencies = [
"hex", "hex",
"lwk_common", "lwk_common",
"rand 0.8.5", "rand 0.8.5",
"reqwest", "reqwest 0.12.4",
"serde", "serde",
"serde_bytes", "serde_bytes",
"serde_cbor", "serde_cbor",
@@ -1700,7 +1787,7 @@ dependencies = [
"once_cell", "once_cell",
"rand 0.8.5", "rand 0.8.5",
"regex-lite", "regex-lite",
"reqwest", "reqwest 0.12.4",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@@ -2229,6 +2316,43 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "reqwest"
version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
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.29",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg 0.50.0",
]
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.4" version = "0.12.4"
@@ -2241,11 +2365,11 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2", "h2 0.4.5",
"http", "http 1.1.0",
"http-body", "http-body 1.0.0",
"http-body-util", "http-body-util",
"hyper", "hyper 1.3.1",
"hyper-rustls", "hyper-rustls",
"hyper-util", "hyper-util",
"ipnet", "ipnet",
@@ -2271,7 +2395,7 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"webpki-roots 0.26.1", "webpki-roots 0.26.1",
"winreg", "winreg 0.52.0",
] ]
[[package]] [[package]]
@@ -3017,7 +3141,7 @@ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
"data-encoding", "data-encoding",
"http", "http 1.1.0",
"httparse", "httparse",
"log", "log",
"native-tls", "native-tls",
@@ -3814,6 +3938,16 @@ version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.52.0" version = "0.52.0"

View File

@@ -35,6 +35,20 @@ typedef struct wire_cst_backup_request {
struct wire_cst_list_prim_u_8_strict *backup_path; struct wire_cst_list_prim_u_8_strict *backup_path;
} wire_cst_backup_request; } wire_cst_backup_request;
typedef struct wire_cst_prepare_pay_onchain_response {
uint64_t amount_sat;
uint64_t fees_sat;
} wire_cst_prepare_pay_onchain_response;
typedef struct wire_cst_pay_onchain_request {
struct wire_cst_list_prim_u_8_strict *address;
struct wire_cst_prepare_pay_onchain_response prepare_res;
} wire_cst_pay_onchain_request;
typedef struct wire_cst_prepare_pay_onchain_request {
uint64_t amount_sat;
} wire_cst_prepare_pay_onchain_request;
typedef struct wire_cst_prepare_receive_request { typedef struct wire_cst_prepare_receive_request {
uint64_t payer_amount_sat; uint64_t payer_amount_sat;
} wire_cst_prepare_receive_request; } wire_cst_prepare_receive_request;
@@ -115,7 +129,8 @@ typedef struct wire_cst_liquid_sdk_event {
typedef struct wire_cst_config { typedef struct wire_cst_config {
struct wire_cst_list_prim_u_8_strict *boltz_url; struct wire_cst_list_prim_u_8_strict *boltz_url;
struct wire_cst_list_prim_u_8_strict *electrum_url; struct wire_cst_list_prim_u_8_strict *liquid_electrum_url;
struct wire_cst_list_prim_u_8_strict *bitcoin_electrum_url;
struct wire_cst_list_prim_u_8_strict *working_dir; struct wire_cst_list_prim_u_8_strict *working_dir;
int32_t network; int32_t network;
uint64_t payment_timeout_sec; uint64_t payment_timeout_sec;
@@ -168,8 +183,13 @@ typedef struct wire_cst_LiquidSdkError_Generic {
struct wire_cst_list_prim_u_8_strict *err; struct wire_cst_list_prim_u_8_strict *err;
} wire_cst_LiquidSdkError_Generic; } wire_cst_LiquidSdkError_Generic;
typedef struct wire_cst_LiquidSdkError_ServiceConnectivity {
struct wire_cst_list_prim_u_8_strict *err;
} wire_cst_LiquidSdkError_ServiceConnectivity;
typedef union LiquidSdkErrorKind { typedef union LiquidSdkErrorKind {
struct wire_cst_LiquidSdkError_Generic Generic; struct wire_cst_LiquidSdkError_Generic Generic;
struct wire_cst_LiquidSdkError_ServiceConnectivity ServiceConnectivity;
} LiquidSdkErrorKind; } LiquidSdkErrorKind;
typedef struct wire_cst_liquid_sdk_error { typedef struct wire_cst_liquid_sdk_error {
@@ -268,6 +288,14 @@ void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_get_info(int64_
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_list_payments(int64_t port_, void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_list_payments(int64_t port_,
uintptr_t that); uintptr_t that);
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_pay_onchain(int64_t port_,
uintptr_t that,
struct wire_cst_pay_onchain_request *req);
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain(int64_t port_,
uintptr_t that,
struct wire_cst_prepare_pay_onchain_request *req);
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment(int64_t port_, void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment(int64_t port_,
uintptr_t that, uintptr_t that,
struct wire_cst_prepare_receive_request *req); struct wire_cst_prepare_receive_request *req);
@@ -316,8 +344,12 @@ struct wire_cst_connect_request *frbgen_breez_liquid_cst_new_box_autoadd_connect
struct wire_cst_liquid_sdk_event *frbgen_breez_liquid_cst_new_box_autoadd_liquid_sdk_event(void); struct wire_cst_liquid_sdk_event *frbgen_breez_liquid_cst_new_box_autoadd_liquid_sdk_event(void);
struct wire_cst_pay_onchain_request *frbgen_breez_liquid_cst_new_box_autoadd_pay_onchain_request(void);
struct wire_cst_payment *frbgen_breez_liquid_cst_new_box_autoadd_payment(void); struct wire_cst_payment *frbgen_breez_liquid_cst_new_box_autoadd_payment(void);
struct wire_cst_prepare_pay_onchain_request *frbgen_breez_liquid_cst_new_box_autoadd_prepare_pay_onchain_request(void);
struct wire_cst_prepare_receive_request *frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request(void); struct wire_cst_prepare_receive_request *frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request(void);
struct wire_cst_prepare_receive_response *frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_response(void); struct wire_cst_prepare_receive_response *frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_response(void);
@@ -343,7 +375,9 @@ static int64_t dummy_method_to_enforce_bundling(void) {
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_binding_event_listener); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_binding_event_listener);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_connect_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_connect_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_liquid_sdk_event); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_liquid_sdk_event);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_pay_onchain_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_payment); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_payment);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_pay_onchain_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_response); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_response);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_send_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_prepare_send_request);
@@ -362,6 +396,8 @@ static int64_t dummy_method_to_enforce_bundling(void) {
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_empty_wallet_cache); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_empty_wallet_cache);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_get_info); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_get_info);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_list_payments); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_list_payments);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_pay_onchain);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_send_payment); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_send_payment);
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_receive_payment); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_receive_payment);

View File

@@ -3,6 +3,7 @@ enum LiquidSdkError {
"AlreadyStarted", "AlreadyStarted",
"Generic", "Generic",
"NotStarted", "NotStarted",
"ServiceConnectivity",
}; };
[Error] [Error]
@@ -29,7 +30,8 @@ enum PaymentError {
dictionary Config { dictionary Config {
string boltz_url; string boltz_url;
string electrum_url; string liquid_electrum_url;
string bitcoin_electrum_url;
string working_dir; string working_dir;
Network network; Network network;
u64 payment_timeout_sec; u64 payment_timeout_sec;
@@ -81,6 +83,20 @@ dictionary ReceivePaymentResponse {
string invoice; string invoice;
}; };
dictionary PreparePayOnchainRequest {
u64 amount_sat;
};
dictionary PreparePayOnchainResponse {
u64 amount_sat;
u64 fees_sat;
};
dictionary PayOnchainRequest {
string address;
PreparePayOnchainResponse prepare_res;
};
dictionary BackupRequest { dictionary BackupRequest {
string? backup_path = null; string? backup_path = null;
}; };
@@ -204,6 +220,12 @@ interface BindingLiquidSdk {
[Throws=PaymentError] [Throws=PaymentError]
ReceivePaymentResponse receive_payment(PrepareReceiveResponse req); ReceivePaymentResponse receive_payment(PrepareReceiveResponse req);
[Throws=PaymentError]
PreparePayOnchainResponse prepare_pay_onchain(PreparePayOnchainRequest req);
[Throws=PaymentError]
SendPaymentResponse pay_onchain(PayOnchainRequest req);
[Throws=PaymentError] [Throws=PaymentError]
sequence<Payment> list_payments(); sequence<Payment> list_payments();

View File

@@ -111,6 +111,17 @@ impl BindingLiquidSdk {
rt().block_on(self.sdk.receive_payment(&req)) rt().block_on(self.sdk.receive_payment(&req))
} }
pub fn prepare_pay_onchain(
&self,
req: PreparePayOnchainRequest,
) -> Result<PreparePayOnchainResponse, PaymentError> {
rt().block_on(self.sdk.prepare_pay_onchain(&req))
}
pub fn pay_onchain(&self, req: PayOnchainRequest) -> Result<SendPaymentResponse, PaymentError> {
rt().block_on(self.sdk.pay_onchain(&req))
}
pub fn list_payments(&self) -> Result<Vec<Payment>, PaymentError> { pub fn list_payments(&self) -> Result<Vec<Payment>, PaymentError> {
rt().block_on(self.sdk.list_payments()) rt().block_on(self.sdk.list_payments())
} }

View File

@@ -15,7 +15,7 @@ frb = ["dep:flutter_rust_bridge"]
anyhow = { workspace = true } anyhow = { workspace = true }
bip39 = { version = "2.0.0", features = ["serde"] } bip39 = { version = "2.0.0", features = ["serde"] }
#boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "a05731cc33030ada9ae14afcafe0cded22842ba6" } #boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", rev = "a05731cc33030ada9ae14afcafe0cded22842ba6" }
boltz-client = { git = "https://github.com/hydra-yse/boltz-rust", branch = "yse-breez-latest" } boltz-client = { git = "https://github.com/dangeross/boltz-rust", rev = "cb2cb02d44fb81cc8ce5d8000346f52cc26b3fc1" }
chrono = "0.4" chrono = "0.4"
env_logger = "0.11" env_logger = "0.11"
flutter_rust_bridge = { version = "=2.0.0-dev.38", features = ["chrono"], optional = true } flutter_rust_bridge = { version = "=2.0.0-dev.38", features = ["chrono"], optional = true }
@@ -37,6 +37,9 @@ tokio-stream = { version = "0.1.14", features = ["sync"] }
url = "2.5.0" url = "2.5.0"
futures-util = { version = "0.3.28", default-features = false, features = ["sink", "std"] } futures-util = { version = "0.3.28", default-features = false, features = ["sink", "std"] }
async-trait = "0.1.80" async-trait = "0.1.80"
hex = "0.4"
reqwest = { version = "=0.11.20", features = ["json"] }
electrum-client = { version = "0.19.0" }
[dev-dependencies] [dev-dependencies]
tempdir = "0.3.7" tempdir = "0.3.7"

View File

@@ -115,6 +115,20 @@ impl BindingLiquidSdk {
self.sdk.receive_payment(&req).await self.sdk.receive_payment(&req).await
} }
pub async fn prepare_pay_onchain(
&self,
req: PreparePayOnchainRequest,
) -> Result<PreparePayOnchainResponse, PaymentError> {
self.sdk.prepare_pay_onchain(&req).await
}
pub async fn pay_onchain(
&self,
req: PayOnchainRequest,
) -> Result<SendPaymentResponse, PaymentError> {
self.sdk.pay_onchain(&req).await
}
pub async fn list_payments(&self) -> Result<Vec<Payment>, PaymentError> { pub async fn list_payments(&self) -> Result<Vec<Payment>, PaymentError> {
self.sdk.list_payments().await self.sdk.list_payments().await
} }

View File

@@ -0,0 +1,116 @@
use std::collections::HashMap;
use anyhow::Result;
use electrum_client::{Client, ElectrumApi, HeaderNotification};
use lwk_wollet::{
bitcoin::{
self,
block::Header,
consensus::{deserialize, serialize},
BlockHash, Script, Transaction, Txid,
},
ElectrumUrl, History,
};
type Height = u32;
/// Trait implemented by types that can fetch data from a blockchain data source.
#[allow(dead_code)]
pub trait BitcoinChainService: Send + Sync {
/// Get the blockchain latest block
fn tip(&mut 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 a list of block headers
///
/// Optionally pass the blockhash if already known
fn get_headers(
&self,
heights: &[Height],
height_blockhash: &HashMap<Height, BlockHash>,
) -> Result<Vec<Header>>;
/// Get the transactions involved in a list of scripts
fn get_scripts_history(&self, scripts: &[&Script]) -> Result<Vec<Vec<History>>>;
}
pub(crate) struct ElectrumClient {
client: Client,
tip: HeaderNotification,
}
impl ElectrumClient {
pub fn new(url: &ElectrumUrl) -> Result<Self> {
let client = url.build_client()?;
let header = client.block_headers_subscribe_raw()?;
let tip: HeaderNotification = header.try_into()?;
Ok(Self { client, tip })
}
}
impl BitcoinChainService for ElectrumClient {
fn tip(&mut self) -> Result<HeaderNotification> {
let mut maybe_popped_header = None;
while let Some(header) = self.client.block_headers_pop_raw()? {
maybe_popped_header = Some(header)
}
if let Some(popped_header) = maybe_popped_header {
let tip: HeaderNotification = popped_header.try_into()?;
self.tip = tip;
}
Ok(self.tip.clone())
}
fn broadcast(&self, tx: &Transaction) -> Result<Txid> {
let txid = self.client.transaction_broadcast_raw(&serialize(tx))?;
Ok(Txid::from_raw_hash(txid.to_raw_hash()))
}
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.client.batch_transaction_get_raw(&txids)? {
let tx: Transaction = deserialize(&tx)?;
result.push(tx);
}
Ok(result)
}
fn get_headers(
&self,
heights: &[Height],
_: &HashMap<Height, BlockHash>,
) -> Result<Vec<Header>> {
let mut result = vec![];
for header in self.client.batch_block_header_raw(heights)? {
let header: Header = deserialize(&header)?;
result.push(header);
}
Ok(result)
}
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
.client
.batch_script_get_history(&scripts)?
.into_iter()
.map(|e| e.into_iter().map(Into::into).collect())
.collect())
}
}

View File

@@ -0,0 +1,6 @@
pub(crate) mod bitcoin;
use lwk_wollet::{BlockchainBackend, ElectrumClient};
pub(crate) trait ChainService: Send + Sync + BlockchainBackend {}
impl ChainService for ElectrumClient {}

568
lib/core/src/chain_swap.rs Normal file
View File

@@ -0,0 +1,568 @@
use std::{str::FromStr, sync::Arc};
use anyhow::{anyhow, Result};
use boltz_client::swaps::boltzv2;
use boltz_client::swaps::{boltz::ChainSwapStates, boltzv2::CreateChainResponse};
use log::{debug, error, info, warn};
use lwk_wollet::elements::Transaction;
use lwk_wollet::ElectrumUrl;
use tokio::sync::{broadcast, Mutex};
use crate::chain::bitcoin::{BitcoinChainService, ElectrumClient};
use crate::chain::ChainService;
use crate::model::PaymentState::{Complete, Created, Failed, Pending, TimedOut};
use crate::model::{ChainSwap, Config, Direction, PaymentTxData, PaymentType};
use crate::swapper::Swapper;
use crate::wallet::OnchainWallet;
use crate::{error::PaymentError, model::PaymentState, persist::Persister};
pub(crate) struct ChainSwapStateHandler {
onchain_wallet: Arc<dyn OnchainWallet>,
persister: Arc<Persister>,
swapper: Arc<dyn Swapper>,
liquid_chain_service: Arc<Mutex<dyn ChainService>>,
bitcoin_chain_service: Arc<Mutex<dyn BitcoinChainService>>,
subscription_notifier: broadcast::Sender<String>,
}
impl ChainSwapStateHandler {
pub(crate) fn new(
config: Config,
onchain_wallet: Arc<dyn OnchainWallet>,
persister: Arc<Persister>,
swapper: Arc<dyn Swapper>,
liquid_chain_service: Arc<Mutex<dyn ChainService>>,
) -> Result<Self> {
let (subscription_notifier, _) = broadcast::channel::<String>(30);
let bitcoin_chain_service = Arc::new(Mutex::new(ElectrumClient::new(&ElectrumUrl::new(
&config.bitcoin_electrum_url,
true,
true,
))?));
Ok(Self {
onchain_wallet,
persister,
swapper,
liquid_chain_service,
bitcoin_chain_service,
subscription_notifier,
})
}
pub(crate) fn subscribe_payment_updates(&self) -> broadcast::Receiver<String> {
self.subscription_notifier.subscribe()
}
/// Handles status updates from Boltz for Chain swaps
pub(crate) async fn on_new_status(&self, update: &boltzv2::Update) -> Result<()> {
let id = &update.id;
let swap = self
.persister
.fetch_chain_swap_by_id(id)?
.ok_or(anyhow!("No ongoing Chain Swap found for ID {id}"))?;
match swap.direction {
Direction::Incoming => self.on_new_incoming_status(&swap, update).await,
Direction::Outgoing => self.on_new_outgoing_status(&swap, update).await,
}
}
async fn on_new_incoming_status(
&self,
swap: &ChainSwap,
update: &boltzv2::Update,
) -> Result<()> {
let id = &update.id;
let status = &update.status;
let swap_state = ChainSwapStates::from_str(status)
.map_err(|_| anyhow!("Invalid ChainSwapState for Chain Swap {id}: {status}"))?;
info!("Handling incoming Chain Swap transition to {status:?} for swap {id}");
// See https://docs.boltz.exchange/v/api/lifecycle#chain-swaps
match swap_state {
// Boltz announced the user lockup tx is in the mempool or has been confirmed.
ChainSwapStates::TransactionMempool | ChainSwapStates::TransactionConfirmed => {
if let Some(zero_conf_rejected) = update.zero_conf_rejected {
info!("Is zero conf rejected for Chain Swap {id}: {zero_conf_rejected}");
self.persister
.update_chain_swap_accept_zero_conf(id, !zero_conf_rejected)?;
}
if let Some(transaction) = update.transaction.clone() {
self.update_swap_info(id, Pending, None, Some(&transaction.id), None, None)
.await?;
}
Ok(())
}
// Boltz announced the server lockup tx is in the mempool or has been confirmed.
// If it's a zero conf swap or confirmed, proceed to cooperative claim
ChainSwapStates::TransactionServerMempool
| ChainSwapStates::TransactionServerConfirmed => {
match swap.claim_tx_id.clone() {
None => match (swap.accept_zero_conf, swap_state) {
(true, _) | (_, ChainSwapStates::TransactionServerConfirmed) => {
if let Some(transaction) = update.transaction.clone() {
self.update_swap_info(
id,
Pending,
Some(&transaction.id),
None,
None,
None,
)
.await?;
}
self.claim(swap).await.map_err(|e| {
error!("Could not cooperate Chain Swap {id} claim: {e}");
anyhow!("Could not post claim details. Err: {e:?}")
})?;
}
_ => info!("Waiting for server lockup confirmation for Chain Swap {id}"),
},
Some(claim_tx_id) => {
warn!("Claim tx for Chain Swap {id} was already broadcast: txid {claim_tx_id}")
}
};
Ok(())
}
// If swap state is unrecoverable, either:
// 1. The transaction failed
// 2. Lockup failed (too little funds were sent)
// 3. The claim lockup was refunded
// 4. The swap has expired (>24h)
// We initiate a cooperative refund, and then fallback to a regular one
ChainSwapStates::TransactionFailed
| ChainSwapStates::TransactionLockupFailed
| ChainSwapStates::TransactionRefunded
| ChainSwapStates::SwapExpired => {
match swap.refund_tx_id.clone() {
None => {
warn!("Chain Swap {id} is in an unrecoverable state: {swap_state:?}");
match swap.user_lockup_tx_id.clone() {
// If there is a lockup tx when receiving we need to refund to a sender address
// TODO: Set the chain swap to refundable
Some(_) => {}
// No user lockup tx was broadcast when sending or receiving
None => {
warn!("Chain Swap {id} user lockup tx was never broadcast. Resolving payment as failed.");
self.update_swap_info(id, Failed, None, None, None, None)
.await?;
}
}
}
Some(refund_tx_id) => warn!(
"Refund tx for Chain Swap {id} was already broadcast: txid {refund_tx_id}"
),
};
Ok(())
}
_ => {
debug!("Unhandled state for Chain Swap {id}: {swap_state:?}");
Ok(())
}
}
}
async fn on_new_outgoing_status(
&self,
swap: &ChainSwap,
update: &boltzv2::Update,
) -> Result<()> {
let id = &update.id;
let status = &update.status;
let swap_state = ChainSwapStates::from_str(status)
.map_err(|_| anyhow!("Invalid ChainSwapState for Chain Swap {id}: {status}"))?;
info!("Handling outgoing Chain Swap transition to {status:?} for swap {id}");
// See https://docs.boltz.exchange/v/api/lifecycle#chain-swaps
match swap_state {
// The swap is created
ChainSwapStates::Created => {
match swap.user_lockup_tx_id.clone() {
// Create the user lockup tx when sending
None => {
let create_response = swap.get_boltz_create_response()?;
let user_lockup_tx = self.lockup_funds(id, &create_response).await?;
let lockup_tx_id = user_lockup_tx.txid().to_string();
let lockup_tx_fees_sat: u64 = user_lockup_tx.all_fees().values().sum();
// We insert a pseudo-lockup-tx in case LWK fails to pick up the new mempool tx for a while
// This makes the tx known to the SDK (get_info, list_payments) instantly
self.persister.insert_or_update_payment(PaymentTxData {
tx_id: lockup_tx_id.clone(),
timestamp: None,
amount_sat: swap.receiver_amount_sat,
// This should be: boltz fee + lockup fee + claim fee
fees_sat: lockup_tx_fees_sat + swap.claim_fees_sat,
payment_type: PaymentType::Send,
is_confirmed: false,
})?;
self.update_swap_info(id, Pending, None, Some(&lockup_tx_id), None, None)
.await?;
},
// Lockup tx already exists when sending
Some(lockup_tx_id) => warn!("User lockup tx for Chain Swap {id} was already broadcast: txid {lockup_tx_id}"),
};
Ok(())
}
// Boltz announced the user lockup tx is in the mempool or has been confirmed.
ChainSwapStates::TransactionMempool | ChainSwapStates::TransactionConfirmed => {
if let Some(zero_conf_rejected) = update.zero_conf_rejected {
info!("Is zero conf rejected for Chain Swap {id}: {zero_conf_rejected}");
self.persister
.update_chain_swap_accept_zero_conf(id, !zero_conf_rejected)?;
}
if let Some(transaction) = update.transaction.clone() {
self.update_swap_info(id, Pending, None, Some(&transaction.id), None, None)
.await?;
}
Ok(())
}
// Boltz announced the server lockup tx is in the mempool or has been confirmed.
// If it's a zero conf swap or confirmed, proceed to cooperative claim
ChainSwapStates::TransactionServerMempool
| ChainSwapStates::TransactionServerConfirmed => {
match swap.claim_tx_id.clone() {
None => match (swap.accept_zero_conf, swap_state) {
(true, _) | (_, ChainSwapStates::TransactionServerConfirmed) => {
if let Some(transaction) = update.transaction.clone() {
self.update_swap_info(
id,
Pending,
Some(&transaction.id),
None,
None,
None,
)
.await?;
}
self.claim(swap).await.map_err(|e| {
error!("Could not cooperate Chain Swap {id} claim: {e}");
anyhow!("Could not post claim details. Err: {e:?}")
})?;
}
_ => info!("Waiting for server lockup confirmation for Chain Swap {id}"),
},
Some(claim_tx_id) => {
warn!("Claim tx for Chain Swap {id} was already broadcast: txid {claim_tx_id}")
}
};
Ok(())
}
// If swap state is unrecoverable, either:
// 1. The transaction failed
// 2. Lockup failed (too little funds were sent)
// 3. The claim lockup was refunded
// 4. The swap has expired (>24h)
// We initiate a cooperative refund, and then fallback to a regular one
ChainSwapStates::TransactionFailed
| ChainSwapStates::TransactionLockupFailed
| ChainSwapStates::TransactionRefunded
| ChainSwapStates::SwapExpired => {
match swap.refund_tx_id.clone() {
None => {
warn!("Chain Swap {id} is in an unrecoverable state: {swap_state:?}");
match swap.user_lockup_tx_id.clone() {
Some(_) => {
warn!("Chain Swap {id} user lockup tx has been broadcast. Attempting refund.");
let refund_tx_id = self.refund(swap).await?;
info!("Broadcast refund tx for Chain Swap {id}. Tx id: {refund_tx_id}");
self.update_swap_info(
id,
Pending,
None,
None,
None,
Some(&refund_tx_id),
)
.await?;
}
None => {
warn!("Chain Swap {id} user lockup tx was never broadcast. Resolving payment as failed.");
self.update_swap_info(id, Failed, None, None, None, None)
.await?;
}
}
}
Some(refund_tx_id) => warn!(
"Refund tx for Chain Swap {id} was already broadcast: txid {refund_tx_id}"
),
};
Ok(())
}
_ => {
debug!("Unhandled state for Chain Swap {id}: {swap_state:?}");
Ok(())
}
}
}
async fn lockup_funds(
&self,
swap_id: &str,
create_response: &CreateChainResponse,
) -> Result<Transaction, PaymentError> {
let lockup_details = create_response.lockup_details.clone();
debug!(
"Initiated Chain Swap: send {} sats to liquid address {}",
lockup_details.amount, lockup_details.lockup_address
);
let lockup_tx = self
.onchain_wallet
.build_tx(
None,
&lockup_details.lockup_address,
lockup_details.amount as u64,
)
.await?;
let lockup_tx_id = self
.liquid_chain_service
.lock()
.await
.broadcast(&lockup_tx)?
.to_string();
debug!(
"Successfully broadcast lockup transaction for Chain Swap {swap_id}. Lockup tx id: {lockup_tx_id}"
);
Ok(lockup_tx)
}
/// Transitions a Chain swap to a new state
pub(crate) async fn update_swap_info(
&self,
swap_id: &str,
to_state: PaymentState,
server_lockup_tx_id: Option<&str>,
user_lockup_tx_id: Option<&str>,
claim_tx_id: Option<&str>,
refund_tx_id: Option<&str>,
) -> Result<(), PaymentError> {
info!("Transitioning Chain swap {swap_id} to {to_state:?} (server_lockup_tx_id = {:?}, user_lockup_tx_id = {:?}, claim_tx_id = {:?}), refund_tx_id = {:?})", server_lockup_tx_id, user_lockup_tx_id, claim_tx_id, refund_tx_id);
let swap: ChainSwap = self
.persister
.fetch_chain_swap_by_id(swap_id)
.map_err(|_| PaymentError::PersistError)?
.ok_or(PaymentError::Generic {
err: format!("Chain Swap not found {swap_id}"),
})?;
let payment_id = user_lockup_tx_id
.map(|c| c.to_string())
.or(swap.user_lockup_tx_id);
Self::validate_state_transition(swap.state, to_state)?;
self.persister.try_handle_chain_swap_update(
swap_id,
to_state,
server_lockup_tx_id,
user_lockup_tx_id,
claim_tx_id,
refund_tx_id,
)?;
if let Some(payment_id) = payment_id {
let _ = self.subscription_notifier.send(payment_id);
}
Ok(())
}
async fn claim(&self, chain_swap: &ChainSwap) -> Result<(), PaymentError> {
debug!("Initiating claim for Chain Swap {}", &chain_swap.id);
let refund_address = self.onchain_wallet.next_unused_address().await?.to_string();
let claim_tx_id = self.swapper.claim_chain_swap(chain_swap, refund_address)?;
if chain_swap.direction == Direction::Incoming {
// We insert a pseudo-claim-tx in case LWK fails to pick up the new mempool tx for a while
// This makes the tx known to the SDK (get_info, list_payments) instantly
self.persister.insert_or_update_payment(PaymentTxData {
tx_id: claim_tx_id.clone(),
timestamp: None,
amount_sat: chain_swap.receiver_amount_sat,
fees_sat: 0,
payment_type: PaymentType::Receive,
is_confirmed: false,
})?;
}
self.update_swap_info(
&chain_swap.id,
Complete,
None,
None,
Some(&claim_tx_id),
None,
)
.await?;
Ok(())
}
async fn refund(&self, swap: &ChainSwap) -> Result<String, PaymentError> {
let amount_sat = swap.receiver_amount_sat;
let output_address = self.onchain_wallet.next_unused_address().await?.to_string();
let fee = self
.onchain_wallet
.build_tx(None, &output_address, amount_sat)
.await?
.all_fees()
.values()
.sum();
let refund_res = self
.swapper
.refund_chain_swap_cooperative(swap, &output_address, fee);
match refund_res {
Ok(res) => Ok(res),
Err(e) => {
warn!("Cooperative refund failed: {:?}", e);
self.refund_non_cooperative(swap, fee).await
}
}
}
async fn refund_non_cooperative(
&self,
swap: &ChainSwap,
broadcast_fees_sat: u64,
) -> Result<String, PaymentError> {
info!(
"Initiating non-cooperative refund for Chain Swap {}",
&swap.id
);
let current_height = match swap.direction {
Direction::Incoming => self.bitcoin_chain_service.lock().await.tip()?.height as u32,
Direction::Outgoing => self.liquid_chain_service.lock().await.tip()?.height,
};
let output_address = self.onchain_wallet.next_unused_address().await?.to_string();
let refund_tx_id = self.swapper.refund_chain_swap_non_cooperative(
swap,
broadcast_fees_sat,
&output_address,
current_height,
)?;
info!(
"Successfully broadcast non-cooperative refund for Chain Swap {}, tx: {}",
swap.id, refund_tx_id
);
Ok(refund_tx_id)
}
fn validate_state_transition(
from_state: PaymentState,
to_state: PaymentState,
) -> Result<(), PaymentError> {
match (from_state, to_state) {
(_, Created) => Err(PaymentError::Generic {
err: "Cannot transition to Created state".to_string(),
}),
(Created | Pending, Pending) => Ok(()),
(Complete | Failed | TimedOut, Pending) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to Pending state"),
}),
(Created | Pending, Complete) => Ok(()),
(Complete | Failed | TimedOut, Complete) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to Complete state"),
}),
(Created, TimedOut) => Ok(()),
(_, TimedOut) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to TimedOut state"),
}),
(Complete, Failed) => Err(PaymentError::Generic {
err: format!("Cannot transition from {from_state:?} to Failed state"),
}),
(_, Failed) => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use anyhow::Result;
use crate::{
model::PaymentState::{self, *},
test_utils::{new_chain_swap, new_chain_swap_state_handler, new_persister},
};
#[tokio::test]
async fn test_chain_swap_state_transitions() -> Result<()> {
let (_temp_dir, storage) = new_persister()?;
let storage = Arc::new(storage);
let chain_swap_state_handler = new_chain_swap_state_handler(storage.clone())?;
// Test valid combinations of states
let all_states = HashSet::from([Created, Pending, Complete, TimedOut, Failed]);
let valid_combinations = HashMap::from([
(
Created,
HashSet::from([Pending, Complete, TimedOut, Failed]),
),
(Pending, HashSet::from([Pending, Complete, Failed])),
(TimedOut, HashSet::from([Failed])),
(Complete, HashSet::from([])),
(Failed, HashSet::from([Failed])),
]);
for (first_state, allowed_states) in valid_combinations.iter() {
for allowed_state in allowed_states {
let chain_swap = new_chain_swap(Some(*first_state));
storage.insert_chain_swap(&chain_swap)?;
assert!(chain_swap_state_handler
.update_swap_info(&chain_swap.id, *allowed_state, None, None, None, None)
.await
.is_ok());
}
}
// Test invalid combinations of states
let invalid_combinations: HashMap<PaymentState, HashSet<PaymentState>> = valid_combinations
.iter()
.map(|(first_state, allowed_states)| {
(
*first_state,
all_states.difference(allowed_states).cloned().collect(),
)
})
.collect();
for (first_state, disallowed_states) in invalid_combinations.iter() {
for disallowed_state in disallowed_states {
let chain_swap = new_chain_swap(Some(*first_state));
storage.insert_chain_swap(&chain_swap)?;
assert!(chain_swap_state_handler
.update_swap_info(&chain_swap.id, *disallowed_state, None, None, None, None)
.await
.is_err());
}
}
Ok(())
}
}

View File

@@ -22,6 +22,9 @@ pub enum LiquidSdkError {
#[error("Liquid SDK instance is not running")] #[error("Liquid SDK instance is not running")]
NotStarted, NotStarted,
#[error("Service connectivity: {err}")]
ServiceConnectivity { err: String },
} }
impl From<anyhow::Error> for LiquidSdkError { impl From<anyhow::Error> for LiquidSdkError {
@@ -41,7 +44,7 @@ pub enum PaymentError {
#[error("The payment is already in progress")] #[error("The payment is already in progress")]
PaymentInProgress, PaymentInProgress,
#[error("Invoice amount is out of range")] #[error("Amount is out of range")]
AmountOutOfRange, AmountOutOfRange,
#[error("Generic error: {err}")] #[error("Generic error: {err}")]
@@ -107,24 +110,26 @@ impl From<boltz_client::error::Error> for PaymentError {
} }
} }
#[allow(clippy::match_single_binding)] impl From<boltz_client::bitcoin::hex::HexToArrayError> for PaymentError {
impl From<lwk_wollet::Error> for PaymentError { fn from(err: boltz_client::bitcoin::hex::HexToArrayError) -> Self {
fn from(err: lwk_wollet::Error) -> Self { PaymentError::Generic {
match err { err: format!("{err:?}"),
_ => PaymentError::LwkError { }
err: format!("{err:?}"), }
}, }
impl From<lwk_wollet::Error> for PaymentError {
fn from(err: lwk_wollet::Error) -> Self {
PaymentError::LwkError {
err: format!("{err:?}"),
} }
} }
} }
#[allow(clippy::match_single_binding)]
impl From<lwk_signer::SignerError> for PaymentError { impl From<lwk_signer::SignerError> for PaymentError {
fn from(err: lwk_signer::SignerError) -> Self { fn from(err: lwk_signer::SignerError) -> Self {
match err { PaymentError::SignerError {
_ => PaymentError::SignerError { err: format!("{err:?}"),
err: format!("{err:?}"),
},
} }
} }
} }

View File

@@ -125,6 +125,13 @@ impl CstDecode<crate::model::LiquidSdkEvent> for *mut wire_cst_liquid_sdk_event
CstDecode::<crate::model::LiquidSdkEvent>::cst_decode(*wrap).into() CstDecode::<crate::model::LiquidSdkEvent>::cst_decode(*wrap).into()
} }
} }
impl CstDecode<crate::model::PayOnchainRequest> for *mut wire_cst_pay_onchain_request {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> crate::model::PayOnchainRequest {
let wrap = unsafe { flutter_rust_bridge::for_generated::box_from_leak_ptr(self) };
CstDecode::<crate::model::PayOnchainRequest>::cst_decode(*wrap).into()
}
}
impl CstDecode<crate::model::Payment> for *mut wire_cst_payment { impl CstDecode<crate::model::Payment> for *mut wire_cst_payment {
// 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::Payment { fn cst_decode(self) -> crate::model::Payment {
@@ -132,6 +139,15 @@ impl CstDecode<crate::model::Payment> for *mut wire_cst_payment {
CstDecode::<crate::model::Payment>::cst_decode(*wrap).into() CstDecode::<crate::model::Payment>::cst_decode(*wrap).into()
} }
} }
impl CstDecode<crate::model::PreparePayOnchainRequest>
for *mut wire_cst_prepare_pay_onchain_request
{
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> crate::model::PreparePayOnchainRequest {
let wrap = unsafe { flutter_rust_bridge::for_generated::box_from_leak_ptr(self) };
CstDecode::<crate::model::PreparePayOnchainRequest>::cst_decode(*wrap).into()
}
}
impl CstDecode<crate::model::PrepareReceiveRequest> for *mut wire_cst_prepare_receive_request { impl CstDecode<crate::model::PrepareReceiveRequest> for *mut wire_cst_prepare_receive_request {
// 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::PrepareReceiveRequest { fn cst_decode(self) -> crate::model::PrepareReceiveRequest {
@@ -178,7 +194,8 @@ impl CstDecode<crate::model::Config> for wire_cst_config {
fn cst_decode(self) -> crate::model::Config { fn cst_decode(self) -> crate::model::Config {
crate::model::Config { crate::model::Config {
boltz_url: self.boltz_url.cst_decode(), boltz_url: self.boltz_url.cst_decode(),
electrum_url: self.electrum_url.cst_decode(), liquid_electrum_url: self.liquid_electrum_url.cst_decode(),
bitcoin_electrum_url: self.bitcoin_electrum_url.cst_decode(),
working_dir: self.working_dir.cst_decode(), working_dir: self.working_dir.cst_decode(),
network: self.network.cst_decode(), network: self.network.cst_decode(),
payment_timeout_sec: self.payment_timeout_sec.cst_decode(), payment_timeout_sec: self.payment_timeout_sec.cst_decode(),
@@ -219,6 +236,12 @@ impl CstDecode<crate::error::LiquidSdkError> for wire_cst_liquid_sdk_error {
} }
} }
2 => crate::error::LiquidSdkError::NotStarted, 2 => crate::error::LiquidSdkError::NotStarted,
3 => {
let ans = unsafe { self.kind.ServiceConnectivity };
crate::error::LiquidSdkError::ServiceConnectivity {
err: ans.err.cst_decode(),
}
}
_ => unreachable!(), _ => unreachable!(),
} }
} }
@@ -335,6 +358,15 @@ impl CstDecode<crate::model::LogEntry> for wire_cst_log_entry {
} }
} }
} }
impl CstDecode<crate::model::PayOnchainRequest> for wire_cst_pay_onchain_request {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> crate::model::PayOnchainRequest {
crate::model::PayOnchainRequest {
address: self.address.cst_decode(),
prepare_res: self.prepare_res.cst_decode(),
}
}
}
impl CstDecode<crate::model::Payment> for wire_cst_payment { impl CstDecode<crate::model::Payment> for wire_cst_payment {
// 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::Payment { fn cst_decode(self) -> crate::model::Payment {
@@ -415,6 +447,23 @@ impl CstDecode<crate::error::PaymentError> for wire_cst_payment_error {
} }
} }
} }
impl CstDecode<crate::model::PreparePayOnchainRequest> for wire_cst_prepare_pay_onchain_request {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> crate::model::PreparePayOnchainRequest {
crate::model::PreparePayOnchainRequest {
amount_sat: self.amount_sat.cst_decode(),
}
}
}
impl CstDecode<crate::model::PreparePayOnchainResponse> for wire_cst_prepare_pay_onchain_response {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> crate::model::PreparePayOnchainResponse {
crate::model::PreparePayOnchainResponse {
amount_sat: self.amount_sat.cst_decode(),
fees_sat: self.fees_sat.cst_decode(),
}
}
}
impl CstDecode<crate::model::PrepareReceiveRequest> for wire_cst_prepare_receive_request { impl CstDecode<crate::model::PrepareReceiveRequest> for wire_cst_prepare_receive_request {
// 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::PrepareReceiveRequest { fn cst_decode(self) -> crate::model::PrepareReceiveRequest {
@@ -524,7 +573,8 @@ impl NewWithNullPtr for wire_cst_config {
fn new_with_null_ptr() -> Self { fn new_with_null_ptr() -> Self {
Self { Self {
boltz_url: core::ptr::null_mut(), boltz_url: core::ptr::null_mut(),
electrum_url: core::ptr::null_mut(), liquid_electrum_url: core::ptr::null_mut(),
bitcoin_electrum_url: core::ptr::null_mut(),
working_dir: core::ptr::null_mut(), working_dir: core::ptr::null_mut(),
network: Default::default(), network: Default::default(),
payment_timeout_sec: Default::default(), payment_timeout_sec: Default::default(),
@@ -628,6 +678,19 @@ impl Default for wire_cst_log_entry {
Self::new_with_null_ptr() Self::new_with_null_ptr()
} }
} }
impl NewWithNullPtr for wire_cst_pay_onchain_request {
fn new_with_null_ptr() -> Self {
Self {
address: core::ptr::null_mut(),
prepare_res: Default::default(),
}
}
}
impl Default for wire_cst_pay_onchain_request {
fn default() -> Self {
Self::new_with_null_ptr()
}
}
impl NewWithNullPtr for wire_cst_payment { impl NewWithNullPtr for wire_cst_payment {
fn new_with_null_ptr() -> Self { fn new_with_null_ptr() -> Self {
Self { Self {
@@ -663,6 +726,31 @@ impl Default for wire_cst_payment_error {
Self::new_with_null_ptr() Self::new_with_null_ptr()
} }
} }
impl NewWithNullPtr for wire_cst_prepare_pay_onchain_request {
fn new_with_null_ptr() -> Self {
Self {
amount_sat: Default::default(),
}
}
}
impl Default for wire_cst_prepare_pay_onchain_request {
fn default() -> Self {
Self::new_with_null_ptr()
}
}
impl NewWithNullPtr for wire_cst_prepare_pay_onchain_response {
fn new_with_null_ptr() -> Self {
Self {
amount_sat: Default::default(),
fees_sat: Default::default(),
}
}
}
impl Default for wire_cst_prepare_pay_onchain_response {
fn default() -> Self {
Self::new_with_null_ptr()
}
}
impl NewWithNullPtr for wire_cst_prepare_receive_request { impl NewWithNullPtr for wire_cst_prepare_receive_request {
fn new_with_null_ptr() -> Self { fn new_with_null_ptr() -> Self {
Self { Self {
@@ -829,6 +917,24 @@ pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_li
wire__crate__bindings__BindingLiquidSdk_list_payments_impl(port_, that) wire__crate__bindings__BindingLiquidSdk_list_payments_impl(port_, that)
} }
#[no_mangle]
pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_pay_onchain(
port_: i64,
that: usize,
req: *mut wire_cst_pay_onchain_request,
) {
wire__crate__bindings__BindingLiquidSdk_pay_onchain_impl(port_, that, req)
}
#[no_mangle]
pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain(
port_: i64,
that: usize,
req: *mut wire_cst_prepare_pay_onchain_request,
) {
wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain_impl(port_, that, req)
}
#[no_mangle] #[no_mangle]
pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment( pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment(
port_: i64, port_: i64,
@@ -970,11 +1076,27 @@ pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_liquid_sdk_event(
) )
} }
#[no_mangle]
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_pay_onchain_request(
) -> *mut wire_cst_pay_onchain_request {
flutter_rust_bridge::for_generated::new_leak_box_ptr(
wire_cst_pay_onchain_request::new_with_null_ptr(),
)
}
#[no_mangle] #[no_mangle]
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_payment() -> *mut wire_cst_payment { pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_payment() -> *mut wire_cst_payment {
flutter_rust_bridge::for_generated::new_leak_box_ptr(wire_cst_payment::new_with_null_ptr()) flutter_rust_bridge::for_generated::new_leak_box_ptr(wire_cst_payment::new_with_null_ptr())
} }
#[no_mangle]
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_prepare_pay_onchain_request(
) -> *mut wire_cst_prepare_pay_onchain_request {
flutter_rust_bridge::for_generated::new_leak_box_ptr(
wire_cst_prepare_pay_onchain_request::new_with_null_ptr(),
)
}
#[no_mangle] #[no_mangle]
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request( pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request(
) -> *mut wire_cst_prepare_receive_request { ) -> *mut wire_cst_prepare_receive_request {
@@ -1085,7 +1207,8 @@ pub struct wire_cst_binding_event_listener {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct wire_cst_config { pub struct wire_cst_config {
boltz_url: *mut wire_cst_list_prim_u_8_strict, boltz_url: *mut wire_cst_list_prim_u_8_strict,
electrum_url: *mut wire_cst_list_prim_u_8_strict, liquid_electrum_url: *mut wire_cst_list_prim_u_8_strict,
bitcoin_electrum_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,
network: i32, network: i32,
payment_timeout_sec: u64, payment_timeout_sec: u64,
@@ -1116,6 +1239,7 @@ pub struct wire_cst_liquid_sdk_error {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub union LiquidSdkErrorKind { pub union LiquidSdkErrorKind {
Generic: wire_cst_LiquidSdkError_Generic, Generic: wire_cst_LiquidSdkError_Generic,
ServiceConnectivity: wire_cst_LiquidSdkError_ServiceConnectivity,
nil__: (), nil__: (),
} }
#[repr(C)] #[repr(C)]
@@ -1125,6 +1249,11 @@ pub struct wire_cst_LiquidSdkError_Generic {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct wire_cst_LiquidSdkError_ServiceConnectivity {
err: *mut wire_cst_list_prim_u_8_strict,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_liquid_sdk_event { pub struct wire_cst_liquid_sdk_event {
tag: i32, tag: i32,
kind: LiquidSdkEventKind, kind: LiquidSdkEventKind,
@@ -1218,6 +1347,12 @@ pub struct wire_cst_log_entry {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct wire_cst_pay_onchain_request {
address: *mut wire_cst_list_prim_u_8_strict,
prepare_res: wire_cst_prepare_pay_onchain_response,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_payment { pub struct wire_cst_payment {
tx_id: *mut wire_cst_list_prim_u_8_strict, tx_id: *mut wire_cst_list_prim_u_8_strict,
swap_id: *mut wire_cst_list_prim_u_8_strict, swap_id: *mut wire_cst_list_prim_u_8_strict,
@@ -1287,6 +1422,17 @@ pub struct wire_cst_PaymentError_SignerError {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct wire_cst_prepare_pay_onchain_request {
amount_sat: u64,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_prepare_pay_onchain_response {
amount_sat: u64,
fees_sat: u64,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct wire_cst_prepare_receive_request { pub struct wire_cst_prepare_receive_request {
payer_amount_sat: u64, payer_amount_sat: u64,
} }

View File

@@ -35,7 +35,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueNom, default_rust_auto_opaque = RustAutoOpaqueNom,
); );
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.0.0-dev.38"; pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.0.0-dev.38";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 308302012; pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -107248138;
// Section: executor // Section: executor
@@ -287,6 +287,95 @@ fn wire__crate__bindings__BindingLiquidSdk_list_payments_impl(
}, },
) )
} }
fn wire__crate__bindings__BindingLiquidSdk_pay_onchain_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
that: impl CstDecode<
RustOpaqueNom<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BindingLiquidSdk>>,
>,
req: impl CstDecode<crate::model::PayOnchainRequest>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::DcoCodec, _, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "BindingLiquidSdk_pay_onchain",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let api_that = that.cst_decode();
let api_req = req.cst_decode();
move |context| async move {
transform_result_dco(
(move || async move {
let mut api_that_decoded = None;
let decode_indices_ =
flutter_rust_bridge::for_generated::lockable_compute_decode_order(
vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new(
&api_that, 0, false,
)],
);
for i in decode_indices_ {
match i {
0 => {
api_that_decoded =
Some(api_that.lockable_decode_async_ref().await)
}
_ => unreachable!(),
}
}
let api_that = &*api_that_decoded.unwrap();
crate::bindings::BindingLiquidSdk::pay_onchain(api_that, api_req).await
})()
.await,
)
}
},
)
}
fn wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
that: impl CstDecode<
RustOpaqueNom<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BindingLiquidSdk>>,
>,
req: impl CstDecode<crate::model::PreparePayOnchainRequest>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::DcoCodec, _, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "BindingLiquidSdk_prepare_pay_onchain",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let api_that = that.cst_decode();
let api_req = req.cst_decode();
move |context| async move {
transform_result_dco(
(move || async move {
let mut api_that_decoded = None;
let decode_indices_ =
flutter_rust_bridge::for_generated::lockable_compute_decode_order(
vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new(
&api_that, 0, false,
)],
);
for i in decode_indices_ {
match i {
0 => {
api_that_decoded =
Some(api_that.lockable_decode_async_ref().await)
}
_ => unreachable!(),
}
}
let api_that = &*api_that_decoded.unwrap();
crate::bindings::BindingLiquidSdk::prepare_pay_onchain(api_that, api_req)
.await
})()
.await,
)
}
},
)
}
fn wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment_impl( fn wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment_impl(
port_: flutter_rust_bridge::for_generated::MessagePort, port_: flutter_rust_bridge::for_generated::MessagePort,
that: impl CstDecode< that: impl CstDecode<
@@ -783,7 +872,8 @@ 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_boltzUrl = <String>::sse_decode(deserializer); let mut var_boltzUrl = <String>::sse_decode(deserializer);
let mut var_electrumUrl = <String>::sse_decode(deserializer); let mut var_liquidElectrumUrl = <String>::sse_decode(deserializer);
let mut var_bitcoinElectrumUrl = <String>::sse_decode(deserializer);
let mut var_workingDir = <String>::sse_decode(deserializer); let mut var_workingDir = <String>::sse_decode(deserializer);
let mut var_network = <crate::model::Network>::sse_decode(deserializer); let mut var_network = <crate::model::Network>::sse_decode(deserializer);
let mut var_paymentTimeoutSec = <u64>::sse_decode(deserializer); let mut var_paymentTimeoutSec = <u64>::sse_decode(deserializer);
@@ -791,7 +881,8 @@ impl SseDecode for crate::model::Config {
let mut var_zeroConfMaxAmountSat = <Option<u64>>::sse_decode(deserializer); let mut var_zeroConfMaxAmountSat = <Option<u64>>::sse_decode(deserializer);
return crate::model::Config { return crate::model::Config {
boltz_url: var_boltzUrl, boltz_url: var_boltzUrl,
electrum_url: var_electrumUrl, liquid_electrum_url: var_liquidElectrumUrl,
bitcoin_electrum_url: var_bitcoinElectrumUrl,
working_dir: var_workingDir, working_dir: var_workingDir,
network: var_network, network: var_network,
payment_timeout_sec: var_paymentTimeoutSec, payment_timeout_sec: var_paymentTimeoutSec,
@@ -858,6 +949,10 @@ impl SseDecode for crate::error::LiquidSdkError {
2 => { 2 => {
return crate::error::LiquidSdkError::NotStarted; return crate::error::LiquidSdkError::NotStarted;
} }
3 => {
let mut var_err = <String>::sse_decode(deserializer);
return crate::error::LiquidSdkError::ServiceConnectivity { err: var_err };
}
_ => { _ => {
unimplemented!(""); unimplemented!("");
} }
@@ -1042,6 +1137,19 @@ impl SseDecode for Option<u64> {
} }
} }
impl SseDecode for crate::model::PayOnchainRequest {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut var_address = <String>::sse_decode(deserializer);
let mut var_prepareRes =
<crate::model::PreparePayOnchainResponse>::sse_decode(deserializer);
return crate::model::PayOnchainRequest {
address: var_address,
prepare_res: var_prepareRes,
};
}
}
impl SseDecode for crate::model::Payment { impl SseDecode for crate::model::Payment {
// 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 {
@@ -1176,6 +1284,28 @@ impl SseDecode for crate::model::PaymentType {
} }
} }
impl SseDecode for crate::model::PreparePayOnchainRequest {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut var_amountSat = <u64>::sse_decode(deserializer);
return crate::model::PreparePayOnchainRequest {
amount_sat: var_amountSat,
};
}
}
impl SseDecode for crate::model::PreparePayOnchainResponse {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut var_amountSat = <u64>::sse_decode(deserializer);
let mut var_feesSat = <u64>::sse_decode(deserializer);
return crate::model::PreparePayOnchainResponse {
amount_sat: var_amountSat,
fees_sat: var_feesSat,
};
}
}
impl SseDecode for crate::model::PrepareReceiveRequest { impl SseDecode for crate::model::PrepareReceiveRequest {
// 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 {
@@ -1400,7 +1530,8 @@ 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.boltz_url.into_into_dart().into_dart(), self.boltz_url.into_into_dart().into_dart(),
self.electrum_url.into_into_dart().into_dart(), self.liquid_electrum_url.into_into_dart().into_dart(),
self.bitcoin_electrum_url.into_into_dart().into_dart(),
self.working_dir.into_into_dart().into_dart(), self.working_dir.into_into_dart().into_dart(),
self.network.into_into_dart().into_dart(), self.network.into_into_dart().into_dart(),
self.payment_timeout_sec.into_into_dart().into_dart(), self.payment_timeout_sec.into_into_dart().into_dart(),
@@ -1463,6 +1594,9 @@ impl flutter_rust_bridge::IntoDart for crate::error::LiquidSdkError {
[1.into_dart(), err.into_into_dart().into_dart()].into_dart() [1.into_dart(), err.into_into_dart().into_dart()].into_dart()
} }
crate::error::LiquidSdkError::NotStarted => [2.into_dart()].into_dart(), crate::error::LiquidSdkError::NotStarted => [2.into_dart()].into_dart(),
crate::error::LiquidSdkError::ServiceConnectivity { err } => {
[3.into_dart(), err.into_into_dart().into_dart()].into_dart()
}
_ => { _ => {
unimplemented!(""); unimplemented!("");
} }
@@ -1575,6 +1709,27 @@ impl flutter_rust_bridge::IntoIntoDart<crate::model::Network> for crate::model::
} }
} }
// 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::PayOnchainRequest {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[
self.address.into_into_dart().into_dart(),
self.prepare_res.into_into_dart().into_dart(),
]
.into_dart()
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
for crate::model::PayOnchainRequest
{
}
impl flutter_rust_bridge::IntoIntoDart<crate::model::PayOnchainRequest>
for crate::model::PayOnchainRequest
{
fn into_into_dart(self) -> crate::model::PayOnchainRequest {
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::model::Payment { impl flutter_rust_bridge::IntoDart for crate::model::Payment {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[ [
@@ -1686,6 +1841,44 @@ impl flutter_rust_bridge::IntoIntoDart<crate::model::PaymentType> for crate::mod
} }
} }
// 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::PreparePayOnchainRequest {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[self.amount_sat.into_into_dart().into_dart()].into_dart()
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
for crate::model::PreparePayOnchainRequest
{
}
impl flutter_rust_bridge::IntoIntoDart<crate::model::PreparePayOnchainRequest>
for crate::model::PreparePayOnchainRequest
{
fn into_into_dart(self) -> crate::model::PreparePayOnchainRequest {
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::model::PreparePayOnchainResponse {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[
self.amount_sat.into_into_dart().into_dart(),
self.fees_sat.into_into_dart().into_dart(),
]
.into_dart()
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
for crate::model::PreparePayOnchainResponse
{
}
impl flutter_rust_bridge::IntoIntoDart<crate::model::PreparePayOnchainResponse>
for crate::model::PreparePayOnchainResponse
{
fn into_into_dart(self) -> crate::model::PreparePayOnchainResponse {
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::model::PrepareReceiveRequest { impl flutter_rust_bridge::IntoDart for crate::model::PrepareReceiveRequest {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[self.payer_amount_sat.into_into_dart().into_dart()].into_dart() [self.payer_amount_sat.into_into_dart().into_dart()].into_dart()
@@ -1917,7 +2110,8 @@ 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.boltz_url, serializer); <String>::sse_encode(self.boltz_url, serializer);
<String>::sse_encode(self.electrum_url, serializer); <String>::sse_encode(self.liquid_electrum_url, serializer);
<String>::sse_encode(self.bitcoin_electrum_url, serializer);
<String>::sse_encode(self.working_dir, serializer); <String>::sse_encode(self.working_dir, serializer);
<crate::model::Network>::sse_encode(self.network, serializer); <crate::model::Network>::sse_encode(self.network, serializer);
<u64>::sse_encode(self.payment_timeout_sec, serializer); <u64>::sse_encode(self.payment_timeout_sec, serializer);
@@ -1972,6 +2166,10 @@ impl SseEncode for crate::error::LiquidSdkError {
crate::error::LiquidSdkError::NotStarted => { crate::error::LiquidSdkError::NotStarted => {
<i32>::sse_encode(2, serializer); <i32>::sse_encode(2, serializer);
} }
crate::error::LiquidSdkError::ServiceConnectivity { err } => {
<i32>::sse_encode(3, serializer);
<String>::sse_encode(err, serializer);
}
_ => { _ => {
unimplemented!(""); unimplemented!("");
} }
@@ -2119,6 +2317,14 @@ impl SseEncode for Option<u64> {
} }
} }
impl SseEncode for crate::model::PayOnchainRequest {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<String>::sse_encode(self.address, serializer);
<crate::model::PreparePayOnchainResponse>::sse_encode(self.prepare_res, serializer);
}
}
impl SseEncode for crate::model::Payment { impl SseEncode for crate::model::Payment {
// 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) {
@@ -2244,6 +2450,21 @@ impl SseEncode for crate::model::PaymentType {
} }
} }
impl SseEncode for crate::model::PreparePayOnchainRequest {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<u64>::sse_encode(self.amount_sat, serializer);
}
}
impl SseEncode for crate::model::PreparePayOnchainResponse {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<u64>::sse_encode(self.amount_sat, serializer);
<u64>::sse_encode(self.fees_sat, serializer);
}
}
impl SseEncode for crate::model::PrepareReceiveRequest { impl SseEncode for crate::model::PrepareReceiveRequest {
// 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) {

View File

@@ -1,5 +1,7 @@
#[cfg(feature = "frb")] #[cfg(feature = "frb")]
pub(crate) mod bindings; pub(crate) mod bindings;
pub(crate) mod chain;
pub(crate) mod chain_swap;
pub mod error; pub mod error;
pub(crate) mod event; pub(crate) mod event;
#[cfg(feature = "frb")] #[cfg(feature = "frb")]

View File

@@ -1,10 +1,10 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use boltz_client::network::Chain; use boltz_client::network::Chain;
use boltz_client::swaps::boltzv2::{ use boltz_client::swaps::boltzv2::{
CreateReverseResponse, CreateSubmarineResponse, Leaf, SwapTree, BOLTZ_MAINNET_URL_V2, CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse, Leaf, Side, SwapTree,
BOLTZ_TESTNET_URL_V2, BOLTZ_MAINNET_URL_V2, BOLTZ_TESTNET_URL_V2,
}; };
use boltz_client::{Keypair, LBtcSwapScriptV2, ToHex}; use boltz_client::{BtcSwapScriptV2, BtcSwapTxV2, Keypair, LBtcSwapScriptV2, LBtcSwapTxV2, ToHex};
use lwk_wollet::ElementsNetwork; use lwk_wollet::ElementsNetwork;
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}; use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
use rusqlite::ToSql; use rusqlite::ToSql;
@@ -21,7 +21,8 @@ use crate::utils;
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct Config { pub struct Config {
pub boltz_url: String, pub boltz_url: String,
pub electrum_url: String, pub liquid_electrum_url: String,
pub bitcoin_electrum_url: String,
/// Directory in which all SDK files (DB, log, cache) are stored. /// Directory in which all SDK files (DB, log, cache) are stored.
/// ///
/// Prefix can be a relative or absolute path to this directory. /// Prefix can be a relative or absolute path to this directory.
@@ -40,7 +41,8 @@ impl Config {
pub fn mainnet() -> Self { pub fn mainnet() -> Self {
Config { Config {
boltz_url: BOLTZ_MAINNET_URL_V2.to_owned(), boltz_url: BOLTZ_MAINNET_URL_V2.to_owned(),
electrum_url: "blockstream.info:995".to_string(), liquid_electrum_url: "blockstream.info:995".to_string(),
bitcoin_electrum_url: "blockstream.info:700".to_string(),
working_dir: ".".to_string(), working_dir: ".".to_string(),
network: Network::Mainnet, network: Network::Mainnet,
payment_timeout_sec: 15, payment_timeout_sec: 15,
@@ -52,7 +54,8 @@ impl Config {
pub fn testnet() -> Self { pub fn testnet() -> Self {
Config { Config {
boltz_url: BOLTZ_TESTNET_URL_V2.to_owned(), boltz_url: BOLTZ_TESTNET_URL_V2.to_owned(),
electrum_url: "blockstream.info:465".to_string(), liquid_electrum_url: "blockstream.info:465".to_string(),
bitcoin_electrum_url: "blockstream.info:993".to_string(),
working_dir: ".".to_string(), working_dir: ".".to_string(),
network: Network::Testnet, network: Network::Testnet,
payment_timeout_sec: 15, payment_timeout_sec: 15,
@@ -74,6 +77,14 @@ pub enum Network {
/// Testnet Bitcoin and Liquid chains /// Testnet Bitcoin and Liquid chains
Testnet, Testnet,
} }
impl Network {
pub fn as_bitcoin_chain(&self) -> Chain {
match self {
Network::Mainnet => Chain::Bitcoin,
Network::Testnet => Chain::BitcoinTestnet,
}
}
}
impl From<Network> for ElementsNetwork { impl From<Network> for ElementsNetwork {
fn from(value: Network) -> Self { fn from(value: Network) -> Self {
@@ -176,6 +187,23 @@ pub struct SendPaymentResponse {
pub payment: Payment, pub payment: Payment,
} }
#[derive(Debug, Serialize, Clone)]
pub struct PreparePayOnchainRequest {
pub amount_sat: u64,
}
#[derive(Debug, Serialize, Clone)]
pub struct PreparePayOnchainResponse {
pub amount_sat: u64,
pub fees_sat: u64,
}
#[derive(Debug, Serialize)]
pub struct PayOnchainRequest {
pub address: String,
pub prepare_res: PreparePayOnchainResponse,
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct GetInfoResponse { pub struct GetInfoResponse {
/// Usable balance. This is the confirmed onchain balance minus `pending_send_sat`. /// Usable balance. This is the confirmed onchain balance minus `pending_send_sat`.
@@ -201,19 +229,193 @@ pub struct RestoreRequest {
pub backup_path: Option<String>, pub backup_path: Option<String>,
} }
// A swap enum variant
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) enum Swap { pub(crate) enum Swap {
Chain(ChainSwap),
Send(SendSwap), Send(SendSwap),
Receive(ReceiveSwap), Receive(ReceiveSwap),
} }
impl Swap { impl Swap {
pub(crate) fn id(&self) -> String { pub(crate) fn id(&self) -> String {
match &self { match &self {
Swap::Send(SendSwap { id, .. }) | Swap::Receive(ReceiveSwap { id, .. }) => id.clone(), Swap::Chain(ChainSwap { id, .. })
| Swap::Send(SendSwap { id, .. })
| Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
} }
} }
} }
#[derive(Clone, Debug)]
pub(crate) enum SwapScriptV2 {
Bitcoin(BtcSwapScriptV2),
Liquid(LBtcSwapScriptV2),
}
impl SwapScriptV2 {
pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScriptV2> {
match self {
SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
_ => Err(anyhow!("Invalid chain")),
}
}
pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScriptV2> {
match self {
SwapScriptV2::Liquid(script) => Ok(script.clone()),
_ => Err(anyhow!("Invalid chain")),
}
}
}
#[allow(clippy::large_enum_variant)]
pub(crate) enum SwapTxV2 {
Bitcoin(BtcSwapTxV2),
Liquid(LBtcSwapTxV2),
}
impl SwapTxV2 {
pub(crate) fn as_bitcoin_tx(&self) -> Result<BtcSwapTxV2> {
match self {
SwapTxV2::Bitcoin(tx) => Ok(tx.clone()),
_ => Err(anyhow!("Invalid chain")),
}
}
pub(crate) fn as_liquid_tx(&self) -> Result<LBtcSwapTxV2> {
match self {
SwapTxV2::Liquid(tx) => Ok(tx.clone()),
_ => Err(anyhow!("Invalid chain")),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize)]
pub enum Direction {
Incoming = 0,
Outgoing = 1,
}
impl ToSql for Direction {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
}
}
impl FromSql for Direction {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => match i as u8 {
0 => Ok(Direction::Incoming),
1 => Ok(Direction::Outgoing),
_ => Err(FromSqlError::OutOfRange(i)),
},
_ => Err(FromSqlError::InvalidType),
}
}
}
/// A chain swap
#[derive(Clone, Debug)]
pub(crate) struct ChainSwap {
pub(crate) id: String,
pub(crate) direction: Direction,
pub(crate) address: String,
pub(crate) preimage: String,
pub(crate) payer_amount_sat: u64,
pub(crate) receiver_amount_sat: u64,
pub(crate) claim_fees_sat: u64,
pub(crate) accept_zero_conf: bool,
/// JSON representation of [crate::persist::send::InternalCreateChainResponse]
pub(crate) create_response_json: String,
/// Persisted only when the server lockup tx is successfully broadcast
pub(crate) server_lockup_tx_id: Option<String>,
/// Persisted only when the user lockup tx is successfully broadcast
pub(crate) user_lockup_tx_id: Option<String>,
/// Persisted as soon as a claim tx is broadcast
pub(crate) claim_tx_id: Option<String>,
/// Persisted as soon as a refund tx is broadcast
pub(crate) refund_tx_id: Option<String>,
pub(crate) created_at: u32,
pub(crate) state: PaymentState,
pub(crate) claim_private_key: String,
pub(crate) refund_private_key: String,
}
impl ChainSwap {
pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
}
pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, PaymentError> {
utils::decode_keypair(&self.refund_private_key).map_err(Into::into)
}
pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
serde_json::from_str(&self.create_response_json).map_err(|e| {
anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
})?;
Ok(CreateChainResponse {
id: self.id.clone(),
claim_details: internal_create_response.claim_details,
lockup_details: internal_create_response.lockup_details,
})
}
pub(crate) fn get_claim_swap_script(&self) -> Result<SwapScriptV2, PaymentError> {
let chain_swap_details = self.get_boltz_create_response()?.claim_details;
let our_pubkey = self.get_claim_keypair()?.public_key();
let swap_script = match self.direction {
Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScriptV2::chain_from_swap_resp(
Side::To,
chain_swap_details,
our_pubkey.into(),
)?),
Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScriptV2::chain_from_swap_resp(
Side::To,
chain_swap_details,
our_pubkey.into(),
)?),
};
Ok(swap_script)
}
pub(crate) fn get_lockup_swap_script(&self) -> Result<SwapScriptV2, PaymentError> {
let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
let our_pubkey = self.get_refund_keypair()?.public_key();
let swap_script = match self.direction {
Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScriptV2::chain_from_swap_resp(
Side::From,
chain_swap_details,
our_pubkey.into(),
)?),
Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScriptV2::chain_from_swap_resp(
Side::From,
chain_swap_details,
our_pubkey.into(),
)?),
};
Ok(swap_script)
}
pub(crate) fn from_boltz_struct_to_json(
create_response: &CreateChainResponse,
expected_swap_id: &str,
) -> Result<String, PaymentError> {
let internal_create_response =
crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
create_response,
expected_swap_id,
)?;
let create_response_json =
serde_json::to_string(&internal_create_response).map_err(|e| {
PaymentError::Generic {
err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
}
})?;
Ok(create_response_json)
}
}
/// A submarine swap, used for Send /// A submarine swap, used for Send
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct SendSwap { pub(crate) struct SendSwap {
@@ -461,6 +663,14 @@ pub enum PaymentType {
Receive = 0, Receive = 0,
Send = 1, Send = 1,
} }
impl From<Direction> for PaymentType {
fn from(value: Direction) -> Self {
match value {
Direction::Incoming => Self::Receive,
Direction::Outgoing => Self::Send,
}
}
}
impl ToSql for PaymentType { impl ToSql for PaymentType {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> { fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::from(*self as i8)) Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
@@ -533,7 +743,7 @@ pub struct PaymentSwapData {
pub preimage: Option<String>, pub preimage: Option<String>,
pub bolt11: String, pub bolt11: Option<String>,
/// Amount sent by the swap payer /// Amount sent by the swap payer
pub payer_amount_sat: u64, pub payer_amount_sat: u64,
@@ -622,7 +832,7 @@ impl Payment {
amount_sat, amount_sat,
fees_sat: swap.payer_amount_sat - swap.receiver_amount_sat, fees_sat: swap.payer_amount_sat - swap.receiver_amount_sat,
preimage: swap.preimage, preimage: swap.preimage,
bolt11: Some(swap.bolt11), bolt11: swap.bolt11,
refund_tx_id: swap.refund_tx_id, refund_tx_id: swap.refund_tx_id,
refund_tx_amount_sat: swap.refund_tx_amount_sat, refund_tx_amount_sat: swap.refund_tx_amount_sat,
payment_type, payment_type,
@@ -647,7 +857,7 @@ impl Payment {
}, },
}, },
preimage: swap.as_ref().and_then(|s| s.preimage.clone()), preimage: swap.as_ref().and_then(|s| s.preimage.clone()),
bolt11: swap.as_ref().map(|s| s.bolt11.clone()), bolt11: swap.as_ref().and_then(|s| s.bolt11.clone()),
refund_tx_id: swap.as_ref().and_then(|s| s.refund_tx_id.clone()), refund_tx_id: swap.as_ref().and_then(|s| s.refund_tx_id.clone()),
refund_tx_amount_sat: swap.as_ref().and_then(|s| s.refund_tx_amount_sat), refund_tx_amount_sat: swap.as_ref().and_then(|s| s.refund_tx_amount_sat),
payment_type: tx.payment_type, payment_type: tx.payment_type,

View File

@@ -0,0 +1,298 @@
use std::collections::HashMap;
use anyhow::Result;
use boltz_client::swaps::boltzv2::{ChainSwapDetails, CreateChainResponse};
use rusqlite::{named_params, params, Connection, Row};
use serde::{Deserialize, Serialize};
use crate::ensure_sdk;
use crate::error::PaymentError;
use crate::model::*;
use crate::persist::Persister;
impl Persister {
pub(crate) fn insert_chain_swap(&self, chain_swap: &ChainSwap) -> Result<()> {
let con = self.get_connection()?;
// There is a limit of 16 param elements in a single tuple in rusqlite,
// so we split up the insert into two statements.
let mut stmt = con.prepare(
"
INSERT INTO chain_swaps (
id,
direction,
address,
preimage,
payer_amount_sat,
receiver_amount_sat,
accept_zero_conf,
create_response_json,
claim_private_key,
refund_private_key,
claim_fees_sat,
created_at,
state
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
)?;
_ = stmt.execute((
&chain_swap.id,
&chain_swap.direction,
&chain_swap.address,
&chain_swap.preimage,
&chain_swap.payer_amount_sat,
&chain_swap.receiver_amount_sat,
&chain_swap.accept_zero_conf,
&chain_swap.create_response_json,
&chain_swap.claim_private_key,
&chain_swap.refund_private_key,
&chain_swap.claim_fees_sat,
&chain_swap.created_at,
&chain_swap.state,
))?;
con.execute(
"UPDATE chain_swaps
SET
server_lockup_tx_id = :server_lockup_tx_id,
user_lockup_tx_id = :user_lockup_tx_id,
claim_tx_id = :claim_tx_id,
refund_tx_id = :refund_tx_id
WHERE
id = :id",
named_params! {
":id": &chain_swap.id,
":server_lockup_tx_id": &chain_swap.server_lockup_tx_id,
":user_lockup_tx_id": &chain_swap.user_lockup_tx_id,
":claim_tx_id": &chain_swap.claim_tx_id,
":refund_tx_id": &chain_swap.refund_tx_id,
},
)?;
Ok(())
}
fn list_chain_swaps_query(where_clauses: Vec<String>) -> String {
let mut where_clause_str = String::new();
if !where_clauses.is_empty() {
where_clause_str = String::from("WHERE ");
where_clause_str.push_str(where_clauses.join(" AND ").as_str());
}
format!(
"
SELECT
id,
direction,
address,
preimage,
payer_amount_sat,
receiver_amount_sat,
accept_zero_conf,
create_response_json,
claim_private_key,
refund_private_key,
server_lockup_tx_id,
user_lockup_tx_id,
claim_fees_sat,
claim_tx_id,
refund_tx_id,
created_at,
state
FROM chain_swaps
{where_clause_str}
ORDER BY created_at
"
)
}
pub(crate) fn fetch_chain_swap_by_id(&self, id: &str) -> Result<Option<ChainSwap>> {
let con: Connection = self.get_connection()?;
let query = Self::list_chain_swaps_query(vec!["id = ?1".to_string()]);
let res = con.query_row(&query, [id], Self::sql_row_to_chain_swap);
Ok(res.ok())
}
fn sql_row_to_chain_swap(row: &Row) -> rusqlite::Result<ChainSwap> {
Ok(ChainSwap {
id: row.get(0)?,
direction: row.get(1)?,
address: row.get(2)?,
preimage: row.get(3)?,
payer_amount_sat: row.get(4)?,
receiver_amount_sat: row.get(5)?,
accept_zero_conf: row.get(6)?,
create_response_json: row.get(7)?,
claim_private_key: row.get(8)?,
refund_private_key: row.get(9)?,
server_lockup_tx_id: row.get(10)?,
user_lockup_tx_id: row.get(11)?,
claim_fees_sat: row.get(12)?,
claim_tx_id: row.get(13)?,
refund_tx_id: row.get(14)?,
created_at: row.get(15)?,
state: row.get(16)?,
})
}
pub(crate) fn list_chain_swaps(
&self,
con: &Connection,
where_clauses: Vec<String>,
) -> rusqlite::Result<Vec<ChainSwap>> {
let query = Self::list_chain_swaps_query(where_clauses);
let chain_swaps = con
.prepare(&query)?
.query_map(params![], Self::sql_row_to_chain_swap)?
.map(|i| i.unwrap())
.collect();
Ok(chain_swaps)
}
pub(crate) fn list_ongoing_chain_swaps(
&self,
con: &Connection,
) -> rusqlite::Result<Vec<ChainSwap>> {
let mut where_clause: Vec<String> = Vec::new();
where_clause.push(format!(
"state in ({})",
[PaymentState::Created, PaymentState::Pending]
.iter()
.map(|t| format!("'{}'", *t as i8))
.collect::<Vec<_>>()
.join(", ")
));
self.list_chain_swaps(con, where_clause)
}
pub(crate) fn list_pending_chain_swaps(&self) -> Result<Vec<ChainSwap>> {
let con: Connection = self.get_connection()?;
let query = Self::list_chain_swaps_query(vec!["state = ?1".to_string()]);
let res = con
.prepare(&query)?
.query_map(params![PaymentState::Pending], Self::sql_row_to_chain_swap)?
.map(|i| i.unwrap())
.collect();
Ok(res)
}
/// Pending Chain swaps, indexed by refund tx id
pub(crate) fn list_pending_chain_swaps_by_refund_tx_id(
&self,
) -> Result<HashMap<String, ChainSwap>> {
let res: HashMap<String, ChainSwap> = self
.list_pending_chain_swaps()?
.iter()
.filter_map(|pending_chain_swap| {
pending_chain_swap
.refund_tx_id
.as_ref()
.map(|refund_tx_id| (refund_tx_id.clone(), pending_chain_swap.clone()))
})
.collect();
Ok(res)
}
pub(crate) fn update_chain_swap_accept_zero_conf(
&self,
swap_id: &str,
accept_zero_conf: bool,
) -> Result<(), PaymentError> {
let con: Connection = self.get_connection()?;
con.execute(
"UPDATE chain_swaps
SET
accept_zero_conf = :accept_zero_conf
WHERE
id = :id",
named_params! {
":id": swap_id,
":accept_zero_conf": accept_zero_conf,
},
)
.map_err(|_| PaymentError::PersistError)?;
Ok(())
}
pub(crate) fn try_handle_chain_swap_update(
&self,
swap_id: &str,
to_state: PaymentState,
server_lockup_tx_id: Option<&str>,
user_lockup_tx_id: Option<&str>,
claim_tx_id: Option<&str>,
refund_tx_id: Option<&str>,
) -> Result<(), PaymentError> {
// Do not overwrite server_lockup_tx_id, user_lockup_tx_id, claim_tx_id, refund_tx_id
let con: Connection = self.get_connection()?;
con.execute(
"UPDATE chain_swaps
SET
server_lockup_tx_id =
CASE
WHEN server_lockup_tx_id IS NULL THEN :server_lockup_tx_id
ELSE server_lockup_tx_id
END,
user_lockup_tx_id =
CASE
WHEN user_lockup_tx_id IS NULL THEN :user_lockup_tx_id
ELSE user_lockup_tx_id
END,
claim_tx_id =
CASE
WHEN claim_tx_id IS NULL THEN :claim_tx_id
ELSE claim_tx_id
END,
refund_tx_id =
CASE
WHEN refund_tx_id IS NULL THEN :refund_tx_id
ELSE refund_tx_id
END,
state = :state
WHERE
id = :id",
named_params! {
":id": swap_id,
":server_lockup_tx_id": server_lockup_tx_id,
":user_lockup_tx_id": user_lockup_tx_id,
":claim_tx_id": claim_tx_id,
":refund_tx_id": refund_tx_id,
":state": to_state,
},
)
.map_err(|_| PaymentError::PersistError)?;
Ok(())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct InternalCreateChainResponse {
pub(crate) claim_details: ChainSwapDetails,
pub(crate) lockup_details: ChainSwapDetails,
}
impl InternalCreateChainResponse {
pub(crate) fn try_convert_from_boltz(
boltz_create_response: &CreateChainResponse,
expected_swap_id: &str,
) -> Result<InternalCreateChainResponse, PaymentError> {
// Do not store the CreateResponse fields that are already stored separately
// Before skipping them, ensure they match the separately stored ones
ensure_sdk!(
boltz_create_response.id == expected_swap_id,
PaymentError::PersistError
);
let res = InternalCreateChainResponse {
claim_details: boltz_create_response.claim_details.clone(),
lockup_details: boltz_create_response.lockup_details.clone(),
};
Ok(res)
}
}

View File

@@ -35,5 +35,24 @@ pub(crate) fn current_migrations() -> Vec<&'static str> {
amount_sat INTEGER NOT NULL, amount_sat INTEGER NOT NULL,
fees_sat INTEGER NOT NULL fees_sat INTEGER NOT NULL
) STRICT;", ) STRICT;",
"CREATE TABLE IF NOT EXISTS chain_swaps (
id TEXT NOT NULL PRIMARY KEY,
direction INTEGER NOT NULL,
address TEXT NOT NULL,
preimage TEXT NOT NULL,
payer_amount_sat INTEGER NOT NULL,
receiver_amount_sat INTEGER NOT NULL,
accept_zero_conf INTEGER NOT NULL,
create_response_json TEXT NOT NULL,
claim_private_key TEXT NOT NULL,
refund_private_key TEXT NOT NULL,
server_lockup_tx_id TEXT,
user_lockup_tx_id TEXT,
claim_fees_sat INTEGER NOT NULL,
claim_tx_id TEXT,
refund_tx_id TEXT,
created_at INTEGER NOT NULL,
state INTEGER NOT NULL
) STRICT;",
] ]
} }

View File

@@ -1,11 +1,12 @@
mod backup; mod backup;
pub(crate) mod chain;
mod migrations; mod migrations;
pub(crate) mod receive; pub(crate) mod receive;
pub(crate) mod send; pub(crate) mod send;
use std::{fs::create_dir_all, path::PathBuf, str::FromStr}; use std::{fs::create_dir_all, path::PathBuf, str::FromStr};
use anyhow::Result; use anyhow::{anyhow, Result};
use migrations::current_migrations; use migrations::current_migrations;
use rusqlite::{params, Connection, OptionalExtension, Row}; use rusqlite::{params, Connection, OptionalExtension, Row};
use rusqlite_migration::{Migrations, M}; use rusqlite_migration::{Migrations, M};
@@ -50,6 +51,19 @@ impl Persister {
Ok(()) Ok(())
} }
pub(crate) fn fetch_swap_by_id(&self, id: &str) -> Result<Swap> {
match self.fetch_send_swap_by_id(id) {
Ok(Some(send_swap)) => Ok(Swap::Send(send_swap)),
_ => match self.fetch_receive_swap(id) {
Ok(Some(receive_swap)) => Ok(Swap::Receive(receive_swap)),
_ => match self.fetch_chain_swap_by_id(id) {
Ok(Some(chain_swap)) => Ok(Swap::Chain(chain_swap)),
_ => Err(anyhow!("Could not find Swap {id}")),
},
},
}
}
pub(crate) fn insert_or_update_payment(&self, ptx: PaymentTxData) -> Result<()> { pub(crate) fn insert_or_update_payment(&self, ptx: PaymentTxData) -> Result<()> {
let mut con = self.get_connection()?; let mut con = self.get_connection()?;
@@ -91,7 +105,17 @@ impl Persister {
.into_iter() .into_iter()
.map(Swap::Receive) .map(Swap::Receive)
.collect(); .collect();
Ok([ongoing_send_swaps, ongoing_receive_swaps].concat()) let ongoing_chain_swaps: Vec<Swap> = self
.list_ongoing_chain_swaps(&con)?
.into_iter()
.map(Swap::Chain)
.collect();
Ok([
ongoing_send_swaps,
ongoing_receive_swaps,
ongoing_chain_swaps,
]
.concat())
} }
fn select_payment_query(&self, where_clause: Option<&str>) -> String { fn select_payment_query(&self, where_clause: Option<&str>) -> String {
@@ -118,6 +142,14 @@ impl Persister {
ss.payer_amount_sat, ss.payer_amount_sat,
ss.receiver_amount_sat, ss.receiver_amount_sat,
ss.state, ss.state,
cs.id,
cs.created_at,
cs.direction,
cs.preimage,
cs.refund_tx_id,
cs.payer_amount_sat,
cs.receiver_amount_sat,
cs.state,
rtx.amount_sat rtx.amount_sat
FROM payment_tx_data AS ptx -- Payment tx (each tx results in a Payment) FROM payment_tx_data AS ptx -- Payment tx (each tx results in a Payment)
FULL JOIN ( FULL JOIN (
@@ -127,8 +159,10 @@ impl Persister {
ON ptx.tx_id = rs.claim_tx_id ON ptx.tx_id = rs.claim_tx_id
LEFT JOIN send_swaps AS ss -- Send Swap data LEFT JOIN send_swaps AS ss -- Send Swap data
ON ptx.tx_id = ss.lockup_tx_id ON ptx.tx_id = ss.lockup_tx_id
LEFT JOIN chain_swaps AS cs -- Chain Swap data
ON ptx.tx_id in (cs.user_lockup_tx_id, cs.claim_tx_id)
LEFT JOIN payment_tx_data AS rtx -- Refund tx data LEFT JOIN payment_tx_data AS rtx -- Refund tx data
ON rtx.tx_id = ss.refund_tx_id ON rtx.tx_id in (ss.refund_tx_id, cs.refund_tx_id)
WHERE -- Filter out refund txs from Payment tx list WHERE -- Filter out refund txs from Payment tx list
ptx.tx_id NOT IN (SELECT refund_tx_id FROM send_swaps WHERE refund_tx_id NOT NULL) ptx.tx_id NOT IN (SELECT refund_tx_id FROM send_swaps WHERE refund_tx_id NOT NULL)
AND {} AND {}
@@ -166,7 +200,17 @@ impl Persister {
let maybe_send_swap_payer_amount_sat: Option<u64> = row.get(17)?; let maybe_send_swap_payer_amount_sat: Option<u64> = row.get(17)?;
let maybe_send_swap_receiver_amount_sat: Option<u64> = row.get(18)?; let maybe_send_swap_receiver_amount_sat: Option<u64> = row.get(18)?;
let maybe_send_swap_state: Option<PaymentState> = row.get(19)?; let maybe_send_swap_state: Option<PaymentState> = row.get(19)?;
let maybe_send_swap_refund_tx_amount_sat: Option<u64> = row.get(20)?;
let maybe_chain_swap_id: Option<String> = row.get(20)?;
let maybe_chain_swap_created_at: Option<u32> = row.get(21)?;
let maybe_chain_swap_direction: Option<Direction> = row.get(22)?;
let maybe_chain_swap_preimage: Option<String> = row.get(23)?;
let maybe_chain_swap_refund_tx_id: Option<String> = row.get(24)?;
let maybe_chain_swap_payer_amount_sat: Option<u64> = row.get(25)?;
let maybe_chain_swap_receiver_amount_sat: Option<u64> = row.get(26)?;
let maybe_chain_swap_state: Option<PaymentState> = row.get(27)?;
let maybe_swap_refund_tx_amount_sat: Option<u64> = row.get(28)?;
let (swap, payment_type) = match maybe_receive_swap_id { let (swap, payment_type) = match maybe_receive_swap_id {
Some(receive_swap_id) => ( Some(receive_swap_id) => (
@@ -174,7 +218,7 @@ impl Persister {
swap_id: receive_swap_id, swap_id: receive_swap_id,
created_at: maybe_receive_swap_created_at.unwrap_or(utils::now()), created_at: maybe_receive_swap_created_at.unwrap_or(utils::now()),
preimage: None, preimage: None,
bolt11: maybe_receive_swap_invoice.unwrap_or("".to_string()), bolt11: maybe_receive_swap_invoice,
payer_amount_sat: maybe_receive_swap_payer_amount_sat.unwrap_or(0), payer_amount_sat: maybe_receive_swap_payer_amount_sat.unwrap_or(0),
receiver_amount_sat: maybe_receive_swap_receiver_amount_sat.unwrap_or(0), receiver_amount_sat: maybe_receive_swap_receiver_amount_sat.unwrap_or(0),
refund_tx_id: None, refund_tx_id: None,
@@ -183,20 +227,41 @@ impl Persister {
}), }),
PaymentType::Receive, PaymentType::Receive,
), ),
None => ( None => match maybe_send_swap_id {
maybe_send_swap_id.map(|send_swap_id| PaymentSwapData { Some(send_swap_id) => (
swap_id: send_swap_id, Some(PaymentSwapData {
created_at: maybe_send_swap_created_at.unwrap_or(utils::now()), swap_id: send_swap_id,
preimage: maybe_send_swap_preimage, created_at: maybe_send_swap_created_at.unwrap_or(utils::now()),
bolt11: maybe_send_swap_invoice.unwrap_or("".to_string()), preimage: maybe_send_swap_preimage,
payer_amount_sat: maybe_send_swap_payer_amount_sat.unwrap_or(0), bolt11: maybe_send_swap_invoice,
receiver_amount_sat: maybe_send_swap_receiver_amount_sat.unwrap_or(0), payer_amount_sat: maybe_send_swap_payer_amount_sat.unwrap_or(0),
refund_tx_id: maybe_send_swap_refund_tx_id, receiver_amount_sat: maybe_send_swap_receiver_amount_sat.unwrap_or(0),
refund_tx_amount_sat: maybe_send_swap_refund_tx_amount_sat, refund_tx_id: maybe_send_swap_refund_tx_id,
status: maybe_send_swap_state.unwrap_or(PaymentState::Created), refund_tx_amount_sat: maybe_swap_refund_tx_amount_sat,
}), status: maybe_send_swap_state.unwrap_or(PaymentState::Created),
PaymentType::Send, }),
), PaymentType::Send,
),
None => match maybe_chain_swap_id {
Some(chain_swap_id) => (
Some(PaymentSwapData {
swap_id: chain_swap_id,
created_at: maybe_chain_swap_created_at.unwrap_or(utils::now()),
preimage: maybe_chain_swap_preimage,
bolt11: None,
payer_amount_sat: maybe_chain_swap_payer_amount_sat.unwrap_or(0),
receiver_amount_sat: maybe_chain_swap_receiver_amount_sat.unwrap_or(0),
refund_tx_id: maybe_chain_swap_refund_tx_id,
refund_tx_amount_sat: maybe_swap_refund_tx_amount_sat,
status: maybe_chain_swap_state.unwrap_or(PaymentState::Created),
}),
maybe_chain_swap_direction
.unwrap_or(Direction::Outgoing)
.into(),
),
None => (None, PaymentType::Send),
},
},
}; };
match (tx, swap.clone()) { match (tx, swap.clone()) {

View File

@@ -51,9 +51,8 @@ impl ReceiveSwapStateHandler {
/// Handles status updates from Boltz for Receive swaps /// Handles status updates from Boltz for Receive swaps
pub(crate) async fn on_new_status(&self, update: &boltzv2::Update) -> Result<()> { pub(crate) async fn on_new_status(&self, update: &boltzv2::Update) -> Result<()> {
let id = update.id(); let id = &update.id;
let swap_state = update.status(); let swap_state = &update.status;
let receive_swap = self let receive_swap = self
.persister .persister
.fetch_receive_swap(id)? .fetch_receive_swap(id)?
@@ -75,7 +74,7 @@ impl ReceiveSwapStateHandler {
// The lockup tx is in the mempool and we accept 0-conf => try to claim // The lockup tx is in the mempool and we accept 0-conf => try to claim
// Execute 0-conf preconditions check // Execute 0-conf preconditions check
Ok(RevSwapStates::TransactionMempool) => { Ok(RevSwapStates::TransactionMempool) => {
let boltzv2::Update::TransactionMempool { transaction, .. } = update else { let Some(transaction) = update.transaction.clone() else {
return Err(anyhow!("Unexpected payload from Boltz status stream")); return Err(anyhow!("Unexpected payload from Boltz status stream"));
}; };

View File

@@ -12,18 +12,20 @@ use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use boltz_client::lightning_invoice::Bolt11InvoiceDescription; use boltz_client::lightning_invoice::Bolt11InvoiceDescription;
use boltz_client::ToHex; use boltz_client::ToHex;
use boltz_client::{swaps::boltzv2::*, util::secrets::Preimage, Amount, Bolt11Invoice}; use boltz_client::{swaps::boltzv2::*, util::secrets::Preimage, Bolt11Invoice};
use futures_util::stream::select_all; use futures_util::stream::select_all;
use futures_util::StreamExt; use futures_util::StreamExt;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use lwk_wollet::bitcoin::hex::DisplayHex; use lwk_wollet::bitcoin::hex::DisplayHex;
use lwk_wollet::hashes::{sha256, Hash}; use lwk_wollet::hashes::{sha256, Hash};
use lwk_wollet::{elements::LockTime, ElementsNetwork}; use lwk_wollet::{elements::LockTime, ElementsNetwork};
use lwk_wollet::{BlockchainBackend, ElectrumClient, ElectrumUrl}; use lwk_wollet::{ElectrumClient, ElectrumUrl};
use tokio::sync::{watch, RwLock}; use tokio::sync::{watch, Mutex, RwLock};
use tokio::time::MissedTickBehavior; use tokio::time::MissedTickBehavior;
use tokio_stream::wrappers::BroadcastStream; use tokio_stream::wrappers::BroadcastStream;
use crate::chain::ChainService;
use crate::chain_swap::ChainSwapStateHandler;
use crate::error::LiquidSdkError; use crate::error::LiquidSdkError;
use crate::model::PaymentState::*; use crate::model::PaymentState::*;
use crate::receive_swap::ReceiveSwapStateHandler; use crate::receive_swap::ReceiveSwapStateHandler;
@@ -42,9 +44,6 @@ use crate::{
pub const DEFAULT_DATA_DIR: &str = ".data"; pub const DEFAULT_DATA_DIR: &str = ".data";
pub(crate) trait ChainService: Send + Sync + BlockchainBackend {}
impl ChainService for ElectrumClient {}
pub struct LiquidSdk { pub struct LiquidSdk {
config: Config, config: Config,
onchain_wallet: Arc<dyn OnchainWallet>, onchain_wallet: Arc<dyn OnchainWallet>,
@@ -52,11 +51,13 @@ pub struct LiquidSdk {
event_manager: Arc<EventManager>, event_manager: Arc<EventManager>,
status_stream: Arc<dyn SwapperStatusStream>, status_stream: Arc<dyn SwapperStatusStream>,
swapper: Arc<dyn Swapper>, swapper: Arc<dyn Swapper>,
chain_service: Arc<Mutex<dyn ChainService>>,
is_started: RwLock<bool>, is_started: RwLock<bool>,
shutdown_sender: watch::Sender<()>, shutdown_sender: watch::Sender<()>,
shutdown_receiver: watch::Receiver<()>, shutdown_receiver: watch::Receiver<()>,
send_swap_state_handler: SendSwapStateHandler, send_swap_state_handler: SendSwapStateHandler,
receive_swap_state_handler: ReceiveSwapStateHandler, receive_swap_state_handler: ReceiveSwapStateHandler,
chain_swap_state_handler: ChainSwapStateHandler,
} }
impl LiquidSdk { impl LiquidSdk {
@@ -80,11 +81,11 @@ impl LiquidSdk {
let swapper = Arc::new(BoltzSwapper::new(config.clone())); let swapper = Arc::new(BoltzSwapper::new(config.clone()));
let status_stream = Arc::<dyn SwapperStatusStream>::from(swapper.create_status_stream()); let status_stream = Arc::<dyn SwapperStatusStream>::from(swapper.create_status_stream());
let chain_service = Arc::new(ElectrumClient::new(&ElectrumUrl::new( let chain_service = Arc::new(Mutex::new(ElectrumClient::new(&ElectrumUrl::new(
&config.electrum_url, &config.liquid_electrum_url,
true, true,
true, true,
))?); ))?));
let onchain_wallet = Arc::new(LiquidOnchainWallet::new(mnemonic, config.clone())?); let onchain_wallet = Arc::new(LiquidOnchainWallet::new(mnemonic, config.clone())?);
@@ -103,6 +104,14 @@ impl LiquidSdk {
swapper.clone(), swapper.clone(),
); );
let chain_swap_state_handler = ChainSwapStateHandler::new(
config.clone(),
onchain_wallet.clone(),
persister.clone(),
swapper.clone(),
chain_service.clone(),
)?;
let sdk = Arc::new(LiquidSdk { let sdk = Arc::new(LiquidSdk {
config: config.clone(), config: config.clone(),
onchain_wallet, onchain_wallet,
@@ -110,11 +119,13 @@ impl LiquidSdk {
event_manager, event_manager,
status_stream: status_stream.clone(), status_stream: status_stream.clone(),
swapper, swapper,
chain_service,
is_started: RwLock::new(false), is_started: RwLock::new(false),
shutdown_sender, shutdown_sender,
shutdown_receiver, shutdown_receiver,
send_swap_state_handler, send_swap_state_handler,
receive_swap_state_handler, receive_swap_state_handler,
chain_swap_state_handler,
}); });
Ok(sdk) Ok(sdk)
} }
@@ -202,6 +213,7 @@ impl LiquidSdk {
cloned cloned
.receive_swap_state_handler .receive_swap_state_handler
.subscribe_payment_updates(), .subscribe_payment_updates(),
cloned.chain_swap_state_handler.subscribe_payment_updates(),
]; ];
let mut combined_swap_streams = let mut combined_swap_streams =
select_all(swaps_streams.into_iter().map(BroadcastStream::new)); select_all(swaps_streams.into_iter().map(BroadcastStream::new));
@@ -222,26 +234,22 @@ impl LiquidSdk {
update = updates_stream.recv() => match update { update = updates_stream.recv() => match update {
Ok(update) => { Ok(update) => {
let _ = cloned.sync().await; let _ = cloned.sync().await;
let id = update.id(); let id = &update.id;
match cloned.persister.fetch_send_swap_by_id(id) { match cloned.persister.fetch_swap_by_id(id) {
Ok(Some(_)) => { Ok(Swap::Send(_)) => match cloned.send_swap_state_handler.on_new_status(&update).await {
match cloned.send_swap_state_handler.on_new_status(&update).await { Ok(_) => info!("Successfully handled Send Swap {id} update"),
Ok(_) => info!("Successfully handled Send Swap {id} update"), Err(e) => error!("Failed to handle Send Swap {id} update: {e}")
Err(e) => error!("Failed to handle Send Swap {id} update: {e}") },
} Ok(Swap::Receive(_)) => match cloned.receive_swap_state_handler.on_new_status(&update).await {
} Ok(_) => info!("Successfully handled Receive Swap {id} update"),
Err(e) => error!("Failed to handle Receive Swap {id} update: {e}")
},
Ok(Swap::Chain(_)) => match cloned.chain_swap_state_handler.on_new_status(&update).await {
Ok(_) => info!("Successfully handled Chain Swap {id} update"),
Err(e) => error!("Failed to handle Chain Swap {id} update: {e}")
},
_ => { _ => {
match cloned.persister.fetch_receive_swap(id) { error!("Could not find Swap {id}");
Ok(Some(_)) => {
match cloned.receive_swap_state_handler.on_new_status(&update).await {
Ok(_) => info!("Successfully handled Receive Swap {id} update"),
Err(e) => error!("Failed to handle Receive Swap {id} update: {e}")
}
}
_ => {
error!("Could not find Swap {id}");
}
}
} }
} }
} }
@@ -275,6 +283,16 @@ impl LiquidSdk {
} }
Err(e) => error!("Error listing pending send swaps: {e:?}"), Err(e) => error!("Error listing pending send swaps: {e:?}"),
} }
match cloned.persister.list_pending_chain_swaps() {
Ok(pending_chain_swaps) => {
for swap in pending_chain_swaps {
if let Err(e) = cloned.check_chain_swap_expiration(&swap).await {
error!("Error checking expiration for Send Swap {}: {e:?}", swap.id);
}
}
}
Err(e) => error!("Error listing pending send swaps: {e:?}"),
}
}, },
_ = shutdown_receiver.changed() => { _ = shutdown_receiver.changed() => {
info!("Received shutdown signal, exiting refundable swaps loop"); info!("Received shutdown signal, exiting refundable swaps loop");
@@ -285,16 +303,46 @@ impl LiquidSdk {
}); });
} }
async fn check_chain_swap_expiration(&self, chain_swap: &ChainSwap) -> Result<()> {
if chain_swap.user_lockup_tx_id.is_some() && chain_swap.refund_tx_id.is_none() {
match chain_swap.direction {
Direction::Outgoing => {
let swap_script = chain_swap.get_lockup_swap_script()?.as_liquid_script()?;
let current_height = self.chain_service.lock().await.tip()?.height;
let locktime_from_height = LockTime::from_height(current_height)?;
info!("Checking Chain Swap {} expiration: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", chain_swap.id, swap_script.locktime);
if utils::is_locktime_expired(locktime_from_height, swap_script.locktime) {
let id = &chain_swap.id;
let refund_tx_id = self.refund_chain(chain_swap).await?;
info!("Broadcast refund tx for Chain Swap {id}. Tx id: {refund_tx_id}");
self.chain_swap_state_handler
.update_swap_info(id, Pending, None, None, None, Some(&refund_tx_id))
.await?;
}
}
_ => {
// TODO: Set the chain swap to refundable when it expires
info!(
"Not checking receive Chain Swap {} expiration",
chain_swap.id
)
}
}
}
Ok(())
}
async fn check_send_swap_expiration(&self, send_swap: &SendSwap) -> Result<()> { async fn check_send_swap_expiration(&self, send_swap: &SendSwap) -> Result<()> {
if send_swap.lockup_tx_id.is_some() && send_swap.refund_tx_id.is_none() { if send_swap.lockup_tx_id.is_some() && send_swap.refund_tx_id.is_none() {
let swap_script = send_swap.get_swap_script()?; let swap_script = send_swap.get_swap_script()?;
let current_height = self.onchain_wallet.tip().await.height(); let current_height = self.chain_service.lock().await.tip()?.height;
let locktime_from_height = LockTime::from_height(current_height)?; let locktime_from_height = LockTime::from_height(current_height)?;
info!("Checking Send Swap {} expiration: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", send_swap.id, swap_script.locktime); info!("Checking Send Swap {} expiration: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", send_swap.id, swap_script.locktime);
if utils::is_locktime_expired(locktime_from_height, swap_script.locktime) { if utils::is_locktime_expired(locktime_from_height, swap_script.locktime) {
let id = &send_swap.id; let id = &send_swap.id;
let refund_tx_id = self.try_refund(send_swap).await?; let refund_tx_id = self.refund_send(send_swap).await?;
info!("Broadcast refund tx for Send Swap {id}. Tx id: {refund_tx_id}"); info!("Broadcast refund tx for Send Swap {id}. Tx id: {refund_tx_id}");
self.send_swap_state_handler self.send_swap_state_handler
.update_swap_info(id, Pending, None, None, Some(&refund_tx_id)) .update_swap_info(id, Pending, None, None, Some(&refund_tx_id))
@@ -335,55 +383,50 @@ impl LiquidSdk {
Pending => { Pending => {
// The swap state has changed to Pending // The swap state has changed to Pending
match payment.swap_id.clone() { match payment.swap_id.clone() {
Some(swap_id) => match payment.payment_type { Some(swap_id) => match self.persister.fetch_swap_by_id(&swap_id)? {
PaymentType::Receive => { Swap::Chain(ChainSwap { claim_tx_id, .. })
match self.persister.fetch_receive_swap(&swap_id)? { | Swap::Receive(ReceiveSwap { claim_tx_id, .. }) => {
Some(swap) => match swap.claim_tx_id { match claim_tx_id {
Some(_) => { Some(_) => {
// The claim tx has now been broadcast // The claim tx has now been broadcast
self.notify_event_listeners( self.notify_event_listeners(
LiquidSdkEvent::PaymentWaitingConfirmation { LiquidSdkEvent::PaymentWaitingConfirmation {
details: payment, details: payment,
}, },
) )
.await? .await?
} }
None => { None => {
// The lockup tx is in the mempool/confirmed // The lockup tx is in the mempool/confirmed
self.notify_event_listeners( self.notify_event_listeners(
LiquidSdkEvent::PaymentPending { LiquidSdkEvent::PaymentPending {
details: payment, details: payment,
}, },
) )
.await? .await?
} }
},
None => debug!("Swap not found: {swap_id}"),
} }
} }
PaymentType::Send => { Swap::Send(SendSwap { refund_tx_id, .. }) => {
match self.persister.fetch_send_swap_by_id(&swap_id)? { match refund_tx_id {
Some(swap) => match swap.refund_tx_id { Some(_) => {
Some(_) => { // The refund tx has now been broadcast
// The refund tx has now been broadcast self.notify_event_listeners(
self.notify_event_listeners( LiquidSdkEvent::PaymentRefundPending {
LiquidSdkEvent::PaymentRefundPending { details: payment,
details: payment, },
}, )
) .await?
.await? }
} None => {
None => { // The lockup tx is in the mempool/confirmed
// The lockup tx is in the mempool/confirmed self.notify_event_listeners(
self.notify_event_listeners( LiquidSdkEvent::PaymentPending {
LiquidSdkEvent::PaymentPending { details: payment,
details: payment, },
}, )
) .await?
.await? }
}
},
None => debug!("Swap not found: {swap_id}"),
} }
} }
}, },
@@ -508,6 +551,25 @@ impl LiquidSdk {
Ok(lbtc_pair) Ok(lbtc_pair)
} }
fn validate_chain_pairs(
&self,
direction: Direction,
amount_sat: u64,
) -> Result<ChainPair, PaymentError> {
let pair = self
.swapper
.get_chain_pairs(direction)?
.ok_or(PaymentError::PairsNotFound)?;
pair.limits.within(amount_sat)?;
let fees_sat = pair.fees.total(amount_sat);
ensure_sdk!(amount_sat > fees_sat, PaymentError::AmountOutOfRange);
Ok(pair)
}
/// Estimate the onchain fee for sending the given amount to the given destination address /// Estimate the onchain fee for sending the given amount to the given destination address
async fn estimate_onchain_tx_fee(&self, amount_sat: u64, address: &str) -> Result<u64> { async fn estimate_onchain_tx_fee(&self, amount_sat: u64, address: &str) -> Result<u64> {
Ok(self Ok(self
@@ -563,10 +625,10 @@ impl LiquidSdk {
}) })
} }
async fn try_refund_non_cooperative( async fn refund_send_non_cooperative(
&self, &self,
swap: &SendSwap, swap: &SendSwap,
broadcast_fees_sat: Amount, broadcast_fees_sat: u64,
) -> Result<String, PaymentError> { ) -> Result<String, PaymentError> {
info!( info!(
"Initiating non-cooperative refund for Send Swap {}", "Initiating non-cooperative refund for Send Swap {}",
@@ -589,13 +651,56 @@ impl LiquidSdk {
Ok(refund_tx_id) Ok(refund_tx_id)
} }
async fn try_refund(&self, swap: &SendSwap) -> Result<String, PaymentError> { async fn refund_chain_non_cooperative(
&self,
swap: &ChainSwap,
broadcast_fees_sat: u64,
) -> Result<String, PaymentError> {
info!(
"Initiating non-cooperative refund for Chain Swap {}",
&swap.id
);
let current_height = self.onchain_wallet.tip().await.height();
let output_address = self.onchain_wallet.next_unused_address().await?.to_string();
let refund_tx_id = self.swapper.refund_chain_swap_non_cooperative(
swap,
broadcast_fees_sat,
&output_address,
current_height,
)?;
info!(
"Successfully broadcast non-cooperative refund for Chain Swap {}, tx: {}",
swap.id, refund_tx_id
);
Ok(refund_tx_id)
}
async fn refund_chain(&self, swap: &ChainSwap) -> Result<String, PaymentError> {
let output_address = self.onchain_wallet.next_unused_address().await?.to_string();
let refund_tx_fees_sat = self
.estimate_onchain_tx_fee(swap.receiver_amount_sat, &output_address)
.await?;
let refund_res =
self.swapper
.refund_chain_swap_cooperative(swap, &output_address, refund_tx_fees_sat);
match refund_res {
Ok(res) => Ok(res),
Err(e) => {
warn!("Cooperative refund failed: {:?}", e);
self.refund_chain_non_cooperative(swap, refund_tx_fees_sat)
.await
}
}
}
async fn refund_send(&self, swap: &SendSwap) -> Result<String, PaymentError> {
let amount_sat = get_invoice_amount!(swap.invoice); let amount_sat = get_invoice_amount!(swap.invoice);
let output_address = self.onchain_wallet.next_unused_address().await?.to_string(); let output_address = self.onchain_wallet.next_unused_address().await?.to_string();
let refund_tx_fees_sat = Amount::from_sat( let refund_tx_fees_sat = self
self.estimate_onchain_tx_fee(amount_sat, &output_address) .estimate_onchain_tx_fee(amount_sat, &output_address)
.await?, .await?;
);
let refund_res = let refund_res =
self.swapper self.swapper
.refund_send_swap_cooperative(swap, &output_address, refund_tx_fees_sat); .refund_send_swap_cooperative(swap, &output_address, refund_tx_fees_sat);
@@ -603,7 +708,7 @@ impl LiquidSdk {
Ok(res) => Ok(res), Ok(res) => Ok(res),
Err(e) => { Err(e) => {
warn!("Cooperative refund failed: {:?}", e); warn!("Cooperative refund failed: {:?}", e);
self.try_refund_non_cooperative(swap, refund_tx_fees_sat) self.refund_send_non_cooperative(swap, refund_tx_fees_sat)
.await .await
} }
} }
@@ -751,22 +856,120 @@ impl LiquidSdk {
self.status_stream.track_swap_id(&swap.id)?; self.status_stream.track_swap_id(&swap.id)?;
let accept_zero_conf = swap.get_boltz_create_response()?.accept_zero_conf; let accept_zero_conf = swap.get_boltz_create_response()?.accept_zero_conf;
self.wait_for_payment(swap.id, accept_zero_conf) self.wait_for_payment(Swap::Send(swap), accept_zero_conf)
.await
.map(|payment| SendPaymentResponse { payment })
}
pub async fn prepare_pay_onchain(
&self,
req: &PreparePayOnchainRequest,
) -> Result<PreparePayOnchainResponse, PaymentError> {
self.ensure_is_started().await?;
let amount_sat = req.amount_sat;
let pair = self.validate_chain_pairs(Direction::Outgoing, amount_sat)?;
let claim_fees_sat = pair.fees.claim_estimate();
let server_lockup_amount_sat = amount_sat + claim_fees_sat;
let lockup_fees_sat = self
.estimate_lockup_tx_fee(server_lockup_amount_sat)
.await?;
Ok(PreparePayOnchainResponse {
amount_sat,
fees_sat: pair.fees.boltz(amount_sat) + lockup_fees_sat + claim_fees_sat,
})
}
pub async fn pay_onchain(
&self,
req: &PayOnchainRequest,
) -> Result<SendPaymentResponse, PaymentError> {
self.ensure_is_started().await?;
let receiver_amount_sat = req.prepare_res.amount_sat;
let pair = self.validate_chain_pairs(Direction::Outgoing, receiver_amount_sat)?;
let claim_fees_sat = pair.fees.claim_estimate();
let server_lockup_amount_sat = receiver_amount_sat + claim_fees_sat;
let lockup_fees_sat = self
.estimate_lockup_tx_fee(server_lockup_amount_sat)
.await?;
ensure_sdk!(
req.prepare_res.fees_sat
== pair.fees.boltz(receiver_amount_sat) + lockup_fees_sat + claim_fees_sat,
PaymentError::InvalidOrExpiredFees
);
let preimage = Preimage::new();
let preimage_str = preimage.to_string().ok_or(PaymentError::InvalidPreimage)?;
let claim_keypair = utils::generate_keypair();
let claim_public_key = boltz_client::PublicKey {
compressed: true,
inner: claim_keypair.public_key(),
};
let refund_keypair = utils::generate_keypair();
let refund_public_key = boltz_client::PublicKey {
compressed: true,
inner: refund_keypair.public_key(),
};
let create_response = self.swapper.create_chain_swap(CreateChainRequest {
from: "L-BTC".to_string(),
to: "BTC".to_string(),
preimage_hash: preimage.sha256,
claim_public_key: Some(claim_public_key),
refund_public_key: Some(refund_public_key),
user_lock_amount: None,
server_lock_amount: Some(server_lockup_amount_sat as u32), // TODO update our model
pair_hash: Some(pair.hash),
referral_id: None,
})?;
let swap_id = &create_response.id;
let create_response_json = ChainSwap::from_boltz_struct_to_json(&create_response, swap_id)?;
let accept_zero_conf = server_lockup_amount_sat <= pair.limits.maximal_zero_conf;
let payer_amount_sat = req.prepare_res.fees_sat + receiver_amount_sat;
let swap = ChainSwap {
id: swap_id.clone(),
direction: Direction::Outgoing,
address: req.address.clone(),
preimage: preimage_str,
payer_amount_sat,
receiver_amount_sat,
claim_fees_sat,
accept_zero_conf,
create_response_json,
claim_private_key: claim_keypair.display_secret().to_string(),
refund_private_key: refund_keypair.display_secret().to_string(),
server_lockup_tx_id: None,
user_lockup_tx_id: None,
claim_tx_id: None,
refund_tx_id: None,
created_at: utils::now(),
state: PaymentState::Created,
};
self.persister.insert_chain_swap(&swap)?;
self.status_stream.track_swap_id(&swap.id)?;
self.wait_for_payment(Swap::Chain(swap), accept_zero_conf)
.await .await
.map(|payment| SendPaymentResponse { payment }) .map(|payment| SendPaymentResponse { payment })
} }
async fn wait_for_payment( async fn wait_for_payment(
&self, &self,
swap_id: String, swap: Swap,
accept_zero_conf: bool, accept_zero_conf: bool,
) -> Result<Payment, PaymentError> { ) -> Result<Payment, PaymentError> {
let timeout_fut = tokio::time::sleep(Duration::from_secs(self.config.payment_timeout_sec)); let timeout_fut = tokio::time::sleep(Duration::from_secs(self.config.payment_timeout_sec));
tokio::pin!(timeout_fut); tokio::pin!(timeout_fut);
let swap_id = swap.id();
let mut events_stream = self.event_manager.subscribe(); let mut events_stream = self.event_manager.subscribe();
let mut maybe_payment: Option<Payment> = None; let mut maybe_payment: Option<Payment> = None;
let send_swap_state_handler = self.send_swap_state_handler.clone();
loop { loop {
tokio::select! { tokio::select! {
@@ -774,7 +977,11 @@ impl LiquidSdk {
Some(payment) => return Ok(payment), Some(payment) => return Ok(payment),
None => { None => {
debug!("Timeout occured without payment, set swap to timed out"); debug!("Timeout occured without payment, set swap to timed out");
send_swap_state_handler.update_swap_info(&swap_id, TimedOut, None, None, None).await?; match swap {
Swap::Send(_) => self.send_swap_state_handler.update_swap_info(&swap_id, TimedOut, None, None, None).await?,
Swap::Chain(_) => self.chain_swap_state_handler.update_swap_info(&swap_id, TimedOut, None, None, None, None).await?,
_ => ()
}
return Err(PaymentError::PaymentTimeout) return Err(PaymentError::PaymentTimeout)
}, },
}, },
@@ -968,6 +1175,8 @@ impl LiquidSdk {
self.persister.list_pending_receive_swaps_by_claim_tx_id()?; self.persister.list_pending_receive_swaps_by_claim_tx_id()?;
let pending_send_swaps_by_refund_tx_id = let pending_send_swaps_by_refund_tx_id =
self.persister.list_pending_send_swaps_by_refund_tx_id()?; self.persister.list_pending_send_swaps_by_refund_tx_id()?;
let pending_chain_swaps_by_refund_tx_id =
self.persister.list_pending_chain_swaps_by_refund_tx_id()?;
for tx in self.onchain_wallet.transactions().await? { for tx in self.onchain_wallet.transactions().await? {
let tx_id = tx.txid.to_string(); let tx_id = tx.txid.to_string();
@@ -998,6 +1207,12 @@ impl LiquidSdk {
.update_swap_info(&swap.id, Failed, None, None, None) .update_swap_info(&swap.id, Failed, None, None, None)
.await?; .await?;
} }
} else if let Some(swap) = pending_chain_swaps_by_refund_tx_id.get(&tx_id) {
if is_tx_confirmed {
self.chain_swap_state_handler
.update_swap_info(&swap.id, Failed, None, None, None, None)
.await?;
}
} else { } else {
// Payments that are not directly associated with a swap (e.g. direct onchain payments using MRH) // Payments that are not directly associated with a swap (e.g. direct onchain payments using MRH)

View File

@@ -4,16 +4,16 @@ use anyhow::{anyhow, Result};
use boltz_client::swaps::boltzv2; use boltz_client::swaps::boltzv2;
use boltz_client::swaps::{boltz::SubSwapStates, boltzv2::CreateSubmarineResponse}; use boltz_client::swaps::{boltz::SubSwapStates, boltzv2::CreateSubmarineResponse};
use boltz_client::util::secrets::Preimage; use boltz_client::util::secrets::Preimage;
use boltz_client::{Amount, Bolt11Invoice, ToHex}; use boltz_client::{Bolt11Invoice, ToHex};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use lwk_wollet::bitcoin::Witness; use lwk_wollet::bitcoin::Witness;
use lwk_wollet::elements::Transaction; use lwk_wollet::elements::Transaction;
use lwk_wollet::hashes::{sha256, Hash}; use lwk_wollet::hashes::{sha256, Hash};
use tokio::sync::broadcast; use tokio::sync::{broadcast, Mutex};
use crate::chain::ChainService;
use crate::model::PaymentState::{Complete, Created, Failed, Pending, TimedOut}; use crate::model::PaymentState::{Complete, Created, Failed, Pending, TimedOut};
use crate::model::{Config, SendSwap}; use crate::model::{Config, SendSwap};
use crate::sdk::ChainService;
use crate::swapper::Swapper; use crate::swapper::Swapper;
use crate::wallet::OnchainWallet; use crate::wallet::OnchainWallet;
use crate::{ensure_sdk, get_invoice_amount}; use crate::{ensure_sdk, get_invoice_amount};
@@ -28,7 +28,7 @@ pub(crate) struct SendSwapStateHandler {
onchain_wallet: Arc<dyn OnchainWallet>, onchain_wallet: Arc<dyn OnchainWallet>,
persister: Arc<Persister>, persister: Arc<Persister>,
swapper: Arc<dyn Swapper>, swapper: Arc<dyn Swapper>,
chain_service: Arc<dyn ChainService>, chain_service: Arc<Mutex<dyn ChainService>>,
subscription_notifier: broadcast::Sender<String>, subscription_notifier: broadcast::Sender<String>,
} }
@@ -38,7 +38,7 @@ impl SendSwapStateHandler {
onchain_wallet: Arc<dyn OnchainWallet>, onchain_wallet: Arc<dyn OnchainWallet>,
persister: Arc<Persister>, persister: Arc<Persister>,
swapper: Arc<dyn Swapper>, swapper: Arc<dyn Swapper>,
chain_service: Arc<dyn ChainService>, chain_service: Arc<Mutex<dyn ChainService>>,
) -> Self { ) -> Self {
let (subscription_notifier, _) = broadcast::channel::<String>(30); let (subscription_notifier, _) = broadcast::channel::<String>(30);
Self { Self {
@@ -57,8 +57,8 @@ impl SendSwapStateHandler {
/// Handles status updates from Boltz for Send swaps /// Handles status updates from Boltz for Send swaps
pub(crate) async fn on_new_status(&self, update: &boltzv2::Update) -> Result<()> { pub(crate) async fn on_new_status(&self, update: &boltzv2::Update) -> Result<()> {
let id = update.id(); let id = &update.id;
let swap_state = update.status(); let swap_state = &update.status;
let swap = self let swap = self
.persister .persister
.fetch_send_swap_by_id(id)? .fetch_send_swap_by_id(id)?
@@ -124,7 +124,9 @@ impl SendSwapStateHandler {
} }
None => { None => {
debug!("The claim tx was a script path spend (non-cooperative claim)"); debug!("The claim tx was a script path spend (non-cooperative claim)");
let preimage = self.get_preimage_from_script_path_claim_spend(&swap)?; let preimage = self
.get_preimage_from_script_path_claim_spend(&swap)
.await?;
self.validate_send_swap_preimage(id, &swap.invoice, &preimage) self.validate_send_swap_preimage(id, &swap.invoice, &preimage)
.await?; .await?;
self.update_swap_info(id, Complete, Some(&preimage), None, None) self.update_swap_info(id, Complete, Some(&preimage), None, None)
@@ -200,7 +202,12 @@ impl SendSwapStateHandler {
) )
.await?; .await?;
let lockup_tx_id = self.chain_service.broadcast(&lockup_tx)?.to_string(); let lockup_tx_id = self
.chain_service
.lock()
.await
.broadcast(&lockup_tx)?
.to_string();
debug!("Successfully broadcast lockup tx for Send Swap {swap_id}. Lockup tx id: {lockup_tx_id}"); debug!("Successfully broadcast lockup tx for Send Swap {swap_id}. Lockup tx id: {lockup_tx_id}");
Ok(lockup_tx) Ok(lockup_tx)
@@ -246,7 +253,7 @@ impl SendSwapStateHandler {
&send_swap.id &send_swap.id
); );
let output_address = self.onchain_wallet.next_unused_address().await?.to_string(); let output_address = self.onchain_wallet.next_unused_address().await?.to_string();
let claim_tx_details = self.swapper.get_claim_tx_details(send_swap)?; let claim_tx_details = self.swapper.get_send_claim_tx_details(send_swap)?;
self.update_swap_info( self.update_swap_info(
&send_swap.id, &send_swap.id,
Complete, Complete,
@@ -260,7 +267,7 @@ impl SendSwapStateHandler {
Ok(()) Ok(())
} }
fn get_preimage_from_script_path_claim_spend( async fn get_preimage_from_script_path_claim_spend(
&self, &self,
swap: &SendSwap, swap: &SendSwap,
) -> Result<String, PaymentError> { ) -> Result<String, PaymentError> {
@@ -276,6 +283,8 @@ impl SendSwapStateHandler {
// Get tx history of the swap script (lockup address) // Get tx history of the swap script (lockup address)
let history: Vec<_> = self let history: Vec<_> = self
.chain_service .chain_service
.lock()
.await
.get_scripts_history(&[&swap_script_pk])? .get_scripts_history(&[&swap_script_pk])?
.into_iter() .into_iter()
.flatten() .flatten()
@@ -299,6 +308,8 @@ impl SendSwapStateHandler {
let claim_tx = self let claim_tx = self
.chain_service .chain_service
.lock()
.await
.get_transactions(&[claim_tx_id]) .get_transactions(&[claim_tx_id])
.map_err(|e| anyhow!("Failed to fetch claim tx {claim_tx_id}: {e}"))? .map_err(|e| anyhow!("Failed to fetch claim tx {claim_tx_id}: {e}"))?
.first() .first()
@@ -349,16 +360,15 @@ impl SendSwapStateHandler {
.all_fees() .all_fees()
.values() .values()
.sum(); .sum();
let broadcast_fees_sat = Amount::from_sat(fee);
let refund_res = let refund_res = self
self.swapper .swapper
.refund_send_swap_cooperative(swap, &output_address, broadcast_fees_sat); .refund_send_swap_cooperative(swap, &output_address, fee);
match refund_res { match refund_res {
Ok(res) => Ok(res), Ok(res) => Ok(res),
Err(e) => { Err(e) => {
warn!("Cooperative refund failed: {:?}", e); warn!("Cooperative refund failed: {:?}", e);
self.refund_non_cooperative(swap, broadcast_fees_sat).await self.refund_non_cooperative(swap, fee).await
} }
} }
} }
@@ -366,7 +376,7 @@ impl SendSwapStateHandler {
async fn refund_non_cooperative( async fn refund_non_cooperative(
&self, &self,
swap: &SendSwap, swap: &SendSwap,
broadcast_fees_sat: Amount, broadcast_fees_sat: u64,
) -> Result<String, PaymentError> { ) -> Result<String, PaymentError> {
info!( info!(
"Initiating non-cooperative refund for Send Swap {}", "Initiating non-cooperative refund for Send Swap {}",

View File

@@ -9,19 +9,22 @@ use boltz_client::error::Error;
use boltz_client::network::electrum::ElectrumConfig; use boltz_client::network::electrum::ElectrumConfig;
use boltz_client::network::Chain; use boltz_client::network::Chain;
use boltz_client::swaps::boltzv2::{ use boltz_client::swaps::boltzv2::{
self, BoltzApiClientV2, ClaimTxResponse, CreateReverseRequest, CreateReverseResponse, self, BoltzApiClientV2, ChainPair, Cooperative, CreateChainRequest, CreateChainResponse,
CreateSubmarineRequest, CreateSubmarineResponse, ReversePair, SubmarinePair, CreateReverseRequest, CreateReverseResponse, CreateSubmarineRequest, CreateSubmarineResponse,
ReversePair, SubmarineClaimTxResponse, SubmarinePair,
}; };
use boltz_client::util::secrets::Preimage; use boltz_client::util::secrets::Preimage;
use boltz_client::{Amount, Bolt11Invoice, LBtcSwapTxV2}; use boltz_client::{Amount, Bolt11Invoice, BtcSwapTxV2, Keypair, LBtcSwapTxV2, LockTime};
use boltz_status_stream::BoltzStatusStream; use boltz_status_stream::BoltzStatusStream;
use log::{debug, info}; use log::{debug, info};
use lwk_wollet::elements::LockTime; use lwk_wollet::elements;
use serde_json::Value; use serde_json::Value;
use tokio::sync::{broadcast, watch}; use tokio::sync::{broadcast, watch};
use crate::error::PaymentError; use crate::error::PaymentError;
use crate::model::{Config, Network, ReceiveSwap, SendSwap}; use crate::model::{
ChainSwap, Config, Direction, Network, ReceiveSwap, SendSwap, SwapScriptV2, SwapTxV2,
};
use crate::utils; use crate::utils;
#[async_trait] #[async_trait]
@@ -41,44 +44,80 @@ pub trait SwapperStatusStream: Send + Sync {
} }
pub trait Swapper: Send + Sync { pub trait Swapper: Send + Sync {
/// Create a new chain swap
fn create_chain_swap(
&self,
req: CreateChainRequest,
) -> Result<CreateChainResponse, PaymentError>;
/// Create a new send swap /// Create a new send swap
fn create_send_swap( fn create_send_swap(
&self, &self,
req: CreateSubmarineRequest, req: CreateSubmarineRequest,
) -> Result<CreateSubmarineResponse, PaymentError>; ) -> Result<CreateSubmarineResponse, PaymentError>;
/// Get a chain pair information
fn get_chain_pairs(&self, direction: Direction) -> Result<Option<ChainPair>, PaymentError>;
/// Get a submarine pair information /// Get a submarine pair information
fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError>; fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError>;
/// Refund a cooperatively chain swap
fn refund_chain_swap_cooperative(
&self,
swap: &ChainSwap,
output_address: &str,
broadcast_fees_sat: u64,
) -> Result<String, PaymentError>;
/// Refund a cooperatively send swap /// Refund a cooperatively send swap
fn refund_send_swap_cooperative( fn refund_send_swap_cooperative(
&self, &self,
swap: &SendSwap, swap: &SendSwap,
output_address: &str, output_address: &str,
broadcast_fees_sat: Amount, broadcast_fees_sat: u64,
) -> Result<String, PaymentError>;
/// Refund non-cooperatively chain swap
fn refund_chain_swap_non_cooperative(
&self,
swap: &ChainSwap,
broadcast_fees_sat: u64,
output_address: &str,
current_height: u32,
) -> Result<String, PaymentError>; ) -> Result<String, PaymentError>;
/// Refund non-cooperatively send swap /// Refund non-cooperatively send swap
fn refund_send_swap_non_cooperative( fn refund_send_swap_non_cooperative(
&self, &self,
swap: &SendSwap, swap: &SendSwap,
broadcast_fees_sat: Amount, broadcast_fees_sat: u64,
output_address: &str, output_address: &str,
current_height: u32, current_height: u32,
) -> Result<String, PaymentError>; ) -> Result<String, PaymentError>;
/// Get claim tx details which includes the preimage as a proof of payment. /// Get send swap claim tx details which includes the preimage as a proof of payment.
/// It is used to validate the preimage before claiming which is the reason why we need to separate /// It is used to validate the preimage before claiming which is the reason why we need to separate
/// the claim into two steps. /// the claim into two steps.
fn get_claim_tx_details(&self, swap: &SendSwap) -> Result<ClaimTxResponse, PaymentError>; fn get_send_claim_tx_details(
&self,
swap: &SendSwap,
) -> Result<SubmarineClaimTxResponse, PaymentError>;
/// Claim chain swap.
fn claim_chain_swap(
&self,
swap: &ChainSwap,
refund_address: String,
) -> Result<String, PaymentError>;
/// Claim send swap cooperatively. Here the remote swapper is the one that claims. /// Claim send swap cooperatively. Here the remote swapper is the one that claims.
/// We are helping to use key spend path for cheaper fees. /// We are helping to use key spend path for cheaper fees.
fn claim_send_swap_cooperative( fn claim_send_swap_cooperative(
&self, &self,
swap: &SendSwap, swap: &SendSwap,
claim_tx_response: ClaimTxResponse, claim_tx_response: SubmarineClaimTxResponse,
output_address: &str, refund_address: &str,
) -> Result<(), PaymentError>; ) -> Result<(), PaymentError>;
/// Create a new receive swap /// Create a new receive swap
@@ -109,7 +148,8 @@ pub trait Swapper: Send + Sync {
pub struct BoltzSwapper { pub struct BoltzSwapper {
client: BoltzApiClientV2, client: BoltzApiClientV2,
config: Config, config: Config,
electrum_config: ElectrumConfig, liquid_electrum_config: ElectrumConfig,
bitcoin_electrum_config: ElectrumConfig,
} }
impl BoltzSwapper { impl BoltzSwapper {
@@ -117,9 +157,16 @@ impl BoltzSwapper {
BoltzSwapper { BoltzSwapper {
client: BoltzApiClientV2::new(&config.boltz_url), client: BoltzApiClientV2::new(&config.boltz_url),
config: config.clone(), config: config.clone(),
electrum_config: ElectrumConfig::new( liquid_electrum_config: ElectrumConfig::new(
config.network.into(), config.network.into(),
&config.electrum_url, &config.liquid_electrum_url,
true,
true,
100,
),
bitcoin_electrum_config: ElectrumConfig::new(
config.network.as_bitcoin_chain(),
&config.bitcoin_electrum_url,
true, true,
true, true,
100, 100,
@@ -129,18 +176,25 @@ impl BoltzSwapper {
fn new_refund_tx( fn new_refund_tx(
&self, &self,
swap: &SendSwap, swap_id: String,
output_address: &String, swap_script: SwapScriptV2,
) -> Result<LBtcSwapTxV2, PaymentError> { refund_address: &String,
let swap_script = swap.get_swap_script()?; ) -> Result<SwapTxV2, PaymentError> {
let swap_tx = match swap_script {
Ok(LBtcSwapTxV2::new_refund( SwapScriptV2::Bitcoin(swap_script) => SwapTxV2::Bitcoin(BtcSwapTxV2::new_refund(
swap_script.clone(), swap_script.clone(),
output_address, refund_address,
&self.electrum_config, &self.bitcoin_electrum_config,
self.config.boltz_url.clone(), )?),
swap.id.to_string(), SwapScriptV2::Liquid(swap_script) => SwapTxV2::Liquid(LBtcSwapTxV2::new_refund(
)?) swap_script.clone(),
refund_address,
&self.liquid_electrum_config,
self.config.boltz_url.clone(),
swap_id,
)?),
};
Ok(swap_tx)
} }
fn validate_send_swap_preimage( fn validate_send_swap_preimage(
@@ -165,9 +219,235 @@ impl BoltzSwapper {
.then_some(()) .then_some(())
.ok_or(PaymentError::InvalidPreimage) .ok_or(PaymentError::InvalidPreimage)
} }
fn claim_outgoing_chain_swap(
&self,
swap: &ChainSwap,
refund_address: String,
) -> Result<String, PaymentError> {
let claim_keypair = swap.get_claim_keypair()?;
let claim_swap_script = swap.get_claim_swap_script()?.as_bitcoin_script()?;
let claim_tx_wrapper = BtcSwapTxV2::new_claim(
claim_swap_script,
swap.address.clone(),
&self.bitcoin_electrum_config,
)?;
let refund_keypair = swap.get_refund_keypair()?;
let lockup_swap_script = swap.get_lockup_swap_script()?;
let refund_tx = self
.new_refund_tx(swap.id.clone(), lockup_swap_script, &refund_address)?
.as_liquid_tx()?;
let claim_tx_response = self.client.get_chain_claim_tx_details(&swap.id)?;
let (partial_sig, pub_nonce) = refund_tx.partial_sig(
&refund_keypair,
&claim_tx_response.pub_nonce,
&claim_tx_response.transaction_hash,
)?;
let claim_tx = claim_tx_wrapper.sign_claim(
&claim_keypair,
&Preimage::from_str(&swap.preimage)?,
swap.claim_fees_sat,
Some(Cooperative {
boltz_api: &self.client,
swap_id: swap.id.clone(),
pub_nonce: Some(pub_nonce),
partial_sig: Some(partial_sig),
}),
)?;
debug!("Claim Tx {:?}", claim_tx);
let claim_tx_id = claim_tx_wrapper
.broadcast(&claim_tx, &self.bitcoin_electrum_config)?
.to_string();
Ok(claim_tx_id)
}
fn claim_incoming_chain_swap(
&self,
swap: &ChainSwap,
refund_address: String,
) -> Result<String, PaymentError> {
let claim_keypair = swap.get_claim_keypair()?;
let swap_script = swap.get_claim_swap_script()?.as_liquid_script()?;
let claim_tx_wrapper = LBtcSwapTxV2::new_claim(
swap_script,
swap.address.clone(),
&self.liquid_electrum_config,
self.config.boltz_url.clone(),
swap.id.clone(),
)?;
let refund_keypair = swap.get_refund_keypair()?;
let lockup_swap_script = swap.get_lockup_swap_script()?;
let refund_tx = self
.new_refund_tx(swap.id.clone(), lockup_swap_script, &refund_address)?
.as_bitcoin_tx()?;
let claim_tx_response = self.client.get_chain_claim_tx_details(&swap.id)?;
let (partial_sig, pub_nonce) = refund_tx.partial_sig(
&refund_keypair,
&claim_tx_response.pub_nonce,
&claim_tx_response.transaction_hash,
)?;
let claim_tx = claim_tx_wrapper.sign_claim(
&claim_keypair,
&Preimage::from_str(&swap.preimage)?,
Amount::from_sat(swap.claim_fees_sat),
Some(Cooperative {
boltz_api: &self.client,
swap_id: swap.id.clone(),
pub_nonce: Some(pub_nonce),
partial_sig: Some(partial_sig),
}),
)?;
debug!("Claim Tx {:?}", claim_tx);
let claim_tx_id = claim_tx_wrapper.broadcast(
&claim_tx,
&self.liquid_electrum_config,
Some((&self.client, self.config.network.into())),
)?;
Ok(claim_tx_id)
}
fn refund_swap_cooperative(
&self,
swap_id: String,
swap_script: SwapScriptV2,
refund_keypair: &Keypair,
refund_address: &str,
broadcast_fees_sat: u64,
) -> Result<String, PaymentError> {
info!("Initiating cooperative refund for Swap {}", &swap_id);
let is_cooperative = Some(Cooperative {
boltz_api: &self.client,
swap_id: swap_id.clone(),
pub_nonce: None,
partial_sig: None,
});
let refund_tx_id = match swap_script.clone() {
SwapScriptV2::Bitcoin(_) => {
let refund_tx = self
.new_refund_tx(swap_id.clone(), swap_script, &refund_address.into())?
.as_bitcoin_tx()?;
let signed_tx =
refund_tx.sign_refund(refund_keypair, broadcast_fees_sat, is_cooperative)?;
refund_tx
.broadcast(&signed_tx, &self.bitcoin_electrum_config)?
.to_string()
}
SwapScriptV2::Liquid(_) => {
let refund_tx = self
.new_refund_tx(swap_id.clone(), swap_script, &refund_address.into())?
.as_liquid_tx()?;
let signed_tx = refund_tx.sign_refund(
refund_keypair,
Amount::from_sat(broadcast_fees_sat),
is_cooperative,
)?;
let is_lowball = match self.config.network {
Network::Mainnet => None,
Network::Testnet => {
Some((&self.client, boltz_client::network::Chain::LiquidTestnet))
}
};
refund_tx.broadcast(&signed_tx, &self.liquid_electrum_config, is_lowball)?
}
};
info!(
"Successfully broadcast cooperative refund for Swap {}",
&swap_id
);
Ok(refund_tx_id)
}
fn refund_swap_non_cooperative(
&self,
swap_id: String,
swap_script: SwapScriptV2,
refund_keypair: &Keypair,
broadcast_fees_sat: u64,
refund_address: &str,
current_height: u32,
) -> Result<String, PaymentError> {
let refund_tx_id = match swap_script.clone() {
SwapScriptV2::Bitcoin(script) => {
let locktime_from_height =
LockTime::from_height(current_height).map_err(|e| PaymentError::Generic {
err: format!("Error getting locktime from height {current_height:?}: {e}",),
})?;
info!("locktime info: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", script.locktime);
if !script.locktime.is_implied_by(locktime_from_height) {
return Err(PaymentError::Generic {
err: format!(
"Cannot refund non-cooperatively. Lock time not elapsed yet. Current tip: {:?}. Script lock time: {:?}",
locktime_from_height, script.locktime
)
});
}
let refund_tx = self
.new_refund_tx(swap_id.clone(), swap_script, &refund_address.into())?
.as_bitcoin_tx()?;
let signed_tx = refund_tx.sign_refund(refund_keypair, broadcast_fees_sat, None)?;
refund_tx
.broadcast(&signed_tx, &self.bitcoin_electrum_config)?
.to_string()
}
SwapScriptV2::Liquid(script) => {
let locktime_from_height = elements::LockTime::from_height(current_height)
.map_err(|e| PaymentError::Generic {
err: format!("Cannot convert current block height to lock time: {e:?}"),
})?;
info!("locktime info: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", script.locktime);
if !utils::is_locktime_expired(locktime_from_height, script.locktime) {
return Err(PaymentError::Generic {
err: format!(
"Cannot refund non-cooperatively. Lock time not elapsed yet. Current tip: {:?}. Script lock time: {:?}",
locktime_from_height, script.locktime
)
});
}
let refund_tx = self
.new_refund_tx(swap_id.clone(), swap_script, &refund_address.into())?
.as_liquid_tx()?;
let signed_tx = refund_tx.sign_refund(
refund_keypair,
Amount::from_sat(broadcast_fees_sat),
None,
)?;
let is_lowball = match self.config.network {
Network::Mainnet => None,
Network::Testnet => {
Some((&self.client, boltz_client::network::Chain::LiquidTestnet))
}
};
refund_tx.broadcast(&signed_tx, &self.liquid_electrum_config, is_lowball)?
}
};
info!(
"Successfully broadcast non-cooperative refund for Swap {}",
swap_id
);
Ok(refund_tx_id)
}
} }
impl Swapper for BoltzSwapper { impl Swapper for BoltzSwapper {
/// Create a new chain swap
fn create_chain_swap(
&self,
req: CreateChainRequest,
) -> Result<CreateChainResponse, PaymentError> {
Ok(self.client.post_chain_req(req)?)
}
/// Create a new send swap /// Create a new send swap
fn create_send_swap( fn create_send_swap(
&self, &self,
@@ -176,114 +456,162 @@ impl Swapper for BoltzSwapper {
Ok(self.client.post_swap_req(&req)?) Ok(self.client.post_swap_req(&req)?)
} }
/// Get a chain pair information
fn get_chain_pairs(&self, direction: Direction) -> Result<Option<ChainPair>, PaymentError> {
let pairs = self.client.get_chain_pairs()?;
let pair = match direction {
Direction::Incoming => pairs.get_btc_to_lbtc_pair(),
Direction::Outgoing => pairs.get_lbtc_to_btc_pair(),
};
Ok(pair)
}
/// Get a submarine pair information /// Get a submarine pair information
fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError> { fn get_submarine_pairs(&self) -> Result<Option<SubmarinePair>, PaymentError> {
Ok(self.client.get_submarine_pairs()?.get_lbtc_to_btc_pair()) Ok(self.client.get_submarine_pairs()?.get_lbtc_to_btc_pair())
} }
/// Refund a cooperatively chain swap
fn refund_chain_swap_cooperative(
&self,
swap: &ChainSwap,
output_address: &str,
broadcast_fees_sat: u64,
) -> Result<String, PaymentError> {
let refund_keypair = swap.get_refund_keypair()?;
let swap_script = swap.get_lockup_swap_script()?;
info!("Initiating cooperative refund for Chain Swap {}", &swap.id);
self.refund_swap_cooperative(
swap.id.clone(),
swap_script,
&refund_keypair,
output_address,
broadcast_fees_sat,
)
}
/// Refund a cooperatively send swap /// Refund a cooperatively send swap
fn refund_send_swap_cooperative( fn refund_send_swap_cooperative(
&self, &self,
swap: &SendSwap, swap: &SendSwap,
output_address: &str, output_address: &str,
broadcast_fees_sat: Amount, broadcast_fees_sat: u64,
) -> Result<String, PaymentError> { ) -> Result<String, PaymentError> {
info!("Initiating cooperative refund for Send Swap {}", &swap.id); info!("Initiating cooperative refund for Send Swap {}", &swap.id);
let refund_tx = self.new_refund_tx(swap, &output_address.into())?; let swap_script = SwapScriptV2::Liquid(swap.get_swap_script()?);
let refund_keypair = swap.get_refund_keypair()?;
let cooperative = Some((&self.client, &swap.id)); self.refund_swap_cooperative(
let tx = refund_tx.sign_refund( swap.id.clone(),
&swap swap_script,
.get_refund_keypair() &refund_keypair,
.map_err(|e| Error::Generic(e.to_string()))?, output_address,
broadcast_fees_sat, broadcast_fees_sat,
cooperative, )
)?; }
let is_lowball = match self.config.network {
Network::Mainnet => None, /// Refund non-cooperatively chain swap
Network::Testnet => Some((&self.client, boltz_client::network::Chain::LiquidTestnet)), fn refund_chain_swap_non_cooperative(
}; &self,
let refund_tx_id = refund_tx.broadcast(&tx, &self.electrum_config, is_lowball)?; swap: &ChainSwap,
broadcast_fees_sat: u64,
output_address: &str,
current_height: u32,
) -> Result<String, PaymentError> {
info!( info!(
"Successfully broadcast cooperative refund for Send Swap {}", "Initiating non cooperative refund for Chain Swap {}",
&swap.id &swap.id
); );
Ok(refund_tx_id.clone()) let refund_keypair = swap.get_refund_keypair()?;
let swap_script = swap.get_lockup_swap_script()?;
self.refund_swap_non_cooperative(
swap.id.clone(),
swap_script,
&refund_keypair,
broadcast_fees_sat,
output_address,
current_height,
)
} }
/// Refund non-cooperatively send swap /// Refund non-cooperatively send swap
fn refund_send_swap_non_cooperative( fn refund_send_swap_non_cooperative(
&self, &self,
swap: &SendSwap, swap: &SendSwap,
broadcast_fees_sat: Amount, broadcast_fees_sat: u64,
output_address: &str, output_address: &str,
current_height: u32, current_height: u32,
) -> Result<String, PaymentError> { ) -> Result<String, PaymentError> {
let swap_script = swap.get_swap_script()?; let swap_script = SwapScriptV2::Liquid(swap.get_swap_script()?);
let locktime_from_height = let refund_keypair = swap.get_refund_keypair()?;
LockTime::from_height(current_height).map_err(|e| PaymentError::Generic { self.refund_swap_non_cooperative(
err: format!("Cannot convert current block height to lock time: {e:?}"), swap.id.clone(),
})?; swap_script,
&refund_keypair,
info!("locktime info: locktime_from_height = {locktime_from_height:?}, swap_script.locktime = {:?}", swap_script.locktime);
if !utils::is_locktime_expired(locktime_from_height, swap_script.locktime) {
return Err(PaymentError::Generic {
err: format!(
"Cannot refund non-cooperatively. Lock time not elapsed yet. Current tip: {:?}. Script lock time: {:?}",
locktime_from_height, swap_script.locktime
)
});
}
let refund_tx = self.new_refund_tx(swap, &output_address.into())?;
let tx = refund_tx.sign_refund(
&swap
.get_refund_keypair()
.map_err(|e| Error::Generic(e.to_string()))?,
broadcast_fees_sat, broadcast_fees_sat,
None, output_address,
)?; current_height,
let is_lowball = match self.config.network { )
Network::Mainnet => None,
Network::Testnet => Some((&self.client, boltz_client::network::Chain::LiquidTestnet)),
};
let refund_tx_id = refund_tx.broadcast(&tx, &self.electrum_config, is_lowball)?;
info!(
"Successfully broadcast non-cooperative refund for swap-in {}",
swap.id
);
Ok(refund_tx_id)
} }
/// Get claim tx details which includes the preimage as a proof of payment. /// Get claim tx details which includes the preimage as a proof of payment.
/// It is used to validate the preimage before claiming which is the reason why we need to separate /// It is used to validate the preimage before claiming which is the reason why we need to separate
/// the claim into two steps. /// the claim into two steps.
fn get_claim_tx_details(&self, swap: &SendSwap) -> Result<ClaimTxResponse, PaymentError> { fn get_send_claim_tx_details(
let claim_tx_response = self.client.get_claim_tx_details(&swap.id)?; &self,
swap: &SendSwap,
) -> Result<SubmarineClaimTxResponse, PaymentError> {
let claim_tx_response = self.client.get_submarine_claim_tx_details(&swap.id)?;
info!("Received claim tx details: {:?}", &claim_tx_response); info!("Received claim tx details: {:?}", &claim_tx_response);
self.validate_send_swap_preimage(&swap.id, &swap.invoice, &claim_tx_response.preimage)?; self.validate_send_swap_preimage(&swap.id, &swap.invoice, &claim_tx_response.preimage)?;
Ok(claim_tx_response) Ok(claim_tx_response)
} }
/// Claim chain swap.
fn claim_chain_swap(
&self,
swap: &ChainSwap,
refund_address: String,
) -> Result<String, PaymentError> {
let claim_tx_id = match swap.direction {
Direction::Incoming => self.claim_incoming_chain_swap(swap, refund_address),
Direction::Outgoing => self.claim_outgoing_chain_swap(swap, refund_address),
}?;
info!(
"Successfully broadcast claim tx {claim_tx_id} for Chain Swap {}",
swap.id
);
Ok(claim_tx_id)
}
/// Claim send swap cooperatively. Here the remote swapper is the one that claims. /// Claim send swap cooperatively. Here the remote swapper is the one that claims.
/// We are helping to use key spend path for cheaper fees. /// We are helping to use key spend path for cheaper fees.
fn claim_send_swap_cooperative( fn claim_send_swap_cooperative(
&self, &self,
swap: &SendSwap, swap: &SendSwap,
claim_tx_response: ClaimTxResponse, claim_tx_response: SubmarineClaimTxResponse,
output_address: &str, refund_address: &str,
) -> Result<(), PaymentError> { ) -> Result<(), PaymentError> {
let swap_id = &swap.id; let swap_id = &swap.id;
let keypair = swap.get_refund_keypair()?; let keypair = swap.get_refund_keypair()?;
let refund_tx = self.new_refund_tx(swap, &output_address.into())?; let swap_script = SwapScriptV2::Liquid(swap.get_swap_script()?);
let refund_tx = self
.new_refund_tx(swap.id.clone(), swap_script, &refund_address.into())?
.as_liquid_tx()?;
self.validate_send_swap_preimage(swap_id, &swap.invoice, &claim_tx_response.preimage)?; self.validate_send_swap_preimage(swap_id, &swap.invoice, &claim_tx_response.preimage)?;
let (partial_sig, pub_nonce) = let (partial_sig, pub_nonce) = refund_tx.partial_sig(
refund_tx.submarine_partial_sig(&keypair, &claim_tx_response)?; &keypair,
&claim_tx_response.pub_nonce,
&claim_tx_response.transaction_hash,
)?;
self.client self.client.post_submarine_claim_tx_details(
.post_claim_tx_details(&swap_id.to_string(), pub_nonce, partial_sig)?; &swap_id.to_string(),
pub_nonce,
partial_sig,
)?;
info!("Successfully sent claim details for swap-in {swap_id}"); info!("Successfully sent claim details for swap-in {swap_id}");
Ok(()) Ok(())
} }
@@ -312,24 +640,27 @@ impl Swapper for BoltzSwapper {
let claim_tx_wrapper = LBtcSwapTxV2::new_claim( let claim_tx_wrapper = LBtcSwapTxV2::new_claim(
swap_script, swap_script,
claim_address, claim_address,
&self.electrum_config, &self.liquid_electrum_config,
self.config.boltz_url.clone(), self.config.boltz_url.clone(),
swap.id.clone(), swap.id.clone(),
)?; )?;
let cooperative = Some((&self.client, swap.id.clone())); let is_cooperative = Some(Cooperative {
boltz_api: &self.client,
swap_id: swap.id.clone(),
pub_nonce: None,
partial_sig: None,
});
let claim_tx = claim_tx_wrapper.sign_claim( let claim_tx = claim_tx_wrapper.sign_claim(
&swap.get_claim_keypair()?, &swap.get_claim_keypair()?,
&Preimage::from_str(&swap.preimage)?, &Preimage::from_str(&swap.preimage)?,
Amount::from_sat(swap.claim_fees_sat), Amount::from_sat(swap.claim_fees_sat),
// Enable cooperative claim (Some) or not (None) is_cooperative,
cooperative,
// None
)?; )?;
let claim_tx_id = claim_tx_wrapper.broadcast( let claim_tx_id = claim_tx_wrapper.broadcast(
&claim_tx, &claim_tx,
&self.electrum_config, &self.liquid_electrum_config,
Some((&self.client, self.config.network.into())), Some((&self.client, self.config.network.into())),
)?; )?;
info!("Successfully broadcast claim tx {claim_tx_id} for Receive Swap {swap_id}"); info!("Successfully broadcast claim tx {claim_tx_id} for Receive Swap {swap_id}");

View File

@@ -2,7 +2,11 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::{
model::{Config, Network, PaymentState, PaymentTxData, PaymentType, ReceiveSwap, SendSwap}, chain_swap::ChainSwapStateHandler,
model::{
ChainSwap, Config, Direction, Network, PaymentState, PaymentTxData, PaymentType,
ReceiveSwap, SendSwap,
},
persist::Persister, persist::Persister,
receive_swap::ReceiveSwapStateHandler, receive_swap::ReceiveSwapStateHandler,
send_swap::SendSwapStateHandler, send_swap::SendSwapStateHandler,
@@ -15,6 +19,7 @@ use anyhow::{anyhow, Result};
use bip39::rand::{self, distributions::Alphanumeric, Rng}; use bip39::rand::{self, distributions::Alphanumeric, Rng};
use lwk_wollet::{ElectrumClient, ElectrumUrl}; use lwk_wollet::{ElectrumClient, ElectrumUrl};
use tempdir::TempDir; use tempdir::TempDir;
use tokio::sync::Mutex;
pub(crate) const TEST_MNEMONIC: &str = pub(crate) const TEST_MNEMONIC: &str =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
@@ -25,11 +30,11 @@ pub(crate) fn new_send_swap_state_handler(
let config = Config::testnet(); let config = Config::testnet();
let onchain_wallet = Arc::new(new_onchain_wallet(&config)?); let onchain_wallet = Arc::new(new_onchain_wallet(&config)?);
let swapper = Arc::new(BoltzSwapper::new(config.clone())); let swapper = Arc::new(BoltzSwapper::new(config.clone()));
let chain_service = Arc::new(ElectrumClient::new(&ElectrumUrl::new( let chain_service = Arc::new(Mutex::new(ElectrumClient::new(&ElectrumUrl::new(
&config.electrum_url, &config.liquid_electrum_url,
true, true,
true, true,
))?); ))?));
Ok(SendSwapStateHandler::new( Ok(SendSwapStateHandler::new(
config, config,
@@ -55,6 +60,27 @@ pub(crate) fn new_receive_swap_state_handler(
)) ))
} }
pub(crate) fn new_chain_swap_state_handler(
persister: Arc<Persister>,
) -> Result<ChainSwapStateHandler> {
let config = Config::testnet();
let onchain_wallet = Arc::new(new_onchain_wallet(&config)?);
let swapper = Arc::new(BoltzSwapper::new(config.clone()));
let liquid_chain_service = Arc::new(Mutex::new(ElectrumClient::new(&ElectrumUrl::new(
&config.liquid_electrum_url,
true,
true,
))?));
ChainSwapStateHandler::new(
config,
onchain_wallet,
persister,
swapper,
liquid_chain_service,
)
}
pub(crate) fn new_send_swap(payment_state: Option<PaymentState>) -> SendSwap { pub(crate) fn new_send_swap(payment_state: Option<PaymentState>) -> SendSwap {
let id = rand::thread_rng() let id = rand::thread_rng()
.sample_iter(&Alphanumeric) .sample_iter(&Alphanumeric)
@@ -102,6 +128,33 @@ pub(crate) fn new_receive_swap(payment_state: Option<PaymentState>) -> ReceiveSw
} }
} }
pub(crate) fn new_chain_swap(payment_state: Option<PaymentState>) -> ChainSwap {
let id = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(4)
.map(char::from)
.collect();
ChainSwap {
id,
direction: Direction::Incoming,
address: "".to_string(),
preimage: "".to_string(),
create_response_json: "{}".to_string(),
claim_private_key: "".to_string(),
refund_private_key: "".to_string(),
payer_amount_sat: 0,
receiver_amount_sat: 0,
claim_fees_sat: 0,
server_lockup_tx_id: None,
user_lockup_tx_id: None,
claim_tx_id: None,
refund_tx_id: None,
created_at: utils::now(),
state: payment_state.unwrap_or(PaymentState::Created),
accept_zero_conf: false,
}
}
pub(crate) fn new_persister() -> Result<(TempDir, Persister)> { pub(crate) fn new_persister() -> Result<(TempDir, Persister)> {
let temp_dir = TempDir::new("liquid-sdk")?; let temp_dir = TempDir::new("liquid-sdk")?;
let persister = Persister::new( let persister = Persister::new(

View File

@@ -137,8 +137,11 @@ impl OnchainWallet for LiquidOnchainWallet {
/// Perform a full scan of the wallet /// Perform a full scan of the wallet
async fn full_scan(&self) -> Result<(), PaymentError> { async fn full_scan(&self) -> Result<(), PaymentError> {
let mut wallet = self.wallet.lock().await; let mut wallet = self.wallet.lock().await;
let mut electrum_client = let mut electrum_client = ElectrumClient::new(&ElectrumUrl::new(
ElectrumClient::new(&ElectrumUrl::new(&self.config.electrum_url, true, true))?; &self.config.liquid_electrum_url,
true,
true,
))?;
lwk_wollet::full_scan_with_electrum_client(&mut wallet, &mut electrum_client)?; lwk_wollet::full_scan_with_electrum_client(&mut wallet, &mut electrum_client)?;
Ok(()) Ok(())
} }

View File

@@ -37,6 +37,10 @@ abstract class BindingLiquidSdk implements RustOpaqueInterface {
Future<List<Payment>> listPayments(); Future<List<Payment>> listPayments();
Future<SendPaymentResponse> payOnchain({required PayOnchainRequest req});
Future<PreparePayOnchainResponse> preparePayOnchain({required PreparePayOnchainRequest req});
Future<PrepareReceiveResponse> prepareReceivePayment({required PrepareReceiveRequest req}); Future<PrepareReceiveResponse> prepareReceivePayment({required PrepareReceiveRequest req});
Future<PrepareSendResponse> prepareSendPayment({required PrepareSendRequest req}); Future<PrepareSendResponse> prepareSendPayment({required PrepareSendRequest req});

View File

@@ -17,6 +17,9 @@ sealed class LiquidSdkError with _$LiquidSdkError implements FrbException {
required String err, required String err,
}) = LiquidSdkError_Generic; }) = LiquidSdkError_Generic;
const factory LiquidSdkError.notStarted() = LiquidSdkError_NotStarted; const factory LiquidSdkError.notStarted() = LiquidSdkError_NotStarted;
const factory LiquidSdkError.serviceConnectivity({
required String err,
}) = LiquidSdkError_ServiceConnectivity;
} }
@freezed @freezed

View File

@@ -188,6 +188,80 @@ abstract class LiquidSdkError_NotStarted extends LiquidSdkError {
const LiquidSdkError_NotStarted._() : super._(); const LiquidSdkError_NotStarted._() : super._();
} }
/// @nodoc
abstract class _$$LiquidSdkError_ServiceConnectivityImplCopyWith<$Res> {
factory _$$LiquidSdkError_ServiceConnectivityImplCopyWith(_$LiquidSdkError_ServiceConnectivityImpl value,
$Res Function(_$LiquidSdkError_ServiceConnectivityImpl) then) =
__$$LiquidSdkError_ServiceConnectivityImplCopyWithImpl<$Res>;
@useResult
$Res call({String err});
}
/// @nodoc
class __$$LiquidSdkError_ServiceConnectivityImplCopyWithImpl<$Res>
extends _$LiquidSdkErrorCopyWithImpl<$Res, _$LiquidSdkError_ServiceConnectivityImpl>
implements _$$LiquidSdkError_ServiceConnectivityImplCopyWith<$Res> {
__$$LiquidSdkError_ServiceConnectivityImplCopyWithImpl(_$LiquidSdkError_ServiceConnectivityImpl _value,
$Res Function(_$LiquidSdkError_ServiceConnectivityImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? err = null,
}) {
return _then(_$LiquidSdkError_ServiceConnectivityImpl(
err: null == err
? _value.err
: err // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$LiquidSdkError_ServiceConnectivityImpl extends LiquidSdkError_ServiceConnectivity {
const _$LiquidSdkError_ServiceConnectivityImpl({required this.err}) : super._();
@override
final String err;
@override
String toString() {
return 'LiquidSdkError.serviceConnectivity(err: $err)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LiquidSdkError_ServiceConnectivityImpl &&
(identical(other.err, err) || other.err == err));
}
@override
int get hashCode => Object.hash(runtimeType, err);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$LiquidSdkError_ServiceConnectivityImplCopyWith<_$LiquidSdkError_ServiceConnectivityImpl> get copyWith =>
__$$LiquidSdkError_ServiceConnectivityImplCopyWithImpl<_$LiquidSdkError_ServiceConnectivityImpl>(
this, _$identity);
}
abstract class LiquidSdkError_ServiceConnectivity extends LiquidSdkError {
const factory LiquidSdkError_ServiceConnectivity({required final String err}) =
_$LiquidSdkError_ServiceConnectivityImpl;
const LiquidSdkError_ServiceConnectivity._() : super._();
String get err;
@JsonKey(ignore: true)
_$$LiquidSdkError_ServiceConnectivityImplCopyWith<_$LiquidSdkError_ServiceConnectivityImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc /// @nodoc
mixin _$PaymentError {} mixin _$PaymentError {}

View File

@@ -54,7 +54,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
String get codegenVersion => '2.0.0-dev.38'; String get codegenVersion => '2.0.0-dev.38';
@override @override
int get rustContentHash => 308302012; int get rustContentHash => -107248138;
static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig(
stem: 'breez_liquid_sdk', stem: 'breez_liquid_sdk',
@@ -76,6 +76,12 @@ abstract class RustLibApi extends BaseApi {
Future<List<Payment>> crateBindingsBindingLiquidSdkListPayments({required BindingLiquidSdk that}); Future<List<Payment>> crateBindingsBindingLiquidSdkListPayments({required BindingLiquidSdk that});
Future<SendPaymentResponse> crateBindingsBindingLiquidSdkPayOnchain(
{required BindingLiquidSdk that, required PayOnchainRequest req});
Future<PreparePayOnchainResponse> crateBindingsBindingLiquidSdkPreparePayOnchain(
{required BindingLiquidSdk that, required PreparePayOnchainRequest req});
Future<PrepareReceiveResponse> crateBindingsBindingLiquidSdkPrepareReceivePayment( Future<PrepareReceiveResponse> crateBindingsBindingLiquidSdkPrepareReceivePayment(
{required BindingLiquidSdk that, required PrepareReceiveRequest req}); {required BindingLiquidSdk that, required PrepareReceiveRequest req});
@@ -266,6 +272,58 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["that"], argNames: ["that"],
); );
@override
Future<SendPaymentResponse> crateBindingsBindingLiquidSdkPayOnchain(
{required BindingLiquidSdk that, required PayOnchainRequest req}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 =
cst_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk(
that);
var arg1 = cst_encode_box_autoadd_pay_onchain_request(req);
return wire.wire__crate__bindings__BindingLiquidSdk_pay_onchain(port_, arg0, arg1);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_send_payment_response,
decodeErrorData: dco_decode_payment_error,
),
constMeta: kCrateBindingsBindingLiquidSdkPayOnchainConstMeta,
argValues: [that, req],
apiImpl: this,
));
}
TaskConstMeta get kCrateBindingsBindingLiquidSdkPayOnchainConstMeta => const TaskConstMeta(
debugName: "BindingLiquidSdk_pay_onchain",
argNames: ["that", "req"],
);
@override
Future<PreparePayOnchainResponse> crateBindingsBindingLiquidSdkPreparePayOnchain(
{required BindingLiquidSdk that, required PreparePayOnchainRequest req}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 =
cst_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerBindingLiquidSdk(
that);
var arg1 = cst_encode_box_autoadd_prepare_pay_onchain_request(req);
return wire.wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain(port_, arg0, arg1);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_prepare_pay_onchain_response,
decodeErrorData: dco_decode_payment_error,
),
constMeta: kCrateBindingsBindingLiquidSdkPreparePayOnchainConstMeta,
argValues: [that, req],
apiImpl: this,
));
}
TaskConstMeta get kCrateBindingsBindingLiquidSdkPreparePayOnchainConstMeta => const TaskConstMeta(
debugName: "BindingLiquidSdk_prepare_pay_onchain",
argNames: ["that", "req"],
);
@override @override
Future<PrepareReceiveResponse> crateBindingsBindingLiquidSdkPrepareReceivePayment( Future<PrepareReceiveResponse> crateBindingsBindingLiquidSdkPrepareReceivePayment(
{required BindingLiquidSdk that, required PrepareReceiveRequest req}) { {required BindingLiquidSdk that, required PrepareReceiveRequest req}) {
@@ -631,12 +689,24 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return dco_decode_liquid_sdk_event(raw); return dco_decode_liquid_sdk_event(raw);
} }
@protected
PayOnchainRequest dco_decode_box_autoadd_pay_onchain_request(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dco_decode_pay_onchain_request(raw);
}
@protected @protected
Payment dco_decode_box_autoadd_payment(dynamic raw) { Payment dco_decode_box_autoadd_payment(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
return dco_decode_payment(raw); return dco_decode_payment(raw);
} }
@protected
PreparePayOnchainRequest dco_decode_box_autoadd_prepare_pay_onchain_request(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dco_decode_prepare_pay_onchain_request(raw);
}
@protected @protected
PrepareReceiveRequest dco_decode_box_autoadd_prepare_receive_request(dynamic raw) { PrepareReceiveRequest dco_decode_box_autoadd_prepare_receive_request(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
@@ -677,15 +747,16 @@ 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 != 7) throw Exception('unexpected arr length: expect 7 but see ${arr.length}'); if (arr.length != 8) throw Exception('unexpected arr length: expect 8 but see ${arr.length}');
return Config( return Config(
boltzUrl: dco_decode_String(arr[0]), boltzUrl: dco_decode_String(arr[0]),
electrumUrl: dco_decode_String(arr[1]), liquidElectrumUrl: dco_decode_String(arr[1]),
workingDir: dco_decode_String(arr[2]), bitcoinElectrumUrl: dco_decode_String(arr[2]),
network: dco_decode_network(arr[3]), workingDir: dco_decode_String(arr[3]),
paymentTimeoutSec: dco_decode_u_64(arr[4]), network: dco_decode_network(arr[4]),
zeroConfMinFeeRate: dco_decode_f_32(arr[5]), paymentTimeoutSec: dco_decode_u_64(arr[5]),
zeroConfMaxAmountSat: dco_decode_opt_box_autoadd_u_64(arr[6]), zeroConfMinFeeRate: dco_decode_f_32(arr[6]),
zeroConfMaxAmountSat: dco_decode_opt_box_autoadd_u_64(arr[7]),
); );
} }
@@ -737,6 +808,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
case 2: case 2:
return LiquidSdkError_NotStarted(); return LiquidSdkError_NotStarted();
case 3:
return LiquidSdkError_ServiceConnectivity(
err: dco_decode_String(raw[1]),
);
default: default:
throw Exception("unreachable"); throw Exception("unreachable");
} }
@@ -851,6 +926,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw == null ? null : dco_decode_box_autoadd_u_64(raw); return raw == null ? null : dco_decode_box_autoadd_u_64(raw);
} }
@protected
PayOnchainRequest dco_decode_pay_onchain_request(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}');
return PayOnchainRequest(
address: dco_decode_String(arr[0]),
prepareRes: dco_decode_prepare_pay_onchain_response(arr[1]),
);
}
@protected @protected
Payment dco_decode_payment(dynamic raw) { Payment dco_decode_payment(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
@@ -943,6 +1029,27 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return PaymentType.values[raw as int]; return PaymentType.values[raw as int];
} }
@protected
PreparePayOnchainRequest dco_decode_prepare_pay_onchain_request(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 1) throw Exception('unexpected arr length: expect 1 but see ${arr.length}');
return PreparePayOnchainRequest(
amountSat: dco_decode_u_64(arr[0]),
);
}
@protected
PreparePayOnchainResponse dco_decode_prepare_pay_onchain_response(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}');
return PreparePayOnchainResponse(
amountSat: dco_decode_u_64(arr[0]),
feesSat: dco_decode_u_64(arr[1]),
);
}
@protected @protected
PrepareReceiveRequest dco_decode_prepare_receive_request(dynamic raw) { PrepareReceiveRequest dco_decode_prepare_receive_request(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
@@ -1162,12 +1269,24 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return (sse_decode_liquid_sdk_event(deserializer)); return (sse_decode_liquid_sdk_event(deserializer));
} }
@protected
PayOnchainRequest sse_decode_box_autoadd_pay_onchain_request(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_pay_onchain_request(deserializer));
}
@protected @protected
Payment sse_decode_box_autoadd_payment(SseDeserializer deserializer) { Payment sse_decode_box_autoadd_payment(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_payment(deserializer)); return (sse_decode_payment(deserializer));
} }
@protected
PreparePayOnchainRequest sse_decode_box_autoadd_prepare_pay_onchain_request(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_prepare_pay_onchain_request(deserializer));
}
@protected @protected
PrepareReceiveRequest sse_decode_box_autoadd_prepare_receive_request(SseDeserializer deserializer) { PrepareReceiveRequest sse_decode_box_autoadd_prepare_receive_request(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@@ -1208,7 +1327,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
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_boltzUrl = sse_decode_String(deserializer); var var_boltzUrl = sse_decode_String(deserializer);
var var_electrumUrl = sse_decode_String(deserializer); var var_liquidElectrumUrl = sse_decode_String(deserializer);
var var_bitcoinElectrumUrl = sse_decode_String(deserializer);
var var_workingDir = sse_decode_String(deserializer); var var_workingDir = sse_decode_String(deserializer);
var var_network = sse_decode_network(deserializer); var var_network = sse_decode_network(deserializer);
var var_paymentTimeoutSec = sse_decode_u_64(deserializer); var var_paymentTimeoutSec = sse_decode_u_64(deserializer);
@@ -1216,7 +1336,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_zeroConfMaxAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer); var var_zeroConfMaxAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer);
return Config( return Config(
boltzUrl: var_boltzUrl, boltzUrl: var_boltzUrl,
electrumUrl: var_electrumUrl, liquidElectrumUrl: var_liquidElectrumUrl,
bitcoinElectrumUrl: var_bitcoinElectrumUrl,
workingDir: var_workingDir, workingDir: var_workingDir,
network: var_network, network: var_network,
paymentTimeoutSec: var_paymentTimeoutSec, paymentTimeoutSec: var_paymentTimeoutSec,
@@ -1271,6 +1392,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return LiquidSdkError_Generic(err: var_err); return LiquidSdkError_Generic(err: var_err);
case 2: case 2:
return LiquidSdkError_NotStarted(); return LiquidSdkError_NotStarted();
case 3:
var var_err = sse_decode_String(deserializer);
return LiquidSdkError_ServiceConnectivity(err: var_err);
default: default:
throw UnimplementedError(''); throw UnimplementedError('');
} }
@@ -1417,6 +1541,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
} }
} }
@protected
PayOnchainRequest sse_decode_pay_onchain_request(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_address = sse_decode_String(deserializer);
var var_prepareRes = sse_decode_prepare_pay_onchain_response(deserializer);
return PayOnchainRequest(address: var_address, prepareRes: var_prepareRes);
}
@protected @protected
Payment sse_decode_payment(SseDeserializer deserializer) { Payment sse_decode_payment(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@@ -1514,6 +1646,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return PaymentType.values[inner]; return PaymentType.values[inner];
} }
@protected
PreparePayOnchainRequest sse_decode_prepare_pay_onchain_request(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_amountSat = sse_decode_u_64(deserializer);
return PreparePayOnchainRequest(amountSat: var_amountSat);
}
@protected
PreparePayOnchainResponse sse_decode_prepare_pay_onchain_response(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_amountSat = sse_decode_u_64(deserializer);
var var_feesSat = sse_decode_u_64(deserializer);
return PreparePayOnchainResponse(amountSat: var_amountSat, feesSat: var_feesSat);
}
@protected @protected
PrepareReceiveRequest sse_decode_prepare_receive_request(SseDeserializer deserializer) { PrepareReceiveRequest sse_decode_prepare_receive_request(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@@ -1788,12 +1935,25 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_liquid_sdk_event(self, serializer); sse_encode_liquid_sdk_event(self, serializer);
} }
@protected
void sse_encode_box_autoadd_pay_onchain_request(PayOnchainRequest self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_pay_onchain_request(self, serializer);
}
@protected @protected
void sse_encode_box_autoadd_payment(Payment self, SseSerializer serializer) { void sse_encode_box_autoadd_payment(Payment 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_payment(self, serializer); sse_encode_payment(self, serializer);
} }
@protected
void sse_encode_box_autoadd_prepare_pay_onchain_request(
PreparePayOnchainRequest self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_prepare_pay_onchain_request(self, serializer);
}
@protected @protected
void sse_encode_box_autoadd_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer) { void sse_encode_box_autoadd_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@@ -1835,7 +1995,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
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.boltzUrl, serializer); sse_encode_String(self.boltzUrl, serializer);
sse_encode_String(self.electrumUrl, serializer); sse_encode_String(self.liquidElectrumUrl, serializer);
sse_encode_String(self.bitcoinElectrumUrl, serializer);
sse_encode_String(self.workingDir, serializer); sse_encode_String(self.workingDir, serializer);
sse_encode_network(self.network, serializer); sse_encode_network(self.network, serializer);
sse_encode_u_64(self.paymentTimeoutSec, serializer); sse_encode_u_64(self.paymentTimeoutSec, serializer);
@@ -1882,6 +2043,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_String(err, serializer); sse_encode_String(err, serializer);
case LiquidSdkError_NotStarted(): case LiquidSdkError_NotStarted():
sse_encode_i_32(2, serializer); sse_encode_i_32(2, serializer);
case LiquidSdkError_ServiceConnectivity(err: final err):
sse_encode_i_32(3, serializer);
sse_encode_String(err, serializer);
default: default:
throw UnimplementedError(''); throw UnimplementedError('');
} }
@@ -2000,6 +2164,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
} }
} }
@protected
void sse_encode_pay_onchain_request(PayOnchainRequest self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_String(self.address, serializer);
sse_encode_prepare_pay_onchain_response(self.prepareRes, serializer);
}
@protected @protected
void sse_encode_payment(Payment self, SseSerializer serializer) { void sse_encode_payment(Payment self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@@ -2081,6 +2252,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_i_32(self.index, serializer); sse_encode_i_32(self.index, serializer);
} }
@protected
void sse_encode_prepare_pay_onchain_request(PreparePayOnchainRequest self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_u_64(self.amountSat, serializer);
}
@protected
void sse_encode_prepare_pay_onchain_response(PreparePayOnchainResponse self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_u_64(self.amountSat, serializer);
sse_encode_u_64(self.feesSat, serializer);
}
@protected @protected
void sse_encode_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer) { void sse_encode_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@@ -2220,6 +2404,12 @@ class BindingLiquidSdkImpl extends RustOpaque implements BindingLiquidSdk {
that: this, that: this,
); );
Future<SendPaymentResponse> payOnchain({required PayOnchainRequest req}) =>
RustLib.instance.api.crateBindingsBindingLiquidSdkPayOnchain(that: this, req: req);
Future<PreparePayOnchainResponse> preparePayOnchain({required PreparePayOnchainRequest req}) =>
RustLib.instance.api.crateBindingsBindingLiquidSdkPreparePayOnchain(that: this, req: req);
Future<PrepareReceiveResponse> prepareReceivePayment({required PrepareReceiveRequest req}) => Future<PrepareReceiveResponse> prepareReceivePayment({required PrepareReceiveRequest req}) =>
RustLib.instance.api.crateBindingsBindingLiquidSdkPrepareReceivePayment(that: this, req: req); RustLib.instance.api.crateBindingsBindingLiquidSdkPrepareReceivePayment(that: this, req: req);

View File

@@ -67,9 +67,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
LiquidSdkEvent dco_decode_box_autoadd_liquid_sdk_event(dynamic raw); LiquidSdkEvent dco_decode_box_autoadd_liquid_sdk_event(dynamic raw);
@protected
PayOnchainRequest dco_decode_box_autoadd_pay_onchain_request(dynamic raw);
@protected @protected
Payment dco_decode_box_autoadd_payment(dynamic raw); Payment dco_decode_box_autoadd_payment(dynamic raw);
@protected
PreparePayOnchainRequest dco_decode_box_autoadd_prepare_pay_onchain_request(dynamic raw);
@protected @protected
PrepareReceiveRequest dco_decode_box_autoadd_prepare_receive_request(dynamic raw); PrepareReceiveRequest dco_decode_box_autoadd_prepare_receive_request(dynamic raw);
@@ -136,6 +142,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw); BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw);
@protected
PayOnchainRequest dco_decode_pay_onchain_request(dynamic raw);
@protected @protected
Payment dco_decode_payment(dynamic raw); Payment dco_decode_payment(dynamic raw);
@@ -148,6 +157,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
PaymentType dco_decode_payment_type(dynamic raw); PaymentType dco_decode_payment_type(dynamic raw);
@protected
PreparePayOnchainRequest dco_decode_prepare_pay_onchain_request(dynamic raw);
@protected
PreparePayOnchainResponse dco_decode_prepare_pay_onchain_response(dynamic raw);
@protected @protected
PrepareReceiveRequest dco_decode_prepare_receive_request(dynamic raw); PrepareReceiveRequest dco_decode_prepare_receive_request(dynamic raw);
@@ -234,9 +249,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
LiquidSdkEvent sse_decode_box_autoadd_liquid_sdk_event(SseDeserializer deserializer); LiquidSdkEvent sse_decode_box_autoadd_liquid_sdk_event(SseDeserializer deserializer);
@protected
PayOnchainRequest sse_decode_box_autoadd_pay_onchain_request(SseDeserializer deserializer);
@protected @protected
Payment sse_decode_box_autoadd_payment(SseDeserializer deserializer); Payment sse_decode_box_autoadd_payment(SseDeserializer deserializer);
@protected
PreparePayOnchainRequest sse_decode_box_autoadd_prepare_pay_onchain_request(SseDeserializer deserializer);
@protected @protected
PrepareReceiveRequest sse_decode_box_autoadd_prepare_receive_request(SseDeserializer deserializer); PrepareReceiveRequest sse_decode_box_autoadd_prepare_receive_request(SseDeserializer deserializer);
@@ -303,6 +324,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer); BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer);
@protected
PayOnchainRequest sse_decode_pay_onchain_request(SseDeserializer deserializer);
@protected @protected
Payment sse_decode_payment(SseDeserializer deserializer); Payment sse_decode_payment(SseDeserializer deserializer);
@@ -315,6 +339,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
PaymentType sse_decode_payment_type(SseDeserializer deserializer); PaymentType sse_decode_payment_type(SseDeserializer deserializer);
@protected
PreparePayOnchainRequest sse_decode_prepare_pay_onchain_request(SseDeserializer deserializer);
@protected
PreparePayOnchainResponse sse_decode_prepare_pay_onchain_response(SseDeserializer deserializer);
@protected @protected
PrepareReceiveRequest sse_decode_prepare_receive_request(SseDeserializer deserializer); PrepareReceiveRequest sse_decode_prepare_receive_request(SseDeserializer deserializer);
@@ -421,6 +451,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return ptr; return ptr;
} }
@protected
ffi.Pointer<wire_cst_pay_onchain_request> cst_encode_box_autoadd_pay_onchain_request(
PayOnchainRequest raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
final ptr = wire.cst_new_box_autoadd_pay_onchain_request();
cst_api_fill_to_wire_pay_onchain_request(raw, ptr.ref);
return ptr;
}
@protected @protected
ffi.Pointer<wire_cst_payment> cst_encode_box_autoadd_payment(Payment raw) { ffi.Pointer<wire_cst_payment> cst_encode_box_autoadd_payment(Payment raw) {
// Codec=Cst (C-struct based), see doc to use other codecs // Codec=Cst (C-struct based), see doc to use other codecs
@@ -429,6 +468,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return ptr; return ptr;
} }
@protected
ffi.Pointer<wire_cst_prepare_pay_onchain_request> cst_encode_box_autoadd_prepare_pay_onchain_request(
PreparePayOnchainRequest raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
final ptr = wire.cst_new_box_autoadd_prepare_pay_onchain_request();
cst_api_fill_to_wire_prepare_pay_onchain_request(raw, ptr.ref);
return ptr;
}
@protected @protected
ffi.Pointer<wire_cst_prepare_receive_request> cst_encode_box_autoadd_prepare_receive_request( ffi.Pointer<wire_cst_prepare_receive_request> cst_encode_box_autoadd_prepare_receive_request(
PrepareReceiveRequest raw) { PrepareReceiveRequest raw) {
@@ -576,11 +624,23 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
cst_api_fill_to_wire_liquid_sdk_event(apiObj, wireObj.ref); cst_api_fill_to_wire_liquid_sdk_event(apiObj, wireObj.ref);
} }
@protected
void cst_api_fill_to_wire_box_autoadd_pay_onchain_request(
PayOnchainRequest apiObj, ffi.Pointer<wire_cst_pay_onchain_request> wireObj) {
cst_api_fill_to_wire_pay_onchain_request(apiObj, wireObj.ref);
}
@protected @protected
void cst_api_fill_to_wire_box_autoadd_payment(Payment apiObj, ffi.Pointer<wire_cst_payment> wireObj) { void cst_api_fill_to_wire_box_autoadd_payment(Payment apiObj, ffi.Pointer<wire_cst_payment> wireObj) {
cst_api_fill_to_wire_payment(apiObj, wireObj.ref); cst_api_fill_to_wire_payment(apiObj, wireObj.ref);
} }
@protected
void cst_api_fill_to_wire_box_autoadd_prepare_pay_onchain_request(
PreparePayOnchainRequest apiObj, ffi.Pointer<wire_cst_prepare_pay_onchain_request> wireObj) {
cst_api_fill_to_wire_prepare_pay_onchain_request(apiObj, wireObj.ref);
}
@protected @protected
void cst_api_fill_to_wire_box_autoadd_prepare_receive_request( void cst_api_fill_to_wire_box_autoadd_prepare_receive_request(
PrepareReceiveRequest apiObj, ffi.Pointer<wire_cst_prepare_receive_request> wireObj) { PrepareReceiveRequest apiObj, ffi.Pointer<wire_cst_prepare_receive_request> wireObj) {
@@ -614,7 +674,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.boltz_url = cst_encode_String(apiObj.boltzUrl); wireObj.boltz_url = cst_encode_String(apiObj.boltzUrl);
wireObj.electrum_url = cst_encode_String(apiObj.electrumUrl); wireObj.liquid_electrum_url = cst_encode_String(apiObj.liquidElectrumUrl);
wireObj.bitcoin_electrum_url = cst_encode_String(apiObj.bitcoinElectrumUrl);
wireObj.working_dir = cst_encode_String(apiObj.workingDir); wireObj.working_dir = cst_encode_String(apiObj.workingDir);
wireObj.network = cst_encode_network(apiObj.network); wireObj.network = cst_encode_network(apiObj.network);
wireObj.payment_timeout_sec = cst_encode_u_64(apiObj.paymentTimeoutSec); wireObj.payment_timeout_sec = cst_encode_u_64(apiObj.paymentTimeoutSec);
@@ -652,6 +713,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
wireObj.tag = 2; wireObj.tag = 2;
return; return;
} }
if (apiObj is LiquidSdkError_ServiceConnectivity) {
var pre_err = cst_encode_String(apiObj.err);
wireObj.tag = 3;
wireObj.kind.ServiceConnectivity.err = pre_err;
return;
}
} }
@protected @protected
@@ -720,6 +787,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
wireObj.level = cst_encode_String(apiObj.level); wireObj.level = cst_encode_String(apiObj.level);
} }
@protected
void cst_api_fill_to_wire_pay_onchain_request(
PayOnchainRequest apiObj, wire_cst_pay_onchain_request wireObj) {
wireObj.address = cst_encode_String(apiObj.address);
cst_api_fill_to_wire_prepare_pay_onchain_response(apiObj.prepareRes, wireObj.prepare_res);
}
@protected @protected
void cst_api_fill_to_wire_payment(Payment apiObj, wire_cst_payment wireObj) { void cst_api_fill_to_wire_payment(Payment apiObj, wire_cst_payment wireObj) {
wireObj.tx_id = cst_encode_opt_String(apiObj.txId); wireObj.tx_id = cst_encode_opt_String(apiObj.txId);
@@ -827,6 +901,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
} }
} }
@protected
void cst_api_fill_to_wire_prepare_pay_onchain_request(
PreparePayOnchainRequest apiObj, wire_cst_prepare_pay_onchain_request wireObj) {
wireObj.amount_sat = cst_encode_u_64(apiObj.amountSat);
}
@protected
void cst_api_fill_to_wire_prepare_pay_onchain_response(
PreparePayOnchainResponse apiObj, wire_cst_prepare_pay_onchain_response wireObj) {
wireObj.amount_sat = cst_encode_u_64(apiObj.amountSat);
wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat);
}
@protected @protected
void cst_api_fill_to_wire_prepare_receive_request( void cst_api_fill_to_wire_prepare_receive_request(
PrepareReceiveRequest apiObj, wire_cst_prepare_receive_request wireObj) { PrepareReceiveRequest apiObj, wire_cst_prepare_receive_request wireObj) {
@@ -966,9 +1053,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
void sse_encode_box_autoadd_liquid_sdk_event(LiquidSdkEvent self, SseSerializer serializer); void sse_encode_box_autoadd_liquid_sdk_event(LiquidSdkEvent self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_pay_onchain_request(PayOnchainRequest self, SseSerializer serializer);
@protected @protected
void sse_encode_box_autoadd_payment(Payment self, SseSerializer serializer); void sse_encode_box_autoadd_payment(Payment self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_prepare_pay_onchain_request(
PreparePayOnchainRequest self, SseSerializer serializer);
@protected @protected
void sse_encode_box_autoadd_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer); void sse_encode_box_autoadd_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer);
@@ -1035,6 +1129,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
void sse_encode_opt_box_autoadd_u_64(BigInt? self, SseSerializer serializer); void sse_encode_opt_box_autoadd_u_64(BigInt? self, SseSerializer serializer);
@protected
void sse_encode_pay_onchain_request(PayOnchainRequest self, SseSerializer serializer);
@protected @protected
void sse_encode_payment(Payment self, SseSerializer serializer); void sse_encode_payment(Payment self, SseSerializer serializer);
@@ -1047,6 +1144,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
void sse_encode_payment_type(PaymentType self, SseSerializer serializer); void sse_encode_payment_type(PaymentType self, SseSerializer serializer);
@protected
void sse_encode_prepare_pay_onchain_request(PreparePayOnchainRequest self, SseSerializer serializer);
@protected
void sse_encode_prepare_pay_onchain_response(PreparePayOnchainResponse self, SseSerializer serializer);
@protected @protected
void sse_encode_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer); void sse_encode_prepare_receive_request(PrepareReceiveRequest self, SseSerializer serializer);
@@ -1229,6 +1332,46 @@ class RustLibWire implements BaseWire {
late final _wire__crate__bindings__BindingLiquidSdk_list_payments = late final _wire__crate__bindings__BindingLiquidSdk_list_payments =
_wire__crate__bindings__BindingLiquidSdk_list_paymentsPtr.asFunction<void Function(int, int)>(); _wire__crate__bindings__BindingLiquidSdk_list_paymentsPtr.asFunction<void Function(int, int)>();
void wire__crate__bindings__BindingLiquidSdk_pay_onchain(
int port_,
int that,
ffi.Pointer<wire_cst_pay_onchain_request> req,
) {
return _wire__crate__bindings__BindingLiquidSdk_pay_onchain(
port_,
that,
req,
);
}
late final _wire__crate__bindings__BindingLiquidSdk_pay_onchainPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Int64, ffi.UintPtr, ffi.Pointer<wire_cst_pay_onchain_request>)>>(
'frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_pay_onchain');
late final _wire__crate__bindings__BindingLiquidSdk_pay_onchain =
_wire__crate__bindings__BindingLiquidSdk_pay_onchainPtr
.asFunction<void Function(int, int, ffi.Pointer<wire_cst_pay_onchain_request>)>();
void wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain(
int port_,
int that,
ffi.Pointer<wire_cst_prepare_pay_onchain_request> req,
) {
return _wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain(
port_,
that,
req,
);
}
late final _wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchainPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Int64, ffi.UintPtr, ffi.Pointer<wire_cst_prepare_pay_onchain_request>)>>(
'frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain');
late final _wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain =
_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchainPtr
.asFunction<void Function(int, int, ffi.Pointer<wire_cst_prepare_pay_onchain_request>)>();
void wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment( void wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment(
int port_, int port_,
int that, int that,
@@ -1497,6 +1640,16 @@ class RustLibWire implements BaseWire {
late final _cst_new_box_autoadd_liquid_sdk_event = _cst_new_box_autoadd_liquid_sdk_eventPtr late final _cst_new_box_autoadd_liquid_sdk_event = _cst_new_box_autoadd_liquid_sdk_eventPtr
.asFunction<ffi.Pointer<wire_cst_liquid_sdk_event> Function()>(); .asFunction<ffi.Pointer<wire_cst_liquid_sdk_event> Function()>();
ffi.Pointer<wire_cst_pay_onchain_request> cst_new_box_autoadd_pay_onchain_request() {
return _cst_new_box_autoadd_pay_onchain_request();
}
late final _cst_new_box_autoadd_pay_onchain_requestPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<wire_cst_pay_onchain_request> Function()>>(
'frbgen_breez_liquid_cst_new_box_autoadd_pay_onchain_request');
late final _cst_new_box_autoadd_pay_onchain_request = _cst_new_box_autoadd_pay_onchain_requestPtr
.asFunction<ffi.Pointer<wire_cst_pay_onchain_request> Function()>();
ffi.Pointer<wire_cst_payment> cst_new_box_autoadd_payment() { ffi.Pointer<wire_cst_payment> cst_new_box_autoadd_payment() {
return _cst_new_box_autoadd_payment(); return _cst_new_box_autoadd_payment();
} }
@@ -1507,6 +1660,17 @@ class RustLibWire implements BaseWire {
late final _cst_new_box_autoadd_payment = late final _cst_new_box_autoadd_payment =
_cst_new_box_autoadd_paymentPtr.asFunction<ffi.Pointer<wire_cst_payment> Function()>(); _cst_new_box_autoadd_paymentPtr.asFunction<ffi.Pointer<wire_cst_payment> Function()>();
ffi.Pointer<wire_cst_prepare_pay_onchain_request> cst_new_box_autoadd_prepare_pay_onchain_request() {
return _cst_new_box_autoadd_prepare_pay_onchain_request();
}
late final _cst_new_box_autoadd_prepare_pay_onchain_requestPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<wire_cst_prepare_pay_onchain_request> Function()>>(
'frbgen_breez_liquid_cst_new_box_autoadd_prepare_pay_onchain_request');
late final _cst_new_box_autoadd_prepare_pay_onchain_request =
_cst_new_box_autoadd_prepare_pay_onchain_requestPtr
.asFunction<ffi.Pointer<wire_cst_prepare_pay_onchain_request> Function()>();
ffi.Pointer<wire_cst_prepare_receive_request> cst_new_box_autoadd_prepare_receive_request() { ffi.Pointer<wire_cst_prepare_receive_request> cst_new_box_autoadd_prepare_receive_request() {
return _cst_new_box_autoadd_prepare_receive_request(); return _cst_new_box_autoadd_prepare_receive_request();
} }
@@ -1652,6 +1816,25 @@ final class wire_cst_backup_request extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> backup_path; external ffi.Pointer<wire_cst_list_prim_u_8_strict> backup_path;
} }
final class wire_cst_prepare_pay_onchain_response extends ffi.Struct {
@ffi.Uint64()
external int amount_sat;
@ffi.Uint64()
external int fees_sat;
}
final class wire_cst_pay_onchain_request extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> address;
external wire_cst_prepare_pay_onchain_response prepare_res;
}
final class wire_cst_prepare_pay_onchain_request extends ffi.Struct {
@ffi.Uint64()
external int amount_sat;
}
final class wire_cst_prepare_receive_request extends ffi.Struct { final class wire_cst_prepare_receive_request extends ffi.Struct {
@ffi.Uint64() @ffi.Uint64()
external int payer_amount_sat; external int payer_amount_sat;
@@ -1761,7 +1944,9 @@ final class wire_cst_liquid_sdk_event 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> boltz_url; external ffi.Pointer<wire_cst_list_prim_u_8_strict> boltz_url;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> electrum_url; external ffi.Pointer<wire_cst_list_prim_u_8_strict> liquid_electrum_url;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> bitcoin_electrum_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;
@@ -1845,8 +2030,14 @@ final class wire_cst_LiquidSdkError_Generic extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> err; external ffi.Pointer<wire_cst_list_prim_u_8_strict> err;
} }
final class wire_cst_LiquidSdkError_ServiceConnectivity extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> err;
}
final class LiquidSdkErrorKind extends ffi.Union { final class LiquidSdkErrorKind extends ffi.Union {
external wire_cst_LiquidSdkError_Generic Generic; external wire_cst_LiquidSdkError_Generic Generic;
external wire_cst_LiquidSdkError_ServiceConnectivity ServiceConnectivity;
} }
final class wire_cst_liquid_sdk_error extends ffi.Struct { final class wire_cst_liquid_sdk_error extends ffi.Struct {

View File

@@ -31,7 +31,8 @@ class BackupRequest {
/// Configuration for the Liquid SDK /// Configuration for the Liquid SDK
class Config { class Config {
final String boltzUrl; final String boltzUrl;
final String electrumUrl; final String liquidElectrumUrl;
final String bitcoinElectrumUrl;
/// Directory in which all SDK files (DB, log, cache) are stored. /// Directory in which all SDK files (DB, log, cache) are stored.
/// ///
@@ -51,7 +52,8 @@ class Config {
const Config({ const Config({
required this.boltzUrl, required this.boltzUrl,
required this.electrumUrl, required this.liquidElectrumUrl,
required this.bitcoinElectrumUrl,
required this.workingDir, required this.workingDir,
required this.network, required this.network,
required this.paymentTimeoutSec, required this.paymentTimeoutSec,
@@ -62,7 +64,8 @@ class Config {
@override @override
int get hashCode => int get hashCode =>
boltzUrl.hashCode ^ boltzUrl.hashCode ^
electrumUrl.hashCode ^ liquidElectrumUrl.hashCode ^
bitcoinElectrumUrl.hashCode ^
workingDir.hashCode ^ workingDir.hashCode ^
network.hashCode ^ network.hashCode ^
paymentTimeoutSec.hashCode ^ paymentTimeoutSec.hashCode ^
@@ -75,7 +78,8 @@ class Config {
other is Config && other is Config &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
boltzUrl == other.boltzUrl && boltzUrl == other.boltzUrl &&
electrumUrl == other.electrumUrl && liquidElectrumUrl == other.liquidElectrumUrl &&
bitcoinElectrumUrl == other.bitcoinElectrumUrl &&
workingDir == other.workingDir && workingDir == other.workingDir &&
network == other.network && network == other.network &&
paymentTimeoutSec == other.paymentTimeoutSec && paymentTimeoutSec == other.paymentTimeoutSec &&
@@ -254,6 +258,27 @@ enum Network {
; ;
} }
class PayOnchainRequest {
final String address;
final PreparePayOnchainResponse prepareRes;
const PayOnchainRequest({
required this.address,
required this.prepareRes,
});
@override
int get hashCode => address.hashCode ^ prepareRes.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PayOnchainRequest &&
runtimeType == other.runtimeType &&
address == other.address &&
prepareRes == other.prepareRes;
}
/// Represents an SDK payment. /// Represents an SDK payment.
/// ///
/// By default, this is an onchain tx. It may represent a swap, if swap metadata is available. /// By default, this is an onchain tx. It may represent a swap, if swap metadata is available.
@@ -418,6 +443,43 @@ enum PaymentType {
; ;
} }
class PreparePayOnchainRequest {
final BigInt amountSat;
const PreparePayOnchainRequest({
required this.amountSat,
});
@override
int get hashCode => amountSat.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PreparePayOnchainRequest && runtimeType == other.runtimeType && amountSat == other.amountSat;
}
class PreparePayOnchainResponse {
final BigInt amountSat;
final BigInt feesSat;
const PreparePayOnchainResponse({
required this.amountSat,
required this.feesSat,
});
@override
int get hashCode => amountSat.hashCode ^ feesSat.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PreparePayOnchainResponse &&
runtimeType == other.runtimeType &&
amountSat == other.amountSat &&
feesSat == other.feesSat;
}
class PrepareReceiveRequest { class PrepareReceiveRequest {
final BigInt payerAmountSat; final BigInt payerAmountSat;

View File

@@ -141,6 +141,46 @@ class FlutterBreezLiquidBindings {
_frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_list_paymentsPtr _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_list_paymentsPtr
.asFunction<void Function(int, int)>(); .asFunction<void Function(int, int)>();
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_pay_onchain(
int port_,
int that,
ffi.Pointer<wire_cst_pay_onchain_request> req,
) {
return _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_pay_onchain(
port_,
that,
req,
);
}
late final _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_pay_onchainPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Int64, ffi.UintPtr, ffi.Pointer<wire_cst_pay_onchain_request>)>>(
'frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_pay_onchain');
late final _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_pay_onchain =
_frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_pay_onchainPtr
.asFunction<void Function(int, int, ffi.Pointer<wire_cst_pay_onchain_request>)>();
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain(
int port_,
int that,
ffi.Pointer<wire_cst_prepare_pay_onchain_request> req,
) {
return _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain(
port_,
that,
req,
);
}
late final _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchainPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Int64, ffi.UintPtr, ffi.Pointer<wire_cst_prepare_pay_onchain_request>)>>(
'frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain');
late final _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchain =
_frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_pay_onchainPtr
.asFunction<void Function(int, int, ffi.Pointer<wire_cst_prepare_pay_onchain_request>)>();
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment( void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_prepare_receive_payment(
int port_, int port_,
int that, int that,
@@ -420,6 +460,17 @@ class FlutterBreezLiquidBindings {
_frbgen_breez_liquid_cst_new_box_autoadd_liquid_sdk_eventPtr _frbgen_breez_liquid_cst_new_box_autoadd_liquid_sdk_eventPtr
.asFunction<ffi.Pointer<wire_cst_liquid_sdk_event> Function()>(); .asFunction<ffi.Pointer<wire_cst_liquid_sdk_event> Function()>();
ffi.Pointer<wire_cst_pay_onchain_request> frbgen_breez_liquid_cst_new_box_autoadd_pay_onchain_request() {
return _frbgen_breez_liquid_cst_new_box_autoadd_pay_onchain_request();
}
late final _frbgen_breez_liquid_cst_new_box_autoadd_pay_onchain_requestPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<wire_cst_pay_onchain_request> Function()>>(
'frbgen_breez_liquid_cst_new_box_autoadd_pay_onchain_request');
late final _frbgen_breez_liquid_cst_new_box_autoadd_pay_onchain_request =
_frbgen_breez_liquid_cst_new_box_autoadd_pay_onchain_requestPtr
.asFunction<ffi.Pointer<wire_cst_pay_onchain_request> Function()>();
ffi.Pointer<wire_cst_payment> frbgen_breez_liquid_cst_new_box_autoadd_payment() { ffi.Pointer<wire_cst_payment> frbgen_breez_liquid_cst_new_box_autoadd_payment() {
return _frbgen_breez_liquid_cst_new_box_autoadd_payment(); return _frbgen_breez_liquid_cst_new_box_autoadd_payment();
} }
@@ -431,6 +482,18 @@ class FlutterBreezLiquidBindings {
_frbgen_breez_liquid_cst_new_box_autoadd_paymentPtr _frbgen_breez_liquid_cst_new_box_autoadd_paymentPtr
.asFunction<ffi.Pointer<wire_cst_payment> Function()>(); .asFunction<ffi.Pointer<wire_cst_payment> Function()>();
ffi.Pointer<wire_cst_prepare_pay_onchain_request>
frbgen_breez_liquid_cst_new_box_autoadd_prepare_pay_onchain_request() {
return _frbgen_breez_liquid_cst_new_box_autoadd_prepare_pay_onchain_request();
}
late final _frbgen_breez_liquid_cst_new_box_autoadd_prepare_pay_onchain_requestPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<wire_cst_prepare_pay_onchain_request> Function()>>(
'frbgen_breez_liquid_cst_new_box_autoadd_prepare_pay_onchain_request');
late final _frbgen_breez_liquid_cst_new_box_autoadd_prepare_pay_onchain_request =
_frbgen_breez_liquid_cst_new_box_autoadd_prepare_pay_onchain_requestPtr
.asFunction<ffi.Pointer<wire_cst_prepare_pay_onchain_request> Function()>();
ffi.Pointer<wire_cst_prepare_receive_request> ffi.Pointer<wire_cst_prepare_receive_request>
frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request() { frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request() {
return _frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request(); return _frbgen_breez_liquid_cst_new_box_autoadd_prepare_receive_request();
@@ -600,6 +663,25 @@ final class wire_cst_backup_request extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> backup_path; external ffi.Pointer<wire_cst_list_prim_u_8_strict> backup_path;
} }
final class wire_cst_prepare_pay_onchain_response extends ffi.Struct {
@ffi.Uint64()
external int amount_sat;
@ffi.Uint64()
external int fees_sat;
}
final class wire_cst_pay_onchain_request extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> address;
external wire_cst_prepare_pay_onchain_response prepare_res;
}
final class wire_cst_prepare_pay_onchain_request extends ffi.Struct {
@ffi.Uint64()
external int amount_sat;
}
final class wire_cst_prepare_receive_request extends ffi.Struct { final class wire_cst_prepare_receive_request extends ffi.Struct {
@ffi.Uint64() @ffi.Uint64()
external int payer_amount_sat; external int payer_amount_sat;
@@ -709,7 +791,9 @@ final class wire_cst_liquid_sdk_event 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> boltz_url; external ffi.Pointer<wire_cst_list_prim_u_8_strict> boltz_url;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> electrum_url; external ffi.Pointer<wire_cst_list_prim_u_8_strict> liquid_electrum_url;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> bitcoin_electrum_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;
@@ -793,8 +877,14 @@ final class wire_cst_LiquidSdkError_Generic extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> err; external ffi.Pointer<wire_cst_list_prim_u_8_strict> err;
} }
final class wire_cst_LiquidSdkError_ServiceConnectivity extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> err;
}
final class LiquidSdkErrorKind extends ffi.Union { final class LiquidSdkErrorKind extends ffi.Union {
external wire_cst_LiquidSdkError_Generic Generic; external wire_cst_LiquidSdkError_Generic Generic;
external wire_cst_LiquidSdkError_ServiceConnectivity ServiceConnectivity;
} }
final class wire_cst_liquid_sdk_error extends ffi.Struct { final class wire_cst_liquid_sdk_error extends ffi.Struct {

View File

@@ -38,7 +38,8 @@ fun asConfig(config: ReadableMap): Config? {
config, config,
arrayOf( arrayOf(
"boltzUrl", "boltzUrl",
"electrumUrl", "liquidElectrumUrl",
"bitcoinElectrumUrl",
"workingDir", "workingDir",
"network", "network",
"paymentTimeoutSec", "paymentTimeoutSec",
@@ -49,7 +50,8 @@ fun asConfig(config: ReadableMap): Config? {
return null return null
} }
val boltzUrl = config.getString("boltzUrl")!! val boltzUrl = config.getString("boltzUrl")!!
val electrumUrl = config.getString("electrumUrl")!! val liquidElectrumUrl = config.getString("liquidElectrumUrl")!!
val bitcoinElectrumUrl = config.getString("bitcoinElectrumUrl")!!
val workingDir = config.getString("workingDir")!! val workingDir = config.getString("workingDir")!!
val network = config.getString("network")?.let { asNetwork(it) }!! val network = config.getString("network")?.let { asNetwork(it) }!!
val paymentTimeoutSec = config.getDouble("paymentTimeoutSec").toULong() val paymentTimeoutSec = config.getDouble("paymentTimeoutSec").toULong()
@@ -66,7 +68,8 @@ fun asConfig(config: ReadableMap): Config? {
} }
return Config( return Config(
boltzUrl, boltzUrl,
electrumUrl, liquidElectrumUrl,
bitcoinElectrumUrl,
workingDir, workingDir,
network, network,
paymentTimeoutSec, paymentTimeoutSec,
@@ -78,7 +81,8 @@ fun asConfig(config: ReadableMap): Config? {
fun readableMapOf(config: Config): ReadableMap = fun readableMapOf(config: Config): ReadableMap =
readableMapOf( readableMapOf(
"boltzUrl" to config.boltzUrl, "boltzUrl" to config.boltzUrl,
"electrumUrl" to config.electrumUrl, "liquidElectrumUrl" to config.liquidElectrumUrl,
"bitcoinElectrumUrl" to config.bitcoinElectrumUrl,
"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,
@@ -286,6 +290,42 @@ fun asLogEntryList(arr: ReadableArray): List<LogEntry> {
return list return list
} }
fun asPayOnchainRequest(payOnchainRequest: ReadableMap): PayOnchainRequest? {
if (!validateMandatoryFields(
payOnchainRequest,
arrayOf(
"address",
"prepareRes",
),
)
) {
return null
}
val address = payOnchainRequest.getString("address")!!
val prepareRes = payOnchainRequest.getMap("prepareRes")?.let { asPreparePayOnchainResponse(it) }!!
return PayOnchainRequest(
address,
prepareRes,
)
}
fun readableMapOf(payOnchainRequest: PayOnchainRequest): ReadableMap =
readableMapOf(
"address" to payOnchainRequest.address,
"prepareRes" to readableMapOf(payOnchainRequest.prepareRes),
)
fun asPayOnchainRequestList(arr: ReadableArray): List<PayOnchainRequest> {
val list = ArrayList<PayOnchainRequest>()
for (value in arr.toArrayList()) {
when (value) {
is ReadableMap -> list.add(asPayOnchainRequest(value)!!)
else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}"))
}
}
return list
}
fun asPayment(payment: ReadableMap): Payment? { fun asPayment(payment: ReadableMap): Payment? {
if (!validateMandatoryFields( if (!validateMandatoryFields(
payment, payment,
@@ -352,6 +392,74 @@ fun asPaymentList(arr: ReadableArray): List<Payment> {
return list return list
} }
fun asPreparePayOnchainRequest(preparePayOnchainRequest: ReadableMap): PreparePayOnchainRequest? {
if (!validateMandatoryFields(
preparePayOnchainRequest,
arrayOf(
"amountSat",
),
)
) {
return null
}
val amountSat = preparePayOnchainRequest.getDouble("amountSat").toULong()
return PreparePayOnchainRequest(
amountSat,
)
}
fun readableMapOf(preparePayOnchainRequest: PreparePayOnchainRequest): ReadableMap =
readableMapOf(
"amountSat" to preparePayOnchainRequest.amountSat,
)
fun asPreparePayOnchainRequestList(arr: ReadableArray): List<PreparePayOnchainRequest> {
val list = ArrayList<PreparePayOnchainRequest>()
for (value in arr.toArrayList()) {
when (value) {
is ReadableMap -> list.add(asPreparePayOnchainRequest(value)!!)
else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}"))
}
}
return list
}
fun asPreparePayOnchainResponse(preparePayOnchainResponse: ReadableMap): PreparePayOnchainResponse? {
if (!validateMandatoryFields(
preparePayOnchainResponse,
arrayOf(
"amountSat",
"feesSat",
),
)
) {
return null
}
val amountSat = preparePayOnchainResponse.getDouble("amountSat").toULong()
val feesSat = preparePayOnchainResponse.getDouble("feesSat").toULong()
return PreparePayOnchainResponse(
amountSat,
feesSat,
)
}
fun readableMapOf(preparePayOnchainResponse: PreparePayOnchainResponse): ReadableMap =
readableMapOf(
"amountSat" to preparePayOnchainResponse.amountSat,
"feesSat" to preparePayOnchainResponse.feesSat,
)
fun asPreparePayOnchainResponseList(arr: ReadableArray): List<PreparePayOnchainResponse> {
val list = ArrayList<PreparePayOnchainResponse>()
for (value in arr.toArrayList()) {
when (value) {
is ReadableMap -> list.add(asPreparePayOnchainResponse(value)!!)
else -> throw LiquidSdkException.Generic(errUnexpectedType("${value::class.java.name}"))
}
}
return list
}
fun asPrepareReceiveRequest(prepareReceiveRequest: ReadableMap): PrepareReceiveRequest? { fun asPrepareReceiveRequest(prepareReceiveRequest: ReadableMap): PrepareReceiveRequest? {
if (!validateMandatoryFields( if (!validateMandatoryFields(
prepareReceiveRequest, prepareReceiveRequest,

View File

@@ -243,6 +243,42 @@ class BreezLiquidSDKModule(
} }
} }
@ReactMethod
fun preparePayOnchain(
req: ReadableMap,
promise: Promise,
) {
executor.execute {
try {
val preparePayOnchainRequest =
asPreparePayOnchainRequest(req)
?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PreparePayOnchainRequest")) }
val res = getBindingLiquidSdk().preparePayOnchain(preparePayOnchainRequest)
promise.resolve(readableMapOf(res))
} catch (e: Exception) {
promise.reject(e.javaClass.simpleName.replace("Exception", "Error"), e.message, e)
}
}
}
@ReactMethod
fun payOnchain(
req: ReadableMap,
promise: Promise,
) {
executor.execute {
try {
val payOnchainRequest =
asPayOnchainRequest(req)
?: run { throw LiquidSdkException.Generic(errMissingMandatoryField("req", "PayOnchainRequest")) }
val res = getBindingLiquidSdk().payOnchain(payOnchainRequest)
promise.resolve(readableMapOf(res))
} catch (e: Exception) {
promise.reject(e.javaClass.simpleName.replace("Exception", "Error"), e.message, e)
}
}
}
@ReactMethod @ReactMethod
fun listPayments(promise: Promise) { fun listPayments(promise: Promise) {
executor.execute { executor.execute {

View File

@@ -42,8 +42,11 @@ enum BreezLiquidSDKMapper {
guard let boltzUrl = config["boltzUrl"] as? String else { guard let boltzUrl = config["boltzUrl"] as? String else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "boltzUrl", typeName: "Config")) throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "boltzUrl", typeName: "Config"))
} }
guard let electrumUrl = config["electrumUrl"] as? String else { guard let liquidElectrumUrl = config["liquidElectrumUrl"] as? String else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "electrumUrl", typeName: "Config")) throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "liquidElectrumUrl", typeName: "Config"))
}
guard let bitcoinElectrumUrl = config["bitcoinElectrumUrl"] as? String else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "bitcoinElectrumUrl", typeName: "Config"))
} }
guard let workingDir = config["workingDir"] as? String else { guard let workingDir = config["workingDir"] as? String else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "workingDir", typeName: "Config")) throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "workingDir", typeName: "Config"))
@@ -69,7 +72,8 @@ enum BreezLiquidSDKMapper {
return Config( return Config(
boltzUrl: boltzUrl, boltzUrl: boltzUrl,
electrumUrl: electrumUrl, liquidElectrumUrl: liquidElectrumUrl,
bitcoinElectrumUrl: bitcoinElectrumUrl,
workingDir: workingDir, workingDir: workingDir,
network: network, network: network,
paymentTimeoutSec: paymentTimeoutSec, paymentTimeoutSec: paymentTimeoutSec,
@@ -81,7 +85,8 @@ enum BreezLiquidSDKMapper {
static func dictionaryOf(config: Config) -> [String: Any?] { static func dictionaryOf(config: Config) -> [String: Any?] {
return [ return [
"boltzUrl": config.boltzUrl, "boltzUrl": config.boltzUrl,
"electrumUrl": config.electrumUrl, "liquidElectrumUrl": config.liquidElectrumUrl,
"bitcoinElectrumUrl": config.bitcoinElectrumUrl,
"workingDir": config.workingDir, "workingDir": config.workingDir,
"network": valueOf(network: config.network), "network": valueOf(network: config.network),
"paymentTimeoutSec": config.paymentTimeoutSec, "paymentTimeoutSec": config.paymentTimeoutSec,
@@ -337,6 +342,45 @@ enum BreezLiquidSDKMapper {
return logEntryList.map { v -> [String: Any?] in dictionaryOf(logEntry: v) } return logEntryList.map { v -> [String: Any?] in dictionaryOf(logEntry: v) }
} }
static func asPayOnchainRequest(payOnchainRequest: [String: Any?]) throws -> PayOnchainRequest {
guard let address = payOnchainRequest["address"] as? String else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "address", typeName: "PayOnchainRequest"))
}
guard let prepareResTmp = payOnchainRequest["prepareRes"] as? [String: Any?] else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "prepareRes", typeName: "PayOnchainRequest"))
}
let prepareRes = try asPreparePayOnchainResponse(preparePayOnchainResponse: prepareResTmp)
return PayOnchainRequest(
address: address,
prepareRes: prepareRes
)
}
static func dictionaryOf(payOnchainRequest: PayOnchainRequest) -> [String: Any?] {
return [
"address": payOnchainRequest.address,
"prepareRes": dictionaryOf(preparePayOnchainResponse: payOnchainRequest.prepareRes),
]
}
static func asPayOnchainRequestList(arr: [Any]) throws -> [PayOnchainRequest] {
var list = [PayOnchainRequest]()
for value in arr {
if let val = value as? [String: Any?] {
var payOnchainRequest = try asPayOnchainRequest(payOnchainRequest: val)
list.append(payOnchainRequest)
} else {
throw LiquidSdkError.Generic(message: errUnexpectedType(typeName: "PayOnchainRequest"))
}
}
return list
}
static func arrayOf(payOnchainRequestList: [PayOnchainRequest]) -> [Any] {
return payOnchainRequestList.map { v -> [String: Any?] in dictionaryOf(payOnchainRequest: v) }
}
static func asPayment(payment: [String: Any?]) throws -> Payment { static func asPayment(payment: [String: Any?]) throws -> Payment {
var txId: String? var txId: String?
if hasNonNilKey(data: payment, key: "txId") { if hasNonNilKey(data: payment, key: "txId") {
@@ -447,6 +491,76 @@ enum BreezLiquidSDKMapper {
return paymentList.map { v -> [String: Any?] in dictionaryOf(payment: v) } return paymentList.map { v -> [String: Any?] in dictionaryOf(payment: v) }
} }
static func asPreparePayOnchainRequest(preparePayOnchainRequest: [String: Any?]) throws -> PreparePayOnchainRequest {
guard let amountSat = preparePayOnchainRequest["amountSat"] as? UInt64 else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "amountSat", typeName: "PreparePayOnchainRequest"))
}
return PreparePayOnchainRequest(
amountSat: amountSat)
}
static func dictionaryOf(preparePayOnchainRequest: PreparePayOnchainRequest) -> [String: Any?] {
return [
"amountSat": preparePayOnchainRequest.amountSat,
]
}
static func asPreparePayOnchainRequestList(arr: [Any]) throws -> [PreparePayOnchainRequest] {
var list = [PreparePayOnchainRequest]()
for value in arr {
if let val = value as? [String: Any?] {
var preparePayOnchainRequest = try asPreparePayOnchainRequest(preparePayOnchainRequest: val)
list.append(preparePayOnchainRequest)
} else {
throw LiquidSdkError.Generic(message: errUnexpectedType(typeName: "PreparePayOnchainRequest"))
}
}
return list
}
static func arrayOf(preparePayOnchainRequestList: [PreparePayOnchainRequest]) -> [Any] {
return preparePayOnchainRequestList.map { v -> [String: Any?] in dictionaryOf(preparePayOnchainRequest: v) }
}
static func asPreparePayOnchainResponse(preparePayOnchainResponse: [String: Any?]) throws -> PreparePayOnchainResponse {
guard let amountSat = preparePayOnchainResponse["amountSat"] as? UInt64 else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "amountSat", typeName: "PreparePayOnchainResponse"))
}
guard let feesSat = preparePayOnchainResponse["feesSat"] as? UInt64 else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "feesSat", typeName: "PreparePayOnchainResponse"))
}
return PreparePayOnchainResponse(
amountSat: amountSat,
feesSat: feesSat
)
}
static func dictionaryOf(preparePayOnchainResponse: PreparePayOnchainResponse) -> [String: Any?] {
return [
"amountSat": preparePayOnchainResponse.amountSat,
"feesSat": preparePayOnchainResponse.feesSat,
]
}
static func asPreparePayOnchainResponseList(arr: [Any]) throws -> [PreparePayOnchainResponse] {
var list = [PreparePayOnchainResponse]()
for value in arr {
if let val = value as? [String: Any?] {
var preparePayOnchainResponse = try asPreparePayOnchainResponse(preparePayOnchainResponse: val)
list.append(preparePayOnchainResponse)
} else {
throw LiquidSdkError.Generic(message: errUnexpectedType(typeName: "PreparePayOnchainResponse"))
}
}
return list
}
static func arrayOf(preparePayOnchainResponseList: [PreparePayOnchainResponse]) -> [Any] {
return preparePayOnchainResponseList.map { v -> [String: Any?] in dictionaryOf(preparePayOnchainResponse: v) }
}
static func asPrepareReceiveRequest(prepareReceiveRequest: [String: Any?]) throws -> PrepareReceiveRequest { static func asPrepareReceiveRequest(prepareReceiveRequest: [String: Any?]) throws -> PrepareReceiveRequest {
guard let payerAmountSat = prepareReceiveRequest["payerAmountSat"] as? UInt64 else { guard let payerAmountSat = prepareReceiveRequest["payerAmountSat"] as? UInt64 else {
throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "payerAmountSat", typeName: "PrepareReceiveRequest")) throw LiquidSdkError.Generic(message: errMissingMandatoryField(fieldName: "payerAmountSat", typeName: "PrepareReceiveRequest"))

View File

@@ -66,6 +66,18 @@ RCT_EXTERN_METHOD(
reject: (RCTPromiseRejectBlock)reject reject: (RCTPromiseRejectBlock)reject
) )
RCT_EXTERN_METHOD(
preparePayOnchain: (NSDictionary*)req
resolve: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject
)
RCT_EXTERN_METHOD(
payOnchain: (NSDictionary*)req
resolve: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject
)
RCT_EXTERN_METHOD( RCT_EXTERN_METHOD(
listPayments: (RCTPromiseResolveBlock)resolve listPayments: (RCTPromiseResolveBlock)resolve
reject: (RCTPromiseRejectBlock)reject reject: (RCTPromiseRejectBlock)reject

View File

@@ -198,6 +198,28 @@ class RNBreezLiquidSDK: RCTEventEmitter {
} }
} }
@objc(preparePayOnchain:resolve:reject:)
func preparePayOnchain(_ req: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
do {
let preparePayOnchainRequest = try BreezLiquidSDKMapper.asPreparePayOnchainRequest(preparePayOnchainRequest: req)
var res = try getBindingLiquidSdk().preparePayOnchain(req: preparePayOnchainRequest)
resolve(BreezLiquidSDKMapper.dictionaryOf(preparePayOnchainResponse: res))
} catch let err {
rejectErr(err: err, reject: reject)
}
}
@objc(payOnchain:resolve:reject:)
func payOnchain(_ req: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
do {
let payOnchainRequest = try BreezLiquidSDKMapper.asPayOnchainRequest(payOnchainRequest: req)
var res = try getBindingLiquidSdk().payOnchain(req: payOnchainRequest)
resolve(BreezLiquidSDKMapper.dictionaryOf(sendPaymentResponse: res))
} catch let err {
rejectErr(err: err, reject: reject)
}
}
@objc(listPayments:reject:) @objc(listPayments:reject:)
func listPayments(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { func listPayments(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
do { do {

View File

@@ -25,7 +25,8 @@ export interface BackupRequest {
export interface Config { export interface Config {
boltzUrl: string boltzUrl: string
electrumUrl: string liquidElectrumUrl: string
bitcoinElectrumUrl: string
workingDir: string workingDir: string
network: Network network: Network
paymentTimeoutSec: number paymentTimeoutSec: number
@@ -65,6 +66,11 @@ export interface LogEntry {
level: string level: string
} }
export interface PayOnchainRequest {
address: string
prepareRes: PreparePayOnchainResponse
}
export interface Payment { export interface Payment {
txId?: string txId?: string
swapId?: string swapId?: string
@@ -79,6 +85,15 @@ export interface Payment {
status: PaymentState status: PaymentState
} }
export interface PreparePayOnchainRequest {
amountSat: number
}
export interface PreparePayOnchainResponse {
amountSat: number
feesSat: number
}
export interface PrepareReceiveRequest { export interface PrepareReceiveRequest {
payerAmountSat: number payerAmountSat: number
} }
@@ -240,6 +255,16 @@ export const receivePayment = async (req: PrepareReceiveResponse): Promise<Recei
return response return response
} }
export const preparePayOnchain = async (req: PreparePayOnchainRequest): Promise<PreparePayOnchainResponse> => {
const response = await BreezLiquidSDK.preparePayOnchain(req)
return response
}
export const payOnchain = async (req: PayOnchainRequest): Promise<SendPaymentResponse> => {
const response = await BreezLiquidSDK.payOnchain(req)
return response
}
export const listPayments = async (): Promise<Payment[]> => { export const listPayments = async (): Promise<Payment[]> => {
const response = await BreezLiquidSDK.listPayments() const response = await BreezLiquidSDK.listPayments()
return response return response