mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2026-01-06 23:54:26 +01:00
BOLT12 receive (#882)
* Add BOLT12 receive payment handling * Handle BOLT12 invoice requests via WS * Fix invoice request subscription on stream initialisation * Store the BOLT12 offer used to receive a payment * Address review feedback * Separate into create BOLT12 invoice fn * Update all BOLT12 offers when webhook URL changes * Deprecate Lightning for Bolt11Invoice
This commit is contained in:
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
- name: Clippy bindings
|
||||
working-directory: lib/bindings
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
run: cargo clippy --all-targets -- -A deprecated -D warnings
|
||||
|
||||
- name: Clippy core
|
||||
working-directory: lib/core
|
||||
@@ -216,7 +216,7 @@ jobs:
|
||||
with:
|
||||
version: "27.2"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
- name: Start regtest environment
|
||||
working-directory: regtest
|
||||
run: sh start.sh
|
||||
|
||||
2
Makefile
2
Makefile
@@ -12,7 +12,7 @@ fmt:
|
||||
clippy: cargo-clippy wasm-clippy
|
||||
|
||||
cargo-clippy:
|
||||
cd lib/bindings && cargo clippy --all-targets -- -D warnings
|
||||
cd lib/bindings && cargo clippy --all-targets -- -A deprecated -D warnings
|
||||
cd lib/core && cargo clippy --all-targets -- -D warnings
|
||||
cd cli && cargo clippy -- -D warnings
|
||||
|
||||
|
||||
82
cli/Cargo.lock
generated
82
cli/Cargo.lock
generated
@@ -637,7 +637,7 @@ checksum = "829a082bd3761fde7476dc2ed85ca56c11628948460ece621e4f56fef5046567"
|
||||
[[package]]
|
||||
name = "boltz-client"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/danielgranhao/boltz-rust?rev=9c703b20a86cb8a4d9a0d3d6dc2f610a0389fe7a#9c703b20a86cb8a4d9a0d3d6dc2f610a0389fe7a"
|
||||
source = "git+https://github.com/dangeross/boltz-rust?rev=0ffdc77b3db6a14484b1a948b78d506a6aebbafe#0ffdc77b3db6a14484b1a948b78d506a6aebbafe"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bip39",
|
||||
@@ -648,7 +648,7 @@ dependencies = [
|
||||
"getrandom 0.2.14",
|
||||
"hex",
|
||||
"js-sys",
|
||||
"lightning-invoice 0.32.0",
|
||||
"lightning 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log",
|
||||
"macros",
|
||||
"reqwest 0.12.12",
|
||||
@@ -2577,9 +2577,25 @@ dependencies = [
|
||||
"dnssec-prover",
|
||||
"hashbrown 0.13.2",
|
||||
"libm",
|
||||
"lightning-invoice 0.33.2",
|
||||
"lightning-types 0.2.0",
|
||||
"possiblyrandom",
|
||||
"lightning-invoice 0.33.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lightning-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"possiblyrandom 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6#1ed173a39d4a415bab8171c3348b459f51f3b5e6"
|
||||
dependencies = [
|
||||
"bech32 0.11.0",
|
||||
"bitcoin 0.32.5",
|
||||
"dnssec-prover",
|
||||
"hashbrown 0.13.2",
|
||||
"libm",
|
||||
"lightning-invoice 0.33.2 (git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6)",
|
||||
"lightning-types 0.2.0 (git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6)",
|
||||
"musig2",
|
||||
"possiblyrandom 0.2.0 (git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2596,17 +2612,6 @@ dependencies = [
|
||||
"secp256k1 0.24.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning-invoice"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ab9f6ea77e20e3129235e62a2e6bd64ed932363df104e864ee65ccffb54a8f"
|
||||
dependencies = [
|
||||
"bech32 0.9.1",
|
||||
"bitcoin 0.32.5",
|
||||
"lightning-types 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning-invoice"
|
||||
version = "0.33.2"
|
||||
@@ -2615,18 +2620,17 @@ checksum = "11209f386879b97198b2bfc9e9c1e5d42870825c6bd4376f17f95357244d6600"
|
||||
dependencies = [
|
||||
"bech32 0.11.0",
|
||||
"bitcoin 0.32.5",
|
||||
"lightning-types 0.2.0",
|
||||
"lightning-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning-types"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1083b8d9137000edf3bfcb1ff011c0d25e0cdd2feb98cc21d6765e64a494148f"
|
||||
name = "lightning-invoice"
|
||||
version = "0.33.2"
|
||||
source = "git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6#1ed173a39d4a415bab8171c3348b459f51f3b5e6"
|
||||
dependencies = [
|
||||
"bech32 0.9.1",
|
||||
"bech32 0.11.0",
|
||||
"bitcoin 0.32.5",
|
||||
"hex-conservative 0.2.1",
|
||||
"lightning-types 0.2.0 (git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2638,6 +2642,14 @@ dependencies = [
|
||||
"bitcoin 0.32.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning-types"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6#1ed173a39d4a415bab8171c3348b459f51f3b5e6"
|
||||
dependencies = [
|
||||
"bitcoin 0.32.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
@@ -2758,7 +2770,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "macros"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/danielgranhao/boltz-rust?rev=9c703b20a86cb8a4d9a0d3d6dc2f610a0389fe7a#9c703b20a86cb8a4d9a0d3d6dc2f610a0389fe7a"
|
||||
source = "git+https://github.com/dangeross/boltz-rust?rev=0ffdc77b3db6a14484b1a948b78d506a6aebbafe#0ffdc77b3db6a14484b1a948b78d506a6aebbafe"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2894,6 +2906,14 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
|
||||
|
||||
[[package]]
|
||||
name = "musig2"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/arik-so/rust-musig2?rev=6f95a05718cbb44d8fe3fa6021aea8117aa38d50#6f95a05718cbb44d8fe3fa6021aea8117aa38d50"
|
||||
dependencies = [
|
||||
"bitcoin 0.32.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
@@ -3258,6 +3278,14 @@ dependencies = [
|
||||
"getrandom 0.2.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "possiblyrandom"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6#1ed173a39d4a415bab8171c3348b459f51f3b5e6"
|
||||
dependencies = [
|
||||
"getrandom 0.2.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -4162,7 +4190,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "sdk-common"
|
||||
version = "0.6.2"
|
||||
source = "git+https://github.com/breez/breez-sdk?rev=223002e47a3b1be8306f68e9b8094ee2884c9568#223002e47a3b1be8306f68e9b8094ee2884c9568"
|
||||
source = "git+https://github.com/breez/breez-sdk?rev=4e1165bd0f78af6c716c52ab8ba401cfaac76f55#4e1165bd0f78af6c716c52ab8ba401cfaac76f55"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"anyhow",
|
||||
@@ -4178,7 +4206,7 @@ dependencies = [
|
||||
"hickory-resolver",
|
||||
"lazy_static",
|
||||
"lightning 0.0.118",
|
||||
"lightning 0.1.2",
|
||||
"lightning 0.1.2 (git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6)",
|
||||
"lightning-invoice 0.26.0",
|
||||
"log",
|
||||
"maybe-sync",
|
||||
@@ -4207,7 +4235,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "sdk-macros"
|
||||
version = "0.6.2"
|
||||
source = "git+https://github.com/breez/breez-sdk?rev=223002e47a3b1be8306f68e9b8094ee2884c9568#223002e47a3b1be8306f68e9b8094ee2884c9568"
|
||||
source = "git+https://github.com/breez/breez-sdk?rev=4e1165bd0f78af6c716c52ab8ba401cfaac76f55#4e1165bd0f78af6c716c52ab8ba401cfaac76f55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -83,9 +83,9 @@ pub(crate) enum Command {
|
||||
},
|
||||
/// Receive a payment directly or via a swap
|
||||
ReceivePayment {
|
||||
/// The method to use when receiving. Either "lightning", "bitcoin" or "liquid"
|
||||
/// The method to use when receiving. Either "invoice", "offer", "bitcoin" or "liquid"
|
||||
#[arg(short = 'm', long = "method")]
|
||||
payment_method: Option<PaymentMethod>,
|
||||
payment_method: Option<String>,
|
||||
|
||||
/// Optional description for the invoice
|
||||
#[clap(short = 'd', long = "description")]
|
||||
@@ -314,6 +314,16 @@ pub(crate) async fn handle_command(
|
||||
description,
|
||||
use_description_hash,
|
||||
} => {
|
||||
let payment_method =
|
||||
payment_method.map_or(Ok(PaymentMethod::Bolt11Invoice), |method| {
|
||||
match method.as_str() {
|
||||
"invoice" => Ok(PaymentMethod::Bolt11Invoice),
|
||||
"offer" => Ok(PaymentMethod::Bolt12Offer),
|
||||
"bitcoin" => Ok(PaymentMethod::BitcoinAddress),
|
||||
"liquid" => Ok(PaymentMethod::LiquidAddress),
|
||||
_ => Err(anyhow!("Invalid payment method")),
|
||||
}
|
||||
})?;
|
||||
let amount = match asset_id {
|
||||
Some(asset_id) => Some(ReceiveAmount::Asset {
|
||||
asset_id,
|
||||
@@ -325,7 +335,7 @@ pub(crate) async fn handle_command(
|
||||
};
|
||||
let prepare_response = sdk
|
||||
.prepare_receive_payment(&PrepareReceiveRequest {
|
||||
payment_method: payment_method.unwrap_or(PaymentMethod::Lightning),
|
||||
payment_method,
|
||||
amount: amount.clone(),
|
||||
})
|
||||
.await?;
|
||||
@@ -359,6 +369,9 @@ pub(crate) async fn handle_command(
|
||||
|
||||
match sdk.parse(&response.destination).await? {
|
||||
InputType::Bolt11 { invoice } => result.push_str(&build_qr_text(&invoice.bolt11)),
|
||||
InputType::Bolt12Offer { offer, .. } => {
|
||||
result.push_str(&build_qr_text(&offer.offer))
|
||||
}
|
||||
InputType::LiquidAddress { address } => {
|
||||
result.push_str(&build_qr_text(&address.to_uri().map_err(|e| {
|
||||
anyhow!("Could not build BIP21 from address data: {e:?}")
|
||||
|
||||
82
lib/Cargo.lock
generated
82
lib/Cargo.lock
generated
@@ -760,7 +760,7 @@ checksum = "829a082bd3761fde7476dc2ed85ca56c11628948460ece621e4f56fef5046567"
|
||||
[[package]]
|
||||
name = "boltz-client"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/danielgranhao/boltz-rust?rev=9c703b20a86cb8a4d9a0d3d6dc2f610a0389fe7a#9c703b20a86cb8a4d9a0d3d6dc2f610a0389fe7a"
|
||||
source = "git+https://github.com/dangeross/boltz-rust?rev=0ffdc77b3db6a14484b1a948b78d506a6aebbafe#0ffdc77b3db6a14484b1a948b78d506a6aebbafe"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bip39",
|
||||
@@ -771,7 +771,7 @@ dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"hex",
|
||||
"js-sys",
|
||||
"lightning-invoice 0.32.0",
|
||||
"lightning 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log",
|
||||
"macros",
|
||||
"reqwest 0.12.12",
|
||||
@@ -2955,9 +2955,25 @@ dependencies = [
|
||||
"dnssec-prover",
|
||||
"hashbrown 0.13.2",
|
||||
"libm",
|
||||
"lightning-invoice 0.33.2",
|
||||
"lightning-types 0.2.0",
|
||||
"possiblyrandom",
|
||||
"lightning-invoice 0.33.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lightning-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"possiblyrandom 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6#1ed173a39d4a415bab8171c3348b459f51f3b5e6"
|
||||
dependencies = [
|
||||
"bech32 0.11.0",
|
||||
"bitcoin 0.32.5",
|
||||
"dnssec-prover",
|
||||
"hashbrown 0.13.2",
|
||||
"libm",
|
||||
"lightning-invoice 0.33.2 (git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6)",
|
||||
"lightning-types 0.2.0 (git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6)",
|
||||
"musig2",
|
||||
"possiblyrandom 0.2.0 (git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2974,17 +2990,6 @@ dependencies = [
|
||||
"secp256k1 0.24.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning-invoice"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ab9f6ea77e20e3129235e62a2e6bd64ed932363df104e864ee65ccffb54a8f"
|
||||
dependencies = [
|
||||
"bech32 0.9.1",
|
||||
"bitcoin 0.32.5",
|
||||
"lightning-types 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning-invoice"
|
||||
version = "0.33.2"
|
||||
@@ -2993,18 +2998,17 @@ checksum = "11209f386879b97198b2bfc9e9c1e5d42870825c6bd4376f17f95357244d6600"
|
||||
dependencies = [
|
||||
"bech32 0.11.0",
|
||||
"bitcoin 0.32.5",
|
||||
"lightning-types 0.2.0",
|
||||
"lightning-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning-types"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1083b8d9137000edf3bfcb1ff011c0d25e0cdd2feb98cc21d6765e64a494148f"
|
||||
name = "lightning-invoice"
|
||||
version = "0.33.2"
|
||||
source = "git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6#1ed173a39d4a415bab8171c3348b459f51f3b5e6"
|
||||
dependencies = [
|
||||
"bech32 0.9.1",
|
||||
"bech32 0.11.0",
|
||||
"bitcoin 0.32.5",
|
||||
"hex-conservative 0.2.1",
|
||||
"lightning-types 0.2.0 (git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3016,6 +3020,14 @@ dependencies = [
|
||||
"bitcoin 0.32.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lightning-types"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6#1ed173a39d4a415bab8171c3348b459f51f3b5e6"
|
||||
dependencies = [
|
||||
"bitcoin 0.32.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
@@ -3184,7 +3196,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "macros"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/danielgranhao/boltz-rust?rev=9c703b20a86cb8a4d9a0d3d6dc2f610a0389fe7a#9c703b20a86cb8a4d9a0d3d6dc2f610a0389fe7a"
|
||||
source = "git+https://github.com/dangeross/boltz-rust?rev=0ffdc77b3db6a14484b1a948b78d506a6aebbafe#0ffdc77b3db6a14484b1a948b78d506a6aebbafe"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3334,6 +3346,14 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
|
||||
|
||||
[[package]]
|
||||
name = "musig2"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/arik-so/rust-musig2?rev=6f95a05718cbb44d8fe3fa6021aea8117aa38d50#6f95a05718cbb44d8fe3fa6021aea8117aa38d50"
|
||||
dependencies = [
|
||||
"bitcoin 0.32.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
@@ -3702,6 +3722,14 @@ dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "possiblyrandom"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6#1ed173a39d4a415bab8171c3348b459f51f3b5e6"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -4688,7 +4716,7 @@ checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21"
|
||||
[[package]]
|
||||
name = "sdk-common"
|
||||
version = "0.6.2"
|
||||
source = "git+https://github.com/breez/breez-sdk?rev=223002e47a3b1be8306f68e9b8094ee2884c9568#223002e47a3b1be8306f68e9b8094ee2884c9568"
|
||||
source = "git+https://github.com/breez/breez-sdk?rev=4e1165bd0f78af6c716c52ab8ba401cfaac76f55#4e1165bd0f78af6c716c52ab8ba401cfaac76f55"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"anyhow",
|
||||
@@ -4704,7 +4732,7 @@ dependencies = [
|
||||
"hickory-resolver",
|
||||
"lazy_static",
|
||||
"lightning 0.0.118",
|
||||
"lightning 0.1.2",
|
||||
"lightning 0.1.2 (git+https://github.com/breez/rust-lightning?rev=1ed173a39d4a415bab8171c3348b459f51f3b5e6)",
|
||||
"lightning-invoice 0.26.0",
|
||||
"log",
|
||||
"maybe-sync",
|
||||
@@ -4733,7 +4761,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "sdk-macros"
|
||||
version = "0.6.2"
|
||||
source = "git+https://github.com/breez/breez-sdk?rev=223002e47a3b1be8306f68e9b8094ee2884c9568#223002e47a3b1be8306f68e9b8094ee2884c9568"
|
||||
source = "git+https://github.com/breez/breez-sdk?rev=4e1165bd0f78af6c716c52ab8ba401cfaac76f55#4e1165bd0f78af6c716c52ab8ba401cfaac76f55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -37,8 +37,8 @@ anyhow = "1.0"
|
||||
log = "0.4.20"
|
||||
once_cell = "1.19"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sdk-common = { git = "https://github.com/breez/breez-sdk", rev = "223002e47a3b1be8306f68e9b8094ee2884c9568", features = ["liquid"] }
|
||||
sdk-macros = { git = "https://github.com/breez/breez-sdk", rev = "223002e47a3b1be8306f68e9b8094ee2884c9568" }
|
||||
sdk-common = { git = "https://github.com/breez/breez-sdk", rev = "4e1165bd0f78af6c716c52ab8ba401cfaac76f55", features = ["liquid"] }
|
||||
sdk-macros = { git = "https://github.com/breez/breez-sdk", rev = "4e1165bd0f78af6c716c52ab8ba401cfaac76f55" }
|
||||
thiserror = "1.0"
|
||||
|
||||
[patch.crates-io]
|
||||
|
||||
@@ -23,6 +23,7 @@ object Constants {
|
||||
@Suppress("unused")
|
||||
const val MESSAGE_DATA_PAYLOAD = "notification_payload"
|
||||
|
||||
const val MESSAGE_TYPE_INVOICE_REQUEST = "invoice_request"
|
||||
const val MESSAGE_TYPE_LNURL_PAY_INFO = "lnurlpay_info"
|
||||
const val MESSAGE_TYPE_LNURL_PAY_INVOICE = "lnurlpay_invoice"
|
||||
const val MESSAGE_TYPE_SWAP_UPDATED = "swap_updated"
|
||||
@@ -42,6 +43,10 @@ object Constants {
|
||||
"foreground_service_notification_channel_name"
|
||||
const val FOREGROUND_SERVICE_NOTIFICATION_TITLE =
|
||||
"foreground_service_notification_title"
|
||||
const val INVOICE_REQUEST_NOTIFICATION_TITLE =
|
||||
"invoice_request_notification_title"
|
||||
const val INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE =
|
||||
"invoice_request_notification_failure_title"
|
||||
const val LNURL_PAY_INFO_NOTIFICATION_TITLE =
|
||||
"lnurl_pay_info_notification_title"
|
||||
const val LNURL_PAY_INVOICE_NOTIFICATION_TITLE =
|
||||
@@ -93,6 +98,10 @@ object Constants {
|
||||
"Running in the background"
|
||||
const val DEFAULT_LNURL_PAY_INFO_NOTIFICATION_TITLE =
|
||||
"Retrieving Payment Information"
|
||||
const val DEFAULT_INVOICE_REQUEST_NOTIFICATION_TITLE =
|
||||
"Fetching Invoice"
|
||||
const val DEFAULT_INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE =
|
||||
"Invoice Request Failed"
|
||||
const val DEFAULT_LNURL_PAY_INVOICE_NOTIFICATION_TITLE =
|
||||
"Fetching Invoice"
|
||||
const val DEFAULT_LNURL_PAY_METADATA_PLAIN_TEXT =
|
||||
|
||||
@@ -11,6 +11,7 @@ import breez_sdk_liquid.EventListener
|
||||
import breez_sdk_liquid.Logger
|
||||
import breez_sdk_liquid.SdkEvent
|
||||
import breez_sdk_liquid_notification.BreezSdkLiquidConnector.Companion.connectSDK
|
||||
import breez_sdk_liquid_notification.Constants.MESSAGE_TYPE_INVOICE_REQUEST
|
||||
import breez_sdk_liquid_notification.Constants.MESSAGE_TYPE_LNURL_PAY_INFO
|
||||
import breez_sdk_liquid_notification.Constants.MESSAGE_TYPE_LNURL_PAY_INVOICE
|
||||
import breez_sdk_liquid_notification.Constants.MESSAGE_TYPE_SWAP_UPDATED
|
||||
@@ -21,6 +22,7 @@ import breez_sdk_liquid_notification.Constants.SHUTDOWN_DELAY_MS
|
||||
import breez_sdk_liquid_notification.NotificationHelper.Companion.notifyForegroundService
|
||||
import breez_sdk_liquid_notification.NotificationHelper.Companion.cancelNotification
|
||||
import breez_sdk_liquid_notification.job.Job
|
||||
import breez_sdk_liquid_notification.job.InvoiceRequestJob
|
||||
import breez_sdk_liquid_notification.job.LnurlPayInfoJob
|
||||
import breez_sdk_liquid_notification.job.LnurlPayInvoiceJob
|
||||
import breez_sdk_liquid_notification.job.SwapUpdatedJob
|
||||
@@ -151,6 +153,14 @@ abstract class ForegroundService :
|
||||
Message.createFromIntent(intent)?.let { message ->
|
||||
message.payload?.let { payload ->
|
||||
when (message.type) {
|
||||
MESSAGE_TYPE_INVOICE_REQUEST ->
|
||||
InvoiceRequestJob(
|
||||
applicationContext,
|
||||
this,
|
||||
payload,
|
||||
logger,
|
||||
)
|
||||
|
||||
MESSAGE_TYPE_SWAP_UPDATED ->
|
||||
SwapUpdatedJob(
|
||||
applicationContext,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package breez_sdk_liquid_notification.job
|
||||
|
||||
import android.content.Context
|
||||
import breez_sdk_liquid.CreateBolt12InvoiceRequest
|
||||
import breez_sdk_liquid.SdkEvent
|
||||
import breez_sdk_liquid.BindingLiquidSdk
|
||||
import breez_sdk_liquid_notification.Constants.DEFAULT_INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE
|
||||
import breez_sdk_liquid_notification.Constants.DEFAULT_INVOICE_REQUEST_NOTIFICATION_TITLE
|
||||
import breez_sdk_liquid_notification.Constants.INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE
|
||||
import breez_sdk_liquid_notification.Constants.INVOICE_REQUEST_NOTIFICATION_TITLE
|
||||
import breez_sdk_liquid_notification.Constants.NOTIFICATION_CHANNEL_REPLACEABLE
|
||||
import breez_sdk_liquid_notification.NotificationHelper.Companion.notifyChannel
|
||||
import breez_sdk_liquid_notification.ResourceHelper.Companion.getString
|
||||
import breez_sdk_liquid_notification.SdkForegroundService
|
||||
import breez_sdk_liquid_notification.ServiceLogger
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@Serializable
|
||||
data class InvoiceRequestRequest(
|
||||
@SerialName("offer") val offer: String,
|
||||
@SerialName("invoice_request") val invoiceRequest: String,
|
||||
@SerialName("reply_url") val replyURL: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class InvoiceRequestResponse(
|
||||
val invoice: String,
|
||||
)
|
||||
|
||||
class InvoiceRequestJob(
|
||||
private val context: Context,
|
||||
private val fgService: SdkForegroundService,
|
||||
private val payload: String,
|
||||
private val logger: ServiceLogger,
|
||||
) : Job {
|
||||
companion object {
|
||||
private const val TAG = "InvoiceRequestJob"
|
||||
}
|
||||
|
||||
override fun start(liquidSDK: BindingLiquidSdk) {
|
||||
var request: InvoiceRequestRequest? = null
|
||||
try {
|
||||
request = Json.decodeFromString(InvoiceRequestRequest.serializer(), payload)
|
||||
val createBolt12InvoiceResponse =
|
||||
liquidSDK.createBolt12Invoice(
|
||||
CreateBolt12InvoiceRequest(request.offer, request.invoiceRequest),
|
||||
)
|
||||
val response = InvoiceRequestResponse(createBolt12InvoiceResponse.invoice)
|
||||
val success = replyServer(Json.encodeToString(response), request.replyURL)
|
||||
notifyChannel(
|
||||
context,
|
||||
NOTIFICATION_CHANNEL_REPLACEABLE,
|
||||
getString(
|
||||
context,
|
||||
if (success) INVOICE_REQUEST_NOTIFICATION_TITLE else INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE,
|
||||
if (success) DEFAULT_INVOICE_REQUEST_NOTIFICATION_TITLE else DEFAULT_INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE,
|
||||
),
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logger.log(TAG, "Failed to process invoice request: ${e.message}", "WARN")
|
||||
notifyChannel(
|
||||
context,
|
||||
NOTIFICATION_CHANNEL_REPLACEABLE,
|
||||
getString(
|
||||
context,
|
||||
INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE,
|
||||
DEFAULT_INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fgService.onFinished(this)
|
||||
}
|
||||
|
||||
override fun onEvent(e: SdkEvent) {}
|
||||
|
||||
override fun onShutdown() {}
|
||||
}
|
||||
@@ -2,6 +2,10 @@ package breez_sdk_liquid_notification.job
|
||||
|
||||
import breez_sdk_liquid.BindingLiquidSdk
|
||||
import breez_sdk_liquid.EventListener
|
||||
import java.io.DataOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
|
||||
interface Job : EventListener {
|
||||
/** When the notification service is connected to the Breez Liquid SDK
|
||||
@@ -13,4 +17,23 @@ interface Job : EventListener {
|
||||
* to cleanup the job.
|
||||
*/
|
||||
fun onShutdown()
|
||||
|
||||
fun replyServer(
|
||||
payload: String,
|
||||
replyURL: String,
|
||||
): Boolean {
|
||||
val url = URL(replyURL)
|
||||
val response = payload.toByteArray()
|
||||
|
||||
with(url.openConnection() as HttpURLConnection) {
|
||||
requestMethod = "POST"
|
||||
doOutput = true
|
||||
useCaches = false
|
||||
setRequestProperty("Content-Type", "application/json")
|
||||
setRequestProperty("Content-Length", response.size.toString())
|
||||
DataOutputStream(outputStream).use { it.write(response, 0, response.size) }
|
||||
|
||||
return responseCode == 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,25 +18,6 @@ data class LnurlErrorResponse(
|
||||
interface LnurlPayJob : Job {
|
||||
override fun onEvent(e: SdkEvent) {}
|
||||
|
||||
fun replyServer(
|
||||
payload: String,
|
||||
replyURL: String,
|
||||
): Boolean {
|
||||
val url = URL(replyURL)
|
||||
val response = payload.toByteArray()
|
||||
|
||||
with(url.openConnection() as HttpURLConnection) {
|
||||
requestMethod = "POST"
|
||||
doOutput = true
|
||||
useCaches = false
|
||||
setRequestProperty("Content-Type", "application/json")
|
||||
setRequestProperty("Content-Length", response.size.toString())
|
||||
DataOutputStream(outputStream).use { it.write(response, 0, response.size) }
|
||||
|
||||
return responseCode == 200
|
||||
}
|
||||
}
|
||||
|
||||
fun fail(
|
||||
withError: String?,
|
||||
replyURL: String,
|
||||
|
||||
@@ -79,6 +79,11 @@ typedef struct wire_cst_check_message_request {
|
||||
struct wire_cst_list_prim_u_8_strict *signature;
|
||||
} wire_cst_check_message_request;
|
||||
|
||||
typedef struct wire_cst_create_bolt_12_invoice_request {
|
||||
struct wire_cst_list_prim_u_8_strict *offer;
|
||||
struct wire_cst_list_prim_u_8_strict *invoice_request;
|
||||
} wire_cst_create_bolt_12_invoice_request;
|
||||
|
||||
typedef struct wire_cst_fetch_payment_proposed_fees_request {
|
||||
struct wire_cst_list_prim_u_8_strict *swap_id;
|
||||
} wire_cst_fetch_payment_proposed_fees_request;
|
||||
@@ -431,8 +436,8 @@ typedef struct wire_cst_prepare_send_request {
|
||||
|
||||
typedef struct wire_cst_prepare_receive_response {
|
||||
int32_t payment_method;
|
||||
struct wire_cst_receive_amount *amount;
|
||||
uint64_t fees_sat;
|
||||
struct wire_cst_receive_amount *amount;
|
||||
uint64_t *min_payer_amount_sat;
|
||||
uint64_t *max_payer_amount_sat;
|
||||
double *swapper_feerate;
|
||||
@@ -839,6 +844,10 @@ typedef struct wire_cst_check_message_response {
|
||||
bool is_valid;
|
||||
} wire_cst_check_message_response;
|
||||
|
||||
typedef struct wire_cst_create_bolt_12_invoice_response {
|
||||
struct wire_cst_list_prim_u_8_strict *invoice;
|
||||
} wire_cst_create_bolt_12_invoice_response;
|
||||
|
||||
typedef struct wire_cst_wallet_info {
|
||||
uint64_t balance_sat;
|
||||
uint64_t pending_send_sat;
|
||||
@@ -1242,6 +1251,10 @@ void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_buy_bitcoin(int
|
||||
WireSyncRust2DartDco frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_check_message(uintptr_t that,
|
||||
struct wire_cst_check_message_request *req);
|
||||
|
||||
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice(int64_t port_,
|
||||
uintptr_t that,
|
||||
struct wire_cst_create_bolt_12_invoice_request *req);
|
||||
|
||||
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_disconnect(int64_t port_,
|
||||
uintptr_t that);
|
||||
|
||||
@@ -1400,6 +1413,8 @@ struct wire_cst_check_message_request *frbgen_breez_liquid_cst_new_box_autoadd_c
|
||||
|
||||
struct wire_cst_connect_request *frbgen_breez_liquid_cst_new_box_autoadd_connect_request(void);
|
||||
|
||||
struct wire_cst_create_bolt_12_invoice_request *frbgen_breez_liquid_cst_new_box_autoadd_create_bolt_12_invoice_request(void);
|
||||
|
||||
double *frbgen_breez_liquid_cst_new_box_autoadd_f_64(double value);
|
||||
|
||||
struct wire_cst_fetch_payment_proposed_fees_request *frbgen_breez_liquid_cst_new_box_autoadd_fetch_payment_proposed_fees_request(void);
|
||||
@@ -1530,6 +1545,7 @@ static int64_t dummy_method_to_enforce_bundling(void) {
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_buy_bitcoin_request);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_check_message_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_create_bolt_12_invoice_request);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_f_64);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_fetch_payment_proposed_fees_request);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_get_payment_request);
|
||||
@@ -1595,6 +1611,7 @@ static int64_t dummy_method_to_enforce_bundling(void) {
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_backup);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_buy_bitcoin);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_check_message);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice);
|
||||
dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_disconnect);
|
||||
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_fetch_fiat_rates);
|
||||
|
||||
@@ -387,6 +387,11 @@ RustBuffer uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_buy_bitco
|
||||
RustBuffer uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_check_message(void*_Nonnull ptr, RustBuffer req, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_BREEZ_SDK_LIQUID_BINDINGS_FN_METHOD_BINDINGLIQUIDSDK_CREATE_BOLT12_INVOICE
|
||||
#define UNIFFI_FFIDEF_UNIFFI_BREEZ_SDK_LIQUID_BINDINGS_FN_METHOD_BINDINGLIQUIDSDK_CREATE_BOLT12_INVOICE
|
||||
RustBuffer uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_create_bolt12_invoice(void*_Nonnull ptr, RustBuffer req, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_BREEZ_SDK_LIQUID_BINDINGS_FN_METHOD_BINDINGLIQUIDSDK_DISCONNECT
|
||||
#define UNIFFI_FFIDEF_UNIFFI_BREEZ_SDK_LIQUID_BINDINGS_FN_METHOD_BINDINGLIQUIDSDK_DISCONNECT
|
||||
void uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_disconnect(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
|
||||
@@ -925,6 +930,12 @@ uint16_t uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_buy_b
|
||||
#define UNIFFI_FFIDEF_UNIFFI_BREEZ_SDK_LIQUID_BINDINGS_CHECKSUM_METHOD_BINDINGLIQUIDSDK_CHECK_MESSAGE
|
||||
uint16_t uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_check_message(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_BREEZ_SDK_LIQUID_BINDINGS_CHECKSUM_METHOD_BINDINGLIQUIDSDK_CREATE_BOLT12_INVOICE
|
||||
#define UNIFFI_FFIDEF_UNIFFI_BREEZ_SDK_LIQUID_BINDINGS_CHECKSUM_METHOD_BINDINGLIQUIDSDK_CREATE_BOLT12_INVOICE
|
||||
uint16_t uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_create_bolt12_invoice(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_BREEZ_SDK_LIQUID_BINDINGS_CHECKSUM_METHOD_BINDINGLIQUIDSDK_DISCONNECT
|
||||
|
||||
@@ -9,11 +9,14 @@ struct Constants {
|
||||
static let MESSAGE_DATA_TYPE = "notification_type"
|
||||
static let MESSAGE_DATA_PAYLOAD = "notification_payload"
|
||||
|
||||
static let MESSAGE_TYPE_INVOICE_REQUEST = "invoice_request"
|
||||
static let MESSAGE_TYPE_SWAP_UPDATED = "swap_updated"
|
||||
static let MESSAGE_TYPE_LNURL_PAY_INFO = "lnurlpay_info"
|
||||
static let MESSAGE_TYPE_LNURL_PAY_INVOICE = "lnurlpay_invoice"
|
||||
|
||||
// Resource Identifiers
|
||||
static let INVOICE_REQUEST_NOTIFICATION_TITLE = "invoice_request_notification_title"
|
||||
static let INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE = "invoice_request_notification_failure_title"
|
||||
static let LNURL_PAY_INFO_NOTIFICATION_TITLE = "lnurl_pay_info_notification_title"
|
||||
static let LNURL_PAY_INVOICE_NOTIFICATION_TITLE = "lnurl_pay_invoice_notification_title"
|
||||
static let LNURL_PAY_METADATA_PLAIN_TEXT = "lnurl_pay_metadata_plain_text"
|
||||
@@ -26,6 +29,8 @@ struct Constants {
|
||||
static let SWAP_CONFIRMED_NOTIFICATION_FAILURE_TITLE = "swap_confirmed_notification_failure_title"
|
||||
|
||||
// Resource Identifier Defaults
|
||||
static let DEFAULT_INVOICE_REQUEST_NOTIFICATION_TITLE = "Fetching Invoice"
|
||||
static let DEFAULT_INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE = "Invoice Request Failed"
|
||||
static let DEFAULT_LNURL_PAY_INFO_NOTIFICATION_TITLE = "Retrieving Payment Information"
|
||||
static let DEFAULT_LNURL_PAY_INVOICE_NOTIFICATION_TITLE = "Fetching Invoice"
|
||||
static let DEFAULT_LNURL_PAY_METADATA_PLAIN_TEXT = "Pay with LNURL"
|
||||
|
||||
@@ -72,6 +72,8 @@ open class SDKNotificationService: UNNotificationServiceExtension {
|
||||
|
||||
self.logger.log(tag: TAG, line: "\(notificationType) data string: \(payload)", level: "INFO")
|
||||
switch(notificationType) {
|
||||
case Constants.MESSAGE_TYPE_INVOICE_REQUEST:
|
||||
return InvoiceRequestTask(payload: payload, logger: self.logger, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent)
|
||||
case Constants.MESSAGE_TYPE_SWAP_UPDATED:
|
||||
return SwapUpdatedTask(payload: payload, logger: self.logger, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent)
|
||||
case Constants.MESSAGE_TYPE_LNURL_PAY_INFO:
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import UserNotifications
|
||||
import Foundation
|
||||
|
||||
struct InvoiceRequestRequest: Codable {
|
||||
let offer: String
|
||||
let invoice_request: String
|
||||
let reply_url: String
|
||||
}
|
||||
|
||||
struct InvoiceRequestResponse: Decodable, Encodable {
|
||||
let invoice: String
|
||||
|
||||
init(invoice: String) {
|
||||
self.invoice = invoice
|
||||
}
|
||||
}
|
||||
|
||||
class InvoiceRequestTask : ReplyableTask {
|
||||
fileprivate let TAG = "InvoiceRequestTask"
|
||||
|
||||
init(payload: String, logger: ServiceLogger, contentHandler: ((UNNotificationContent) -> Void)? = nil, bestAttemptContent: UNMutableNotificationContent? = nil) {
|
||||
let successNotificationTitle = ResourceHelper.shared.getString(key: Constants.INVOICE_REQUEST_NOTIFICATION_TITLE, fallback: Constants.DEFAULT_INVOICE_REQUEST_NOTIFICATION_TITLE)
|
||||
let failNotificationTitle = ResourceHelper.shared.getString(key: Constants.INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE, fallback: Constants.DEFAULT_INVOICE_REQUEST_NOTIFICATION_FAILURE_TITLE)
|
||||
super.init(payload: payload, logger: logger, contentHandler: contentHandler, bestAttemptContent: bestAttemptContent, successNotificationTitle: successNotificationTitle, failNotificationTitle: failNotificationTitle)
|
||||
}
|
||||
|
||||
override func start(liquidSDK: BindingLiquidSdk) throws {
|
||||
var request: InvoiceRequestRequest? = nil
|
||||
do {
|
||||
request = try JSONDecoder().decode(InvoiceRequestRequest.self, from: self.payload.data(using: .utf8)!)
|
||||
} catch let e {
|
||||
self.logger.log(tag: TAG, line: "failed to decode payload: \(e)", level: "ERROR")
|
||||
self.displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
|
||||
throw e
|
||||
}
|
||||
|
||||
do {
|
||||
let createBolt12InvoiceRes = try liquidSDK.createBolt12Invoice(req: CreateBolt12InvoiceRequest(offer: request!.offer, invoiceRequest: request!.invoice_request))
|
||||
self.replyServer(encodable: InvoiceRequestResponse(invoice: createBolt12InvoiceRes.invoice), replyURL: request!.reply_url)
|
||||
} catch let e {
|
||||
self.logger.log(tag: TAG, line: "failed to process invoice request: \(e)", level: "ERROR")
|
||||
self.displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,52 +11,7 @@ struct LnurlErrorResponse: Decodable, Encodable {
|
||||
}
|
||||
}
|
||||
|
||||
class LnurlPayTask : TaskProtocol {
|
||||
var payload: String
|
||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
var bestAttemptContent: UNMutableNotificationContent?
|
||||
var logger: ServiceLogger
|
||||
var successNotificationTitle: String
|
||||
var failNotificationTitle: String
|
||||
|
||||
init(payload: String, logger: ServiceLogger, contentHandler: ((UNNotificationContent) -> Void)? = nil, bestAttemptContent: UNMutableNotificationContent? = nil, successNotificationTitle: String, failNotificationTitle: String) {
|
||||
self.payload = payload
|
||||
self.contentHandler = contentHandler
|
||||
self.bestAttemptContent = bestAttemptContent
|
||||
self.logger = logger
|
||||
self.successNotificationTitle = successNotificationTitle;
|
||||
self.failNotificationTitle = failNotificationTitle;
|
||||
}
|
||||
|
||||
func start(liquidSDK: BindingLiquidSdk) throws {}
|
||||
|
||||
public func onEvent(e: SdkEvent) {}
|
||||
|
||||
func onShutdown() {
|
||||
displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
|
||||
}
|
||||
|
||||
func replyServer(encodable: Encodable, replyURL: String) {
|
||||
guard let serverReplyURL = URL(string: replyURL) else {
|
||||
self.displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
|
||||
return
|
||||
}
|
||||
var request = URLRequest(url: serverReplyURL)
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = try! JSONEncoder().encode(encodable)
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
let statusCode = (response as! HTTPURLResponse).statusCode
|
||||
|
||||
if statusCode == 200 {
|
||||
self.displayPushNotification(title: self.successNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
|
||||
} else {
|
||||
self.displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
|
||||
return
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
class LnurlPayTask : ReplyableTask {
|
||||
func fail(withError: String, replyURL: String, failNotificationTitle: String? = nil) {
|
||||
if let serverReplyURL = URL(string: replyURL) {
|
||||
var request = URLRequest(url: serverReplyURL)
|
||||
|
||||
@@ -59,3 +59,50 @@ extension TaskProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReplyableTask : TaskProtocol {
|
||||
var payload: String
|
||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
var bestAttemptContent: UNMutableNotificationContent?
|
||||
var logger: ServiceLogger
|
||||
var successNotificationTitle: String
|
||||
var failNotificationTitle: String
|
||||
|
||||
init(payload: String, logger: ServiceLogger, contentHandler: ((UNNotificationContent) -> Void)? = nil, bestAttemptContent: UNMutableNotificationContent? = nil, successNotificationTitle: String, failNotificationTitle: String) {
|
||||
self.payload = payload
|
||||
self.contentHandler = contentHandler
|
||||
self.bestAttemptContent = bestAttemptContent
|
||||
self.logger = logger
|
||||
self.successNotificationTitle = successNotificationTitle;
|
||||
self.failNotificationTitle = failNotificationTitle;
|
||||
}
|
||||
|
||||
func start(liquidSDK: BindingLiquidSdk) throws {}
|
||||
|
||||
public func onEvent(e: SdkEvent) {}
|
||||
|
||||
func onShutdown() {
|
||||
displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
|
||||
}
|
||||
|
||||
func replyServer(encodable: Encodable, replyURL: String) {
|
||||
guard let serverReplyURL = URL(string: replyURL) else {
|
||||
self.displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
|
||||
return
|
||||
}
|
||||
var request = URLRequest(url: serverReplyURL)
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = try! JSONEncoder().encode(encodable)
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
let statusCode = (response as! HTTPURLResponse).statusCode
|
||||
|
||||
if statusCode == 200 {
|
||||
self.displayPushNotification(title: self.successNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
|
||||
} else {
|
||||
self.displayPushNotification(title: self.failNotificationTitle, logger: self.logger, threadIdentifier: Constants.NOTIFICATION_THREAD_REPLACEABLE)
|
||||
return
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,6 +459,8 @@ dictionary SendPaymentResponse {
|
||||
|
||||
enum PaymentMethod {
|
||||
"Lightning",
|
||||
"Bolt11Invoice",
|
||||
"Bolt12Offer",
|
||||
"BitcoinAddress",
|
||||
"LiquidAddress",
|
||||
};
|
||||
@@ -493,6 +495,15 @@ dictionary ReceivePaymentResponse {
|
||||
string destination;
|
||||
};
|
||||
|
||||
dictionary CreateBolt12InvoiceRequest {
|
||||
string offer;
|
||||
string invoice_request;
|
||||
};
|
||||
|
||||
dictionary CreateBolt12InvoiceResponse {
|
||||
string invoice;
|
||||
};
|
||||
|
||||
dictionary Limits {
|
||||
u64 min_sat;
|
||||
u64 max_sat;
|
||||
@@ -808,6 +819,9 @@ interface BindingLiquidSdk {
|
||||
[Throws=PaymentError]
|
||||
ReceivePaymentResponse receive_payment(ReceivePaymentRequest req);
|
||||
|
||||
[Throws=PaymentError]
|
||||
CreateBolt12InvoiceResponse create_bolt12_invoice(CreateBolt12InvoiceRequest req);
|
||||
|
||||
[Throws=PaymentError]
|
||||
LightningPaymentLimitsResponse fetch_lightning_limits();
|
||||
|
||||
|
||||
@@ -136,6 +136,13 @@ impl BindingLiquidSdk {
|
||||
rt().block_on(self.sdk.receive_payment(&req))
|
||||
}
|
||||
|
||||
pub fn create_bolt12_invoice(
|
||||
&self,
|
||||
req: CreateBolt12InvoiceRequest,
|
||||
) -> Result<CreateBolt12InvoiceResponse, PaymentError> {
|
||||
rt().block_on(self.sdk.create_bolt12_invoice(&req))
|
||||
}
|
||||
|
||||
pub fn fetch_lightning_limits(&self) -> Result<LightningPaymentLimitsResponse, PaymentError> {
|
||||
rt().block_on(self.sdk.fetch_lightning_limits())
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ maybe-sync = { version = "0.1.1", features = ["sync"] }
|
||||
prost = "^0.11"
|
||||
tonic = { version = "^0.8", features = ["tls", "tls-webpki-roots"] }
|
||||
uuid = { version = "1.8.0", features = ["v4"] }
|
||||
boltz-client = { git = "https://github.com/danielgranhao/boltz-rust", rev = "9c703b20a86cb8a4d9a0d3d6dc2f610a0389fe7a", features = [
|
||||
boltz-client = { git = "https://github.com/dangeross/boltz-rust", rev = "0ffdc77b3db6a14484b1a948b78d506a6aebbafe", features = [
|
||||
"electrum",
|
||||
] }
|
||||
rusqlite = { git = "https://github.com/Spxg/rusqlite", rev = "e36644127f31fa6e7ea0999b59432deb4a07f220", features = [
|
||||
@@ -98,7 +98,7 @@ tonic = { version = "0.12", default-features = false, features = [
|
||||
"codegen",
|
||||
"prost",
|
||||
] }
|
||||
boltz-client = { git = "https://github.com/danielgranhao/boltz-rust", rev = "9c703b20a86cb8a4d9a0d3d6dc2f610a0389fe7a" }
|
||||
boltz-client = { git = "https://github.com/dangeross/boltz-rust", rev = "0ffdc77b3db6a14484b1a948b78d506a6aebbafe" }
|
||||
rusqlite = { git = "https://github.com/Spxg/rusqlite", rev = "e36644127f31fa6e7ea0999b59432deb4a07f220", features = [
|
||||
"backup",
|
||||
"bundled",
|
||||
|
||||
@@ -134,6 +134,13 @@ impl BindingLiquidSdk {
|
||||
self.sdk.receive_payment(&req).await
|
||||
}
|
||||
|
||||
pub async fn create_bolt12_invoice(
|
||||
&self,
|
||||
req: CreateBolt12InvoiceRequest,
|
||||
) -> Result<CreateBolt12InvoiceResponse, PaymentError> {
|
||||
self.sdk.create_bolt12_invoice(&req).await
|
||||
}
|
||||
|
||||
pub async fn fetch_lightning_limits(
|
||||
&self,
|
||||
) -> Result<LightningPaymentLimitsResponse, PaymentError> {
|
||||
|
||||
@@ -469,7 +469,7 @@ impl ChainSwapHandler {
|
||||
|
||||
ensure_sdk!(
|
||||
matches!(swap.direction, Direction::Incoming),
|
||||
PaymentError::generic(&format!(
|
||||
PaymentError::generic(format!(
|
||||
"Only an incoming chain swap can be a zero-amount swap. Swap ID: {}",
|
||||
&swap.id
|
||||
))
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use anyhow::Error;
|
||||
use lwk_wollet::secp256k1;
|
||||
use sdk_common::prelude::{LnUrlAuthError, LnUrlPayError, LnUrlWithdrawError};
|
||||
use sdk_common::{
|
||||
lightning_with_bolt12::offers::parse::Bolt12SemanticError,
|
||||
prelude::{LnUrlAuthError, LnUrlPayError, LnUrlWithdrawError},
|
||||
};
|
||||
|
||||
use crate::payjoin::error::PayjoinError;
|
||||
|
||||
@@ -127,39 +130,47 @@ pub enum PaymentError {
|
||||
SignerError { err: String },
|
||||
}
|
||||
impl PaymentError {
|
||||
pub(crate) fn asset_error(err: &str) -> Self {
|
||||
pub(crate) fn asset_error<S: AsRef<str>>(err: S) -> Self {
|
||||
Self::AssetError {
|
||||
err: err.to_string(),
|
||||
err: err.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generic(err: &str) -> Self {
|
||||
pub(crate) fn generic<S: AsRef<str>>(err: S) -> Self {
|
||||
Self::Generic {
|
||||
err: err.to_string(),
|
||||
err: err.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_invoice(err: &str) -> Self {
|
||||
pub(crate) fn invalid_invoice<S: AsRef<str>>(err: S) -> Self {
|
||||
Self::InvalidInvoice {
|
||||
err: err.to_string(),
|
||||
err: err.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_network(err: &str) -> Self {
|
||||
pub(crate) fn invalid_network<S: AsRef<str>>(err: S) -> Self {
|
||||
Self::InvalidNetwork {
|
||||
err: err.to_string(),
|
||||
err: err.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn receive_error(err: &str) -> Self {
|
||||
pub(crate) fn receive_error<S: AsRef<str>>(err: S) -> Self {
|
||||
Self::ReceiveError {
|
||||
err: err.to_string(),
|
||||
err: err.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn amount_missing(err: &str) -> Self {
|
||||
pub(crate) fn amount_missing<S: AsRef<str>>(err: S) -> Self {
|
||||
Self::AmountMissing {
|
||||
err: err.to_string(),
|
||||
err: err.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bolt12SemanticError> for PaymentError {
|
||||
fn from(err: Bolt12SemanticError) -> Self {
|
||||
PaymentError::Generic {
|
||||
err: format!("Failed to create BOLT12 invoice: {err:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
||||
default_rust_auto_opaque = RustAutoOpaqueNom,
|
||||
);
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.9.0";
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1264782025;
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 464449310;
|
||||
|
||||
// Section: executor
|
||||
|
||||
@@ -271,6 +271,55 @@ fn wire__crate__bindings__BindingLiquidSdk_check_message_impl(
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
that: impl CstDecode<
|
||||
RustOpaqueNom<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<BindingLiquidSdk>>,
|
||||
>,
|
||||
req: impl CstDecode<crate::model::CreateBolt12InvoiceRequest>,
|
||||
) {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::DcoCodec, _, _, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "BindingLiquidSdk_create_bolt12_invoice",
|
||||
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::<_, _, crate::error::PaymentError>(
|
||||
(move || async move {
|
||||
let mut api_that_guard = 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_guard =
|
||||
Some(api_that.lockable_decode_async_ref().await)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
let api_that_guard = api_that_guard.unwrap();
|
||||
let output_ok = crate::bindings::BindingLiquidSdk::create_bolt12_invoice(
|
||||
&*api_that_guard,
|
||||
api_req,
|
||||
)
|
||||
.await?;
|
||||
Ok(output_ok)
|
||||
})()
|
||||
.await,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__bindings__BindingLiquidSdk_disconnect_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
that: impl CstDecode<
|
||||
@@ -2185,8 +2234,10 @@ impl CstDecode<crate::model::PaymentMethod> for i32 {
|
||||
fn cst_decode(self) -> crate::model::PaymentMethod {
|
||||
match self {
|
||||
0 => crate::model::PaymentMethod::Lightning,
|
||||
1 => crate::model::PaymentMethod::BitcoinAddress,
|
||||
2 => crate::model::PaymentMethod::LiquidAddress,
|
||||
1 => crate::model::PaymentMethod::Bolt11Invoice,
|
||||
2 => crate::model::PaymentMethod::Bolt12Offer,
|
||||
3 => crate::model::PaymentMethod::BitcoinAddress,
|
||||
4 => crate::model::PaymentMethod::LiquidAddress,
|
||||
_ => unreachable!("Invalid variant for PaymentMethod: {}", self),
|
||||
}
|
||||
}
|
||||
@@ -2625,6 +2676,28 @@ impl SseDecode for crate::model::ConnectRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::model::CreateBolt12InvoiceRequest {
|
||||
// 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_offer = <String>::sse_decode(deserializer);
|
||||
let mut var_invoiceRequest = <String>::sse_decode(deserializer);
|
||||
return crate::model::CreateBolt12InvoiceRequest {
|
||||
offer: var_offer,
|
||||
invoice_request: var_invoiceRequest,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::model::CreateBolt12InvoiceResponse {
|
||||
// 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_invoice = <String>::sse_decode(deserializer);
|
||||
return crate::model::CreateBolt12InvoiceResponse {
|
||||
invoice: var_invoice,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::bindings::CurrencyInfo {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
@@ -4122,8 +4195,10 @@ impl SseDecode for crate::model::PaymentMethod {
|
||||
let mut inner = <i32>::sse_decode(deserializer);
|
||||
return match inner {
|
||||
0 => crate::model::PaymentMethod::Lightning,
|
||||
1 => crate::model::PaymentMethod::BitcoinAddress,
|
||||
2 => crate::model::PaymentMethod::LiquidAddress,
|
||||
1 => crate::model::PaymentMethod::Bolt11Invoice,
|
||||
2 => crate::model::PaymentMethod::Bolt12Offer,
|
||||
3 => crate::model::PaymentMethod::BitcoinAddress,
|
||||
4 => crate::model::PaymentMethod::LiquidAddress,
|
||||
_ => unreachable!("Invalid variant for PaymentMethod: {}", inner),
|
||||
};
|
||||
}
|
||||
@@ -4264,15 +4339,15 @@ impl SseDecode for crate::model::PrepareReceiveResponse {
|
||||
// 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_paymentMethod = <crate::model::PaymentMethod>::sse_decode(deserializer);
|
||||
let mut var_amount = <Option<crate::model::ReceiveAmount>>::sse_decode(deserializer);
|
||||
let mut var_feesSat = <u64>::sse_decode(deserializer);
|
||||
let mut var_amount = <Option<crate::model::ReceiveAmount>>::sse_decode(deserializer);
|
||||
let mut var_minPayerAmountSat = <Option<u64>>::sse_decode(deserializer);
|
||||
let mut var_maxPayerAmountSat = <Option<u64>>::sse_decode(deserializer);
|
||||
let mut var_swapperFeerate = <Option<f64>>::sse_decode(deserializer);
|
||||
return crate::model::PrepareReceiveResponse {
|
||||
payment_method: var_paymentMethod,
|
||||
amount: var_amount,
|
||||
fees_sat: var_feesSat,
|
||||
amount: var_amount,
|
||||
min_payer_amount_sat: var_minPayerAmountSat,
|
||||
max_payer_amount_sat: var_maxPayerAmountSat,
|
||||
swapper_feerate: var_swapperFeerate,
|
||||
@@ -5274,6 +5349,44 @@ impl flutter_rust_bridge::IntoIntoDart<crate::model::ConnectRequest>
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::model::CreateBolt12InvoiceRequest {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
self.offer.into_into_dart().into_dart(),
|
||||
self.invoice_request.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
}
|
||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
|
||||
for crate::model::CreateBolt12InvoiceRequest
|
||||
{
|
||||
}
|
||||
impl flutter_rust_bridge::IntoIntoDart<crate::model::CreateBolt12InvoiceRequest>
|
||||
for crate::model::CreateBolt12InvoiceRequest
|
||||
{
|
||||
fn into_into_dart(self) -> crate::model::CreateBolt12InvoiceRequest {
|
||||
self
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::model::CreateBolt12InvoiceResponse {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[self.invoice.into_into_dart().into_dart()].into_dart()
|
||||
}
|
||||
}
|
||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
|
||||
for crate::model::CreateBolt12InvoiceResponse
|
||||
{
|
||||
}
|
||||
impl flutter_rust_bridge::IntoIntoDart<crate::model::CreateBolt12InvoiceResponse>
|
||||
for crate::model::CreateBolt12InvoiceResponse
|
||||
{
|
||||
fn into_into_dart(self) -> crate::model::CreateBolt12InvoiceResponse {
|
||||
self
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for FrbWrapper<crate::bindings::CurrencyInfo> {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
@@ -6479,8 +6592,10 @@ impl flutter_rust_bridge::IntoDart for crate::model::PaymentMethod {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
match self {
|
||||
Self::Lightning => 0.into_dart(),
|
||||
Self::BitcoinAddress => 1.into_dart(),
|
||||
Self::LiquidAddress => 2.into_dart(),
|
||||
Self::Bolt11Invoice => 1.into_dart(),
|
||||
Self::Bolt12Offer => 2.into_dart(),
|
||||
Self::BitcoinAddress => 3.into_dart(),
|
||||
Self::LiquidAddress => 4.into_dart(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -6693,8 +6808,8 @@ impl flutter_rust_bridge::IntoDart for crate::model::PrepareReceiveResponse {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
self.payment_method.into_into_dart().into_dart(),
|
||||
self.amount.into_into_dart().into_dart(),
|
||||
self.fees_sat.into_into_dart().into_dart(),
|
||||
self.amount.into_into_dart().into_dart(),
|
||||
self.min_payer_amount_sat.into_into_dart().into_dart(),
|
||||
self.max_payer_amount_sat.into_into_dart().into_dart(),
|
||||
self.swapper_feerate.into_into_dart().into_dart(),
|
||||
@@ -7624,6 +7739,21 @@ impl SseEncode for crate::model::ConnectRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::model::CreateBolt12InvoiceRequest {
|
||||
// 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.offer, serializer);
|
||||
<String>::sse_encode(self.invoice_request, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::model::CreateBolt12InvoiceResponse {
|
||||
// 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.invoice, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::bindings::CurrencyInfo {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
@@ -8839,8 +8969,10 @@ impl SseEncode for crate::model::PaymentMethod {
|
||||
<i32>::sse_encode(
|
||||
match self {
|
||||
crate::model::PaymentMethod::Lightning => 0,
|
||||
crate::model::PaymentMethod::BitcoinAddress => 1,
|
||||
crate::model::PaymentMethod::LiquidAddress => 2,
|
||||
crate::model::PaymentMethod::Bolt11Invoice => 1,
|
||||
crate::model::PaymentMethod::Bolt12Offer => 2,
|
||||
crate::model::PaymentMethod::BitcoinAddress => 3,
|
||||
crate::model::PaymentMethod::LiquidAddress => 4,
|
||||
_ => {
|
||||
unimplemented!("");
|
||||
}
|
||||
@@ -8956,8 +9088,8 @@ impl SseEncode for crate::model::PrepareReceiveResponse {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<crate::model::PaymentMethod>::sse_encode(self.payment_method, serializer);
|
||||
<Option<crate::model::ReceiveAmount>>::sse_encode(self.amount, serializer);
|
||||
<u64>::sse_encode(self.fees_sat, serializer);
|
||||
<Option<crate::model::ReceiveAmount>>::sse_encode(self.amount, serializer);
|
||||
<Option<u64>>::sse_encode(self.min_payer_amount_sat, serializer);
|
||||
<Option<u64>>::sse_encode(self.max_payer_amount_sat, serializer);
|
||||
<Option<f64>>::sse_encode(self.swapper_feerate, serializer);
|
||||
@@ -9724,6 +9856,15 @@ mod io {
|
||||
CstDecode::<crate::model::ConnectRequest>::cst_decode(*wrap).into()
|
||||
}
|
||||
}
|
||||
impl CstDecode<crate::model::CreateBolt12InvoiceRequest>
|
||||
for *mut wire_cst_create_bolt_12_invoice_request
|
||||
{
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> crate::model::CreateBolt12InvoiceRequest {
|
||||
let wrap = unsafe { flutter_rust_bridge::for_generated::box_from_leak_ptr(self) };
|
||||
CstDecode::<crate::model::CreateBolt12InvoiceRequest>::cst_decode(*wrap).into()
|
||||
}
|
||||
}
|
||||
impl CstDecode<f64> for *mut f64 {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> f64 {
|
||||
@@ -10090,6 +10231,27 @@ mod io {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl CstDecode<crate::model::CreateBolt12InvoiceRequest>
|
||||
for wire_cst_create_bolt_12_invoice_request
|
||||
{
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> crate::model::CreateBolt12InvoiceRequest {
|
||||
crate::model::CreateBolt12InvoiceRequest {
|
||||
offer: self.offer.cst_decode(),
|
||||
invoice_request: self.invoice_request.cst_decode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl CstDecode<crate::model::CreateBolt12InvoiceResponse>
|
||||
for wire_cst_create_bolt_12_invoice_response
|
||||
{
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> crate::model::CreateBolt12InvoiceResponse {
|
||||
crate::model::CreateBolt12InvoiceResponse {
|
||||
invoice: self.invoice.cst_decode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl CstDecode<crate::bindings::CurrencyInfo> for wire_cst_currency_info {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
fn cst_decode(self) -> crate::bindings::CurrencyInfo {
|
||||
@@ -11156,8 +11318,8 @@ mod io {
|
||||
fn cst_decode(self) -> crate::model::PrepareReceiveResponse {
|
||||
crate::model::PrepareReceiveResponse {
|
||||
payment_method: self.payment_method.cst_decode(),
|
||||
amount: self.amount.cst_decode(),
|
||||
fees_sat: self.fees_sat.cst_decode(),
|
||||
amount: self.amount.cst_decode(),
|
||||
min_payer_amount_sat: self.min_payer_amount_sat.cst_decode(),
|
||||
max_payer_amount_sat: self.max_payer_amount_sat.cst_decode(),
|
||||
swapper_feerate: self.swapper_feerate.cst_decode(),
|
||||
@@ -11813,6 +11975,31 @@ mod io {
|
||||
Self::new_with_null_ptr()
|
||||
}
|
||||
}
|
||||
impl NewWithNullPtr for wire_cst_create_bolt_12_invoice_request {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
offer: core::ptr::null_mut(),
|
||||
invoice_request: core::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for wire_cst_create_bolt_12_invoice_request {
|
||||
fn default() -> Self {
|
||||
Self::new_with_null_ptr()
|
||||
}
|
||||
}
|
||||
impl NewWithNullPtr for wire_cst_create_bolt_12_invoice_response {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
invoice: core::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for wire_cst_create_bolt_12_invoice_response {
|
||||
fn default() -> Self {
|
||||
Self::new_with_null_ptr()
|
||||
}
|
||||
}
|
||||
impl NewWithNullPtr for wire_cst_currency_info {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
@@ -12519,8 +12706,8 @@ mod io {
|
||||
fn new_with_null_ptr() -> Self {
|
||||
Self {
|
||||
payment_method: Default::default(),
|
||||
amount: core::ptr::null_mut(),
|
||||
fees_sat: Default::default(),
|
||||
amount: core::ptr::null_mut(),
|
||||
min_payer_amount_sat: core::ptr::null_mut(),
|
||||
max_payer_amount_sat: core::ptr::null_mut(),
|
||||
swapper_feerate: core::ptr::null_mut(),
|
||||
@@ -12942,6 +13129,15 @@ mod io {
|
||||
wire__crate__bindings__BindingLiquidSdk_check_message_impl(that, req)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice(
|
||||
port_: i64,
|
||||
that: usize,
|
||||
req: *mut wire_cst_create_bolt_12_invoice_request,
|
||||
) {
|
||||
wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice_impl(port_, that, req)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_disconnect(
|
||||
port_: i64,
|
||||
@@ -13371,6 +13567,14 @@ mod io {
|
||||
)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_create_bolt_12_invoice_request(
|
||||
) -> *mut wire_cst_create_bolt_12_invoice_request {
|
||||
flutter_rust_bridge::for_generated::new_leak_box_ptr(
|
||||
wire_cst_create_bolt_12_invoice_request::new_with_null_ptr(),
|
||||
)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_f_64(value: f64) -> *mut f64 {
|
||||
flutter_rust_bridge::for_generated::new_leak_box_ptr(value)
|
||||
@@ -14083,6 +14287,17 @@ mod io {
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct wire_cst_create_bolt_12_invoice_request {
|
||||
offer: *mut wire_cst_list_prim_u_8_strict,
|
||||
invoice_request: *mut wire_cst_list_prim_u_8_strict,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct wire_cst_create_bolt_12_invoice_response {
|
||||
invoice: *mut wire_cst_list_prim_u_8_strict,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct wire_cst_currency_info {
|
||||
name: *mut wire_cst_list_prim_u_8_strict,
|
||||
fraction_size: u32,
|
||||
@@ -14966,8 +15181,8 @@ mod io {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct wire_cst_prepare_receive_response {
|
||||
payment_method: i32,
|
||||
amount: *mut wire_cst_receive_amount,
|
||||
fees_sat: u64,
|
||||
amount: *mut wire_cst_receive_amount,
|
||||
min_payer_amount_sat: *mut u64,
|
||||
max_payer_amount_sat: *mut u64,
|
||||
swapper_feerate: *mut f64,
|
||||
|
||||
@@ -170,12 +170,14 @@ pub(crate) mod chain;
|
||||
pub(crate) mod chain_swap;
|
||||
pub mod error;
|
||||
pub(crate) mod event;
|
||||
#[allow(deprecated)]
|
||||
#[cfg(feature = "frb")]
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
pub(crate) mod frb_generated;
|
||||
pub(crate) mod lnurl;
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
pub mod logger;
|
||||
#[allow(deprecated)]
|
||||
pub mod model;
|
||||
pub(crate) mod payjoin;
|
||||
pub mod persist;
|
||||
|
||||
@@ -14,9 +14,9 @@ use lwk_wollet::ElementsNetwork;
|
||||
use maybe_sync::{MaybeSend, MaybeSync};
|
||||
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
|
||||
use rusqlite::ToSql;
|
||||
use sdk_common::bitcoin::hashes::hex::ToHex as _;
|
||||
use sdk_common::prelude::*;
|
||||
use sdk_common::utils::Arc;
|
||||
use sdk_common::{bitcoin::hashes::hex::ToHex, lightning_with_bolt12::offers::offer::Offer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::PartialEq;
|
||||
use std::path::PathBuf;
|
||||
@@ -573,13 +573,13 @@ pub(crate) struct ReservedAddress {
|
||||
}
|
||||
|
||||
/// The send/receive methods supported by the SDK
|
||||
#[derive(Clone, Debug, EnumString, Serialize, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub enum PaymentMethod {
|
||||
#[strum(serialize = "lightning")]
|
||||
#[deprecated(since = "0.8.1", note = "Use `Bolt11Invoice` instead")]
|
||||
Lightning,
|
||||
#[strum(serialize = "bitcoin")]
|
||||
Bolt11Invoice,
|
||||
Bolt12Offer,
|
||||
BitcoinAddress,
|
||||
#[strum(serialize = "liquid")]
|
||||
LiquidAddress,
|
||||
}
|
||||
|
||||
@@ -599,7 +599,6 @@ pub enum ReceiveAmount {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PrepareReceiveRequest {
|
||||
pub payment_method: PaymentMethod,
|
||||
|
||||
/// The amount to be paid in either Bitcoin or another asset
|
||||
pub amount: Option<ReceiveAmount>,
|
||||
}
|
||||
@@ -608,8 +607,6 @@ pub struct PrepareReceiveRequest {
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct PrepareReceiveResponse {
|
||||
pub payment_method: PaymentMethod,
|
||||
pub amount: Option<ReceiveAmount>,
|
||||
|
||||
/// Generally represents the total fees that would be paid to send or receive this payment.
|
||||
///
|
||||
/// In case of Zero-Amount Receive Chain swaps, the swapper service fee (`swapper_feerate` times
|
||||
@@ -619,17 +616,16 @@ pub struct PrepareReceiveResponse {
|
||||
///
|
||||
/// In all other types of swaps, the swapper service fee is included in `fees_sat`.
|
||||
pub fees_sat: u64,
|
||||
|
||||
/// The amount to be paid in either Bitcoin or another asset
|
||||
pub amount: Option<ReceiveAmount>,
|
||||
/// The minimum amount the payer can send for this swap to succeed.
|
||||
///
|
||||
/// When the method is [PaymentMethod::LiquidAddress], this is empty.
|
||||
pub min_payer_amount_sat: Option<u64>,
|
||||
|
||||
/// The maximum amount the payer can send for this swap to succeed.
|
||||
///
|
||||
/// When the method is [PaymentMethod::LiquidAddress], this is empty.
|
||||
pub max_payer_amount_sat: Option<u64>,
|
||||
|
||||
/// The percentage of the sent amount that will count towards the service fee.
|
||||
///
|
||||
/// When the method is [PaymentMethod::LiquidAddress], this is empty.
|
||||
@@ -654,6 +650,22 @@ pub struct ReceivePaymentResponse {
|
||||
pub destination: String,
|
||||
}
|
||||
|
||||
/// An argument when calling [crate::sdk::LiquidSdk::create_bolt12_invoice].
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CreateBolt12InvoiceRequest {
|
||||
/// The BOLT12 offer
|
||||
pub offer: String,
|
||||
/// The invoice request created from the offer
|
||||
pub invoice_request: String,
|
||||
}
|
||||
|
||||
/// Returned when calling [crate::sdk::LiquidSdk::create_bolt12_invoice].
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct CreateBolt12InvoiceResponse {
|
||||
/// The BOLT12 invoice
|
||||
pub invoice: String,
|
||||
}
|
||||
|
||||
/// The minimum and maximum in satoshis of a Lightning or onchain payment.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Limits {
|
||||
@@ -1393,6 +1405,8 @@ pub struct ReceiveSwap {
|
||||
pub(crate) create_response_json: String,
|
||||
pub(crate) claim_private_key: String,
|
||||
pub(crate) invoice: String,
|
||||
/// The bolt12 offer, if used to create the swap
|
||||
pub(crate) bolt12_offer: Option<String>,
|
||||
pub(crate) payment_hash: Option<String>,
|
||||
pub(crate) destination_pubkey: Option<String>,
|
||||
pub(crate) description: Option<String>,
|
||||
@@ -1444,7 +1458,7 @@ impl ReceiveSwap {
|
||||
|
||||
let res = CreateReverseResponse {
|
||||
id: self.id.clone(),
|
||||
invoice: self.invoice.clone(),
|
||||
invoice: Some(self.invoice.clone()),
|
||||
swap_tree: internal_create_response.swap_tree.clone().into(),
|
||||
lockup_address: internal_create_response.lockup_address.clone(),
|
||||
refund_public_key: crate::utils::json_to_pubkey(
|
||||
@@ -1479,7 +1493,7 @@ impl ReceiveSwap {
|
||||
pub(crate) fn from_boltz_struct_to_json(
|
||||
create_response: &CreateReverseResponse,
|
||||
expected_swap_id: &str,
|
||||
expected_invoice: &str,
|
||||
expected_invoice: Option<&str>,
|
||||
) -> Result<String, PaymentError> {
|
||||
let internal_create_response =
|
||||
crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
|
||||
@@ -1510,6 +1524,35 @@ pub struct RefundableSwap {
|
||||
pub last_refund_tx_id: Option<String>,
|
||||
}
|
||||
|
||||
/// A BOLT12 offer
|
||||
#[derive(Clone, Debug, Derivative)]
|
||||
#[derivative(PartialEq)]
|
||||
pub(crate) struct Bolt12Offer {
|
||||
/// The BOLT12 offer string
|
||||
pub(crate) id: String,
|
||||
/// The description of the offer
|
||||
pub(crate) description: String,
|
||||
/// The private key used to create the offer
|
||||
pub(crate) private_key: String,
|
||||
/// The optional webhook URL where the offer invoice request will be sent
|
||||
pub(crate) webhook_url: Option<String>,
|
||||
/// The creation time of the offer
|
||||
pub(crate) created_at: u32,
|
||||
}
|
||||
impl Bolt12Offer {
|
||||
pub(crate) fn get_keypair(&self) -> Result<Keypair, SdkError> {
|
||||
utils::decode_keypair(&self.private_key)
|
||||
}
|
||||
}
|
||||
impl TryFrom<Bolt12Offer> for Offer {
|
||||
type Error = SdkError;
|
||||
|
||||
fn try_from(val: Bolt12Offer) -> Result<Self, Self::Error> {
|
||||
Offer::from_str(&val.id)
|
||||
.map_err(|e| SdkError::generic(format!("Failed to parse BOLT12 offer: {e:?}")))
|
||||
}
|
||||
}
|
||||
|
||||
/// The payment state of an individual payment.
|
||||
#[derive(Clone, Copy, Debug, Default, EnumString, Eq, PartialEq, Serialize, Hash)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
@@ -2432,33 +2475,6 @@ impl From<electrum_client::GetBalanceRes> for BtcScriptBalance {
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_invoice_amount {
|
||||
($invoice:expr) => {
|
||||
$invoice
|
||||
.parse::<Bolt11Invoice>()
|
||||
.expect("Expecting valid invoice")
|
||||
.amount_milli_satoshis()
|
||||
.expect("Expecting valid amount")
|
||||
/ 1000
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_invoice_description {
|
||||
($invoice:expr) => {
|
||||
match $invoice
|
||||
.trim()
|
||||
.parse::<Bolt11Invoice>()
|
||||
.expect("Expecting valid invoice")
|
||||
.description()
|
||||
{
|
||||
Bolt11InvoiceDescription::Direct(msg) => Some(msg.to_string()),
|
||||
Bolt11InvoiceDescription::Hash(_) => None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_updated_fields {
|
||||
($($var:ident),* $(,)?) => {{
|
||||
|
||||
240
lib/core/src/persist/bolt12_offer.rs
Normal file
240
lib/core/src/persist/bolt12_offer.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
use anyhow::Result;
|
||||
use rusqlite::{params, Connection, Params, Row};
|
||||
|
||||
use crate::model::*;
|
||||
use crate::persist::Persister;
|
||||
use crate::sync::model::data::Bolt12OfferSyncData;
|
||||
use crate::sync::model::RecordType;
|
||||
|
||||
impl Persister {
|
||||
pub(crate) fn insert_or_update_bolt12_offer_inner(
|
||||
con: &Connection,
|
||||
bolt12_offer: &Bolt12Offer,
|
||||
) -> Result<()> {
|
||||
con.execute(
|
||||
"
|
||||
INSERT INTO bolt12_offers (
|
||||
id,
|
||||
description,
|
||||
private_key,
|
||||
webhook_url,
|
||||
created_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT (id)
|
||||
DO UPDATE SET webhook_url = excluded.webhook_url
|
||||
",
|
||||
(
|
||||
&bolt12_offer.id,
|
||||
&bolt12_offer.description,
|
||||
&bolt12_offer.private_key,
|
||||
&bolt12_offer.webhook_url,
|
||||
&bolt12_offer.created_at,
|
||||
),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn insert_or_update_bolt12_offer(&self, bolt12_offer: &Bolt12Offer) -> Result<()> {
|
||||
let maybe_bolt12_offer = self.fetch_bolt12_offer_by_id(&bolt12_offer.id)?;
|
||||
let updated_fields = Bolt12OfferSyncData::updated_fields(maybe_bolt12_offer, bolt12_offer);
|
||||
|
||||
let mut con = self.get_connection()?;
|
||||
let tx = con.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
|
||||
|
||||
Self::insert_or_update_bolt12_offer_inner(&tx, bolt12_offer)?;
|
||||
|
||||
// Trigger a sync if:
|
||||
// - updated_fields is None (swap is inserted, not updated)
|
||||
// - updated_fields in a non empty list of updated fields
|
||||
if updated_fields.as_ref().is_none_or(|u| !u.is_empty()) {
|
||||
self.commit_outgoing(
|
||||
&tx,
|
||||
&bolt12_offer.id,
|
||||
RecordType::Bolt12Offer,
|
||||
updated_fields,
|
||||
)?;
|
||||
tx.commit()?;
|
||||
self.trigger_sync();
|
||||
} else {
|
||||
tx.commit()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_bolt12_offers_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,
|
||||
description,
|
||||
private_key,
|
||||
webhook_url,
|
||||
created_at
|
||||
FROM bolt12_offers
|
||||
{where_clause_str}
|
||||
ORDER BY created_at
|
||||
"
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_bolt12_offer_by_id(&self, id: &str) -> Result<Option<Bolt12Offer>> {
|
||||
let con: Connection = self.get_connection()?;
|
||||
let query = Self::list_bolt12_offers_query(vec!["id = ?".to_string()]);
|
||||
let res = con.query_row(&query, [id], Self::sql_row_to_bolt12_offer);
|
||||
|
||||
Ok(res.ok())
|
||||
}
|
||||
|
||||
pub(crate) fn fetch_bolt12_offer_by_description(
|
||||
&self,
|
||||
description: &str,
|
||||
) -> Result<Option<Bolt12Offer>> {
|
||||
let con: Connection = self.get_connection()?;
|
||||
let query = Self::list_bolt12_offers_query(vec!["description = ?".to_string()]);
|
||||
let res = con.query_row(&query, [description], Self::sql_row_to_bolt12_offer);
|
||||
|
||||
Ok(res.ok())
|
||||
}
|
||||
|
||||
fn sql_row_to_bolt12_offer(row: &Row) -> rusqlite::Result<Bolt12Offer> {
|
||||
Ok(Bolt12Offer {
|
||||
id: row.get(0)?,
|
||||
description: row.get(1)?,
|
||||
private_key: row.get(2)?,
|
||||
webhook_url: row.get(3)?,
|
||||
created_at: row.get(4)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn list_bolt12_offers(&self) -> Result<Vec<Bolt12Offer>> {
|
||||
let con: Connection = self.get_connection()?;
|
||||
self.list_bolt12_offers_where(&con, vec![], params![])
|
||||
}
|
||||
|
||||
pub(crate) fn list_bolt12_offers_where<P>(
|
||||
&self,
|
||||
con: &Connection,
|
||||
where_clauses: Vec<String>,
|
||||
params: P,
|
||||
) -> Result<Vec<Bolt12Offer>>
|
||||
where
|
||||
P: Params,
|
||||
{
|
||||
let query = Self::list_bolt12_offers_query(where_clauses);
|
||||
let offers = con
|
||||
.prepare(&query)?
|
||||
.query_map(params, Self::sql_row_to_bolt12_offer)?
|
||||
.map(|i| i.unwrap())
|
||||
.collect();
|
||||
Ok(offers)
|
||||
}
|
||||
|
||||
pub(crate) fn list_bolt12_offers_by_webhook_url(
|
||||
&self,
|
||||
webhook_url: &str,
|
||||
) -> Result<Vec<Bolt12Offer>> {
|
||||
let con = self.get_connection()?;
|
||||
let where_clause = vec!["webhook_url = ?".to_string()];
|
||||
self.list_bolt12_offers_where(&con, where_clause, params![webhook_url])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::test_utils::{bolt12_offer::new_bolt12_offer, persist::create_persister};
|
||||
use anyhow::{anyhow, Result};
|
||||
use rusqlite::params;
|
||||
|
||||
#[cfg(feature = "browser-tests")]
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[sdk_macros::test_all]
|
||||
fn test_fetch_bolt12_offer() -> Result<()> {
|
||||
create_persister!(storage);
|
||||
let bolt12_offer = new_bolt12_offer(None, Some("http://localhost:4004/notify".to_string()));
|
||||
|
||||
storage.insert_or_update_bolt12_offer(&bolt12_offer)?;
|
||||
// Fetch bolt12 offer by id
|
||||
assert!(storage.fetch_send_swap_by_id(&bolt12_offer.id).is_ok());
|
||||
// Fetch bolt12 offer by description
|
||||
assert!(storage
|
||||
.fetch_bolt12_offer_by_description(&bolt12_offer.description)
|
||||
.is_ok());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sdk_macros::test_all]
|
||||
fn test_list_bolt12_offers() -> Result<()> {
|
||||
create_persister!(storage);
|
||||
storage.insert_or_update_bolt12_offer(&new_bolt12_offer(
|
||||
None,
|
||||
Some("http://localhost:4004/notify".to_string()),
|
||||
))?;
|
||||
storage.insert_or_update_bolt12_offer(&new_bolt12_offer(
|
||||
Some("another".to_string()),
|
||||
Some("http://other.local:4004/notify".to_string()),
|
||||
))?;
|
||||
storage.insert_or_update_bolt12_offer(&new_bolt12_offer(
|
||||
Some("another-other".to_string()),
|
||||
Some("http://localhost:4004/notify".to_string()),
|
||||
))?;
|
||||
|
||||
let con = storage.get_connection()?;
|
||||
let offers = storage.list_bolt12_offers_where(&con, vec![], params![])?;
|
||||
assert_eq!(offers.len(), 3);
|
||||
|
||||
let offers = storage.list_bolt12_offers_by_webhook_url("http://localhost:4004/notify")?;
|
||||
assert_eq!(offers.len(), 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sdk_macros::test_all]
|
||||
fn test_update_bolt12_offer() -> Result<()> {
|
||||
create_persister!(storage);
|
||||
|
||||
let mut bolt12_offer =
|
||||
new_bolt12_offer(None, Some("http://localhost:4004/notify".to_string()));
|
||||
storage.insert_or_update_bolt12_offer(&bolt12_offer)?;
|
||||
|
||||
let offers = storage.list_bolt12_offers_by_webhook_url("http://localhost:4004/notify")?;
|
||||
assert_eq!(offers.len(), 1);
|
||||
|
||||
bolt12_offer.webhook_url = Some("http://other.local:4004/notify".to_string());
|
||||
storage.insert_or_update_bolt12_offer(&bolt12_offer)?;
|
||||
|
||||
let offers = storage.list_bolt12_offers_by_webhook_url("http://localhost:4004/notify")?;
|
||||
assert_eq!(offers.len(), 0);
|
||||
|
||||
let offers = storage.list_bolt12_offers_by_webhook_url("http://other.local:4004/notify")?;
|
||||
assert_eq!(offers.len(), 1);
|
||||
|
||||
let mut offer = storage
|
||||
.fetch_bolt12_offer_by_id(&bolt12_offer.id)?
|
||||
.ok_or(anyhow!("Could not find BOLT12 offer in database"))?;
|
||||
|
||||
assert_eq!(offer.id, bolt12_offer.id);
|
||||
assert_eq!(
|
||||
offer.webhook_url,
|
||||
Some("http://other.local:4004/notify".to_string())
|
||||
);
|
||||
|
||||
offer.webhook_url = None;
|
||||
storage.insert_or_update_bolt12_offer(&offer)?;
|
||||
|
||||
let offers = storage.list_bolt12_offers_by_webhook_url("http://other.local:4004/notify")?;
|
||||
assert_eq!(offers.len(), 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -328,5 +328,15 @@ pub(crate) fn current_migrations(network: LiquidNetwork) -> Vec<&'static str> {
|
||||
ALTER TABLE send_swaps ADD COLUMN refund_address TEXT;
|
||||
ALTER TABLE chain_swaps ADD COLUMN refund_address TEXT;
|
||||
",
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS bolt12_offers(
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
description TEXT NOT NULL,
|
||||
private_key TEXT NOT NULL,
|
||||
webhook_url TEXT,
|
||||
created_at INTEGER NOT NULL
|
||||
) STRICT;
|
||||
",
|
||||
"ALTER TABLE receive_swaps ADD COLUMN bolt12_offer TEXT;",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod address;
|
||||
pub(crate) mod asset_metadata;
|
||||
mod backup;
|
||||
pub(crate) mod bolt12_offer;
|
||||
pub(crate) mod cache;
|
||||
pub(crate) mod chain;
|
||||
mod migrations;
|
||||
@@ -13,10 +14,9 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::ops::Not;
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use crate::lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription};
|
||||
use crate::model::*;
|
||||
use crate::sync::model::RecordType;
|
||||
use crate::{get_invoice_description, utils};
|
||||
use crate::utils;
|
||||
use anyhow::{anyhow, Result};
|
||||
use boltz_client::boltz::{ChainPair, ReversePair, SubmarinePair};
|
||||
use log::{error, warn};
|
||||
@@ -501,6 +501,7 @@ impl Persister {
|
||||
rs.created_at,
|
||||
rs.timeout_block_height,
|
||||
rs.invoice,
|
||||
rs.bolt12_offer,
|
||||
rs.payment_hash,
|
||||
rs.destination_pubkey,
|
||||
rs.description,
|
||||
@@ -612,67 +613,68 @@ impl Persister {
|
||||
let maybe_receive_swap_created_at: Option<u32> = row.get(9)?;
|
||||
let maybe_receive_swap_timeout_block_height: Option<u32> = row.get(10)?;
|
||||
let maybe_receive_swap_invoice: Option<String> = row.get(11)?;
|
||||
let maybe_receive_swap_payment_hash: Option<String> = row.get(12)?;
|
||||
let maybe_receive_swap_destination_pubkey: Option<String> = row.get(13)?;
|
||||
let maybe_receive_swap_description: Option<String> = row.get(14)?;
|
||||
let maybe_receive_swap_preimage: Option<String> = row.get(15)?;
|
||||
let maybe_receive_swap_payer_amount_sat: Option<u64> = row.get(16)?;
|
||||
let maybe_receive_swap_receiver_amount_sat: Option<u64> = row.get(17)?;
|
||||
let maybe_receive_swap_receiver_state: Option<PaymentState> = row.get(18)?;
|
||||
let maybe_receive_swap_pair_fees_json: Option<String> = row.get(19)?;
|
||||
let maybe_receive_swap_bolt12_offer: Option<String> = row.get(12)?;
|
||||
let maybe_receive_swap_payment_hash: Option<String> = row.get(13)?;
|
||||
let maybe_receive_swap_destination_pubkey: Option<String> = row.get(14)?;
|
||||
let maybe_receive_swap_description: Option<String> = row.get(15)?;
|
||||
let maybe_receive_swap_preimage: Option<String> = row.get(16)?;
|
||||
let maybe_receive_swap_payer_amount_sat: Option<u64> = row.get(17)?;
|
||||
let maybe_receive_swap_receiver_amount_sat: Option<u64> = row.get(18)?;
|
||||
let maybe_receive_swap_receiver_state: Option<PaymentState> = row.get(19)?;
|
||||
let maybe_receive_swap_pair_fees_json: Option<String> = row.get(20)?;
|
||||
let maybe_receive_swap_pair_fees: Option<ReversePair> =
|
||||
maybe_receive_swap_pair_fees_json.and_then(|pair| serde_json::from_str(&pair).ok());
|
||||
let maybe_receive_swap_claim_tx_id: Option<String> = row.get(20)?;
|
||||
let maybe_receive_swap_claim_tx_id: Option<String> = row.get(21)?;
|
||||
|
||||
let maybe_send_swap_id: Option<String> = row.get(21)?;
|
||||
let maybe_send_swap_created_at: Option<u32> = row.get(22)?;
|
||||
let maybe_send_swap_timeout_block_height: Option<u32> = row.get(23)?;
|
||||
let maybe_send_swap_invoice: Option<String> = row.get(24)?;
|
||||
let maybe_send_swap_bolt12_offer: Option<String> = row.get(25)?;
|
||||
let maybe_send_swap_payment_hash: Option<String> = row.get(26)?;
|
||||
let maybe_send_swap_destination_pubkey: Option<String> = row.get(27)?;
|
||||
let maybe_send_swap_description: Option<String> = row.get(28)?;
|
||||
let maybe_send_swap_preimage: Option<String> = row.get(29)?;
|
||||
let maybe_send_swap_refund_tx_id: Option<String> = row.get(30)?;
|
||||
let maybe_send_swap_payer_amount_sat: Option<u64> = row.get(31)?;
|
||||
let maybe_send_swap_receiver_amount_sat: Option<u64> = row.get(32)?;
|
||||
let maybe_send_swap_state: Option<PaymentState> = row.get(33)?;
|
||||
let maybe_send_swap_pair_fees_json: Option<String> = row.get(34)?;
|
||||
let maybe_send_swap_id: Option<String> = row.get(22)?;
|
||||
let maybe_send_swap_created_at: Option<u32> = row.get(23)?;
|
||||
let maybe_send_swap_timeout_block_height: Option<u32> = row.get(24)?;
|
||||
let maybe_send_swap_invoice: Option<String> = row.get(25)?;
|
||||
let maybe_send_swap_bolt12_offer: Option<String> = row.get(26)?;
|
||||
let maybe_send_swap_payment_hash: Option<String> = row.get(27)?;
|
||||
let maybe_send_swap_destination_pubkey: Option<String> = row.get(28)?;
|
||||
let maybe_send_swap_description: Option<String> = row.get(29)?;
|
||||
let maybe_send_swap_preimage: Option<String> = row.get(30)?;
|
||||
let maybe_send_swap_refund_tx_id: Option<String> = row.get(31)?;
|
||||
let maybe_send_swap_payer_amount_sat: Option<u64> = row.get(32)?;
|
||||
let maybe_send_swap_receiver_amount_sat: Option<u64> = row.get(33)?;
|
||||
let maybe_send_swap_state: Option<PaymentState> = row.get(34)?;
|
||||
let maybe_send_swap_pair_fees_json: Option<String> = row.get(35)?;
|
||||
let maybe_send_swap_pair_fees: Option<SubmarinePair> =
|
||||
maybe_send_swap_pair_fees_json.and_then(|pair| serde_json::from_str(&pair).ok());
|
||||
|
||||
let maybe_chain_swap_id: Option<String> = row.get(35)?;
|
||||
let maybe_chain_swap_created_at: Option<u32> = row.get(36)?;
|
||||
let maybe_chain_swap_timeout_block_height: Option<u32> = row.get(37)?;
|
||||
let maybe_chain_swap_direction: Option<Direction> = row.get(38)?;
|
||||
let maybe_chain_swap_preimage: Option<String> = row.get(39)?;
|
||||
let maybe_chain_swap_description: Option<String> = row.get(40)?;
|
||||
let maybe_chain_swap_refund_tx_id: Option<String> = row.get(41)?;
|
||||
let maybe_chain_swap_payer_amount_sat: Option<u64> = row.get(42)?;
|
||||
let maybe_chain_swap_receiver_amount_sat: Option<u64> = row.get(43)?;
|
||||
let maybe_chain_swap_claim_address: Option<String> = row.get(44)?;
|
||||
let maybe_chain_swap_state: Option<PaymentState> = row.get(45)?;
|
||||
let maybe_chain_swap_pair_fees_json: Option<String> = row.get(46)?;
|
||||
let maybe_chain_swap_id: Option<String> = row.get(36)?;
|
||||
let maybe_chain_swap_created_at: Option<u32> = row.get(37)?;
|
||||
let maybe_chain_swap_timeout_block_height: Option<u32> = row.get(38)?;
|
||||
let maybe_chain_swap_direction: Option<Direction> = row.get(39)?;
|
||||
let maybe_chain_swap_preimage: Option<String> = row.get(40)?;
|
||||
let maybe_chain_swap_description: Option<String> = row.get(41)?;
|
||||
let maybe_chain_swap_refund_tx_id: Option<String> = row.get(42)?;
|
||||
let maybe_chain_swap_payer_amount_sat: Option<u64> = row.get(43)?;
|
||||
let maybe_chain_swap_receiver_amount_sat: Option<u64> = row.get(44)?;
|
||||
let maybe_chain_swap_claim_address: Option<String> = row.get(45)?;
|
||||
let maybe_chain_swap_state: Option<PaymentState> = row.get(46)?;
|
||||
let maybe_chain_swap_pair_fees_json: Option<String> = row.get(47)?;
|
||||
let maybe_chain_swap_pair_fees: Option<ChainPair> =
|
||||
maybe_chain_swap_pair_fees_json.and_then(|pair| serde_json::from_str(&pair).ok());
|
||||
let maybe_chain_swap_actual_payer_amount_sat: Option<u64> = row.get(47)?;
|
||||
let maybe_chain_swap_accepted_receiver_amount_sat: Option<u64> = row.get(48)?;
|
||||
let maybe_chain_swap_auto_accepted_fees: Option<bool> = row.get(49)?;
|
||||
let maybe_chain_swap_claim_tx_id: Option<String> = row.get(50)?;
|
||||
let maybe_chain_swap_actual_payer_amount_sat: Option<u64> = row.get(48)?;
|
||||
let maybe_chain_swap_accepted_receiver_amount_sat: Option<u64> = row.get(49)?;
|
||||
let maybe_chain_swap_auto_accepted_fees: Option<bool> = row.get(50)?;
|
||||
let maybe_chain_swap_claim_tx_id: Option<String> = row.get(51)?;
|
||||
|
||||
let maybe_swap_refund_tx_amount_sat: Option<u64> = row.get(51)?;
|
||||
let maybe_swap_refund_tx_amount_sat: Option<u64> = row.get(52)?;
|
||||
|
||||
let maybe_payment_details_destination: Option<String> = row.get(52)?;
|
||||
let maybe_payment_details_description: Option<String> = row.get(53)?;
|
||||
let maybe_payment_details_lnurl_info_json: Option<String> = row.get(54)?;
|
||||
let maybe_payment_details_destination: Option<String> = row.get(53)?;
|
||||
let maybe_payment_details_description: Option<String> = row.get(54)?;
|
||||
let maybe_payment_details_lnurl_info_json: Option<String> = row.get(55)?;
|
||||
let maybe_payment_details_lnurl_info: Option<LnUrlInfo> =
|
||||
maybe_payment_details_lnurl_info_json.and_then(|info| serde_json::from_str(&info).ok());
|
||||
let maybe_payment_details_bip353_address: Option<String> = row.get(55)?;
|
||||
let maybe_payment_details_asset_fees: Option<u64> = row.get(56)?;
|
||||
let maybe_payment_details_bip353_address: Option<String> = row.get(56)?;
|
||||
let maybe_payment_details_asset_fees: Option<u64> = row.get(57)?;
|
||||
|
||||
let maybe_asset_metadata_name: Option<String> = row.get(57)?;
|
||||
let maybe_asset_metadata_ticker: Option<String> = row.get(58)?;
|
||||
let maybe_asset_metadata_precision: Option<u8> = row.get(59)?;
|
||||
let maybe_asset_metadata_name: Option<String> = row.get(58)?;
|
||||
let maybe_asset_metadata_ticker: Option<String> = row.get(59)?;
|
||||
let maybe_asset_metadata_precision: Option<u8> = row.get(60)?;
|
||||
|
||||
let (swap, payment_type) = match maybe_receive_swap_id {
|
||||
Some(receive_swap_id) => {
|
||||
@@ -687,12 +689,14 @@ impl Persister {
|
||||
.unwrap_or(0),
|
||||
preimage: maybe_receive_swap_preimage,
|
||||
invoice: maybe_receive_swap_invoice.clone(),
|
||||
bolt12_offer: None, // Bolt12 not supported for Receive Swaps
|
||||
bolt12_offer: maybe_receive_swap_bolt12_offer,
|
||||
payment_hash: maybe_receive_swap_payment_hash,
|
||||
destination_pubkey: maybe_receive_swap_destination_pubkey,
|
||||
description: maybe_receive_swap_description.unwrap_or_else(|| {
|
||||
maybe_receive_swap_invoice
|
||||
.and_then(|bolt11| get_invoice_description!(bolt11))
|
||||
.and_then(|invoice| {
|
||||
utils::get_invoice_description(&invoice).ok().flatten()
|
||||
})
|
||||
.unwrap_or("Lightning payment".to_string())
|
||||
}),
|
||||
payer_amount_sat,
|
||||
|
||||
@@ -63,6 +63,7 @@ impl Persister {
|
||||
let rows_affected = con.execute(
|
||||
"UPDATE receive_swaps
|
||||
SET
|
||||
bolt12_offer = :bolt12_offer,
|
||||
description = :description,
|
||||
claim_address = :claim_address,
|
||||
claim_tx_id = :claim_tx_id,
|
||||
@@ -76,6 +77,7 @@ impl Persister {
|
||||
version = :version",
|
||||
named_params! {
|
||||
":id": &receive_swap.id,
|
||||
":bolt12_offer": &receive_swap.bolt12_offer,
|
||||
":description": &receive_swap.description,
|
||||
":claim_address": &receive_swap.claim_address,
|
||||
":claim_tx_id": &receive_swap.claim_tx_id,
|
||||
@@ -140,6 +142,7 @@ impl Persister {
|
||||
rs.create_response_json,
|
||||
rs.claim_private_key,
|
||||
rs.invoice,
|
||||
rs.bolt12_offer,
|
||||
rs.payment_hash,
|
||||
rs.destination_pubkey,
|
||||
rs.timeout_block_height,
|
||||
@@ -193,25 +196,26 @@ impl Persister {
|
||||
create_response_json: row.get(2)?,
|
||||
claim_private_key: row.get(3)?,
|
||||
invoice: row.get(4)?,
|
||||
payment_hash: row.get(5)?,
|
||||
destination_pubkey: row.get(6)?,
|
||||
timeout_block_height: row.get(7)?,
|
||||
description: row.get(8)?,
|
||||
payer_amount_sat: row.get(9)?,
|
||||
receiver_amount_sat: row.get(10)?,
|
||||
claim_fees_sat: row.get(11)?,
|
||||
claim_address: row.get(12)?,
|
||||
claim_tx_id: row.get(13)?,
|
||||
lockup_tx_id: row.get(14)?,
|
||||
mrh_address: row.get(15)?,
|
||||
mrh_tx_id: row.get(16)?,
|
||||
created_at: row.get(17)?,
|
||||
state: row.get(18)?,
|
||||
pair_fees_json: row.get(19)?,
|
||||
bolt12_offer: row.get(5)?,
|
||||
payment_hash: row.get(6)?,
|
||||
destination_pubkey: row.get(7)?,
|
||||
timeout_block_height: row.get(8)?,
|
||||
description: row.get(9)?,
|
||||
payer_amount_sat: row.get(10)?,
|
||||
receiver_amount_sat: row.get(11)?,
|
||||
claim_fees_sat: row.get(12)?,
|
||||
claim_address: row.get(13)?,
|
||||
claim_tx_id: row.get(14)?,
|
||||
lockup_tx_id: row.get(15)?,
|
||||
mrh_address: row.get(16)?,
|
||||
mrh_tx_id: row.get(17)?,
|
||||
created_at: row.get(18)?,
|
||||
state: row.get(19)?,
|
||||
pair_fees_json: row.get(20)?,
|
||||
metadata: SwapMetadata {
|
||||
version: row.get(20)?,
|
||||
last_updated_at: row.get(21)?,
|
||||
is_local: row.get::<usize, Option<bool>>(22)?.unwrap_or(true),
|
||||
version: row.get(21)?,
|
||||
last_updated_at: row.get(22)?,
|
||||
is_local: row.get::<usize, Option<bool>>(23)?.unwrap_or(true),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -385,7 +389,7 @@ impl InternalCreateReverseResponse {
|
||||
pub(crate) fn try_convert_from_boltz(
|
||||
boltz_create_response: &CreateReverseResponse,
|
||||
expected_swap_id: &str,
|
||||
expected_invoice: &str,
|
||||
expected_invoice: Option<&str>,
|
||||
) -> Result<Self, PaymentError> {
|
||||
// Do not store the CreateResponse fields that are already stored separately
|
||||
// Before skipping them, ensure they match the separately stored ones
|
||||
@@ -393,10 +397,15 @@ impl InternalCreateReverseResponse {
|
||||
boltz_create_response.id == expected_swap_id,
|
||||
PaymentError::PersistError
|
||||
);
|
||||
ensure_sdk!(
|
||||
boltz_create_response.invoice == expected_invoice,
|
||||
PaymentError::PersistError
|
||||
);
|
||||
match (&boltz_create_response.invoice, expected_invoice) {
|
||||
(Some(invoice), Some(expected_invoice)) => {
|
||||
ensure_sdk!(invoice == expected_invoice, PaymentError::PersistError);
|
||||
}
|
||||
(None, None) => {}
|
||||
_ => {
|
||||
return Err(PaymentError::PersistError);
|
||||
}
|
||||
}
|
||||
|
||||
let res = InternalCreateReverseResponse {
|
||||
swap_tree: boltz_create_response.swap_tree.clone().into(),
|
||||
|
||||
@@ -9,6 +9,7 @@ use tokio::sync::broadcast;
|
||||
|
||||
use super::{cache::KEY_LAST_DERIVATION_INDEX, PaymentTxDetails, Persister, Swap};
|
||||
use crate::{
|
||||
model::Bolt12Offer,
|
||||
persist::where_clauses_to_string,
|
||||
sync::model::{
|
||||
data::LAST_DERIVATION_INDEX_DATA_ID, Record, RecordType, SyncOutgoingChanges, SyncSettings,
|
||||
@@ -496,6 +497,33 @@ impl Persister {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn commit_incoming_bolt12_offer(
|
||||
&self,
|
||||
bolt12_offer: Bolt12Offer,
|
||||
sync_state: &SyncState,
|
||||
last_commit_time: Option<u32>,
|
||||
) -> Result<()> {
|
||||
let mut con = self.get_connection()?;
|
||||
let tx = con.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||
|
||||
if let Some(last_commit_time) = last_commit_time {
|
||||
Self::check_commit_update(&tx, &sync_state.record_id, last_commit_time)?;
|
||||
}
|
||||
|
||||
Self::insert_or_update_bolt12_offer_inner(&tx, &bolt12_offer)?;
|
||||
|
||||
Self::set_sync_state_stmt(&tx)?.execute(named_params! {
|
||||
":data_id": &sync_state.data_id,
|
||||
":record_id": &sync_state.record_id,
|
||||
":record_revision": &sync_state.record_revision,
|
||||
":is_local": &sync_state.is_local,
|
||||
})?;
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn subscribe_sync_trigger(&self) -> Result<broadcast::Receiver<()>> {
|
||||
match self.sync_trigger {
|
||||
Some(ref sender) => Ok(sender.subscribe()),
|
||||
|
||||
@@ -272,6 +272,7 @@ mod test {
|
||||
create_response_json: r#"{"swap_tree":{"claim_leaf":{"output":"82012088a91460bac83421a184c3cf912ae231df8e3f0ce6ac5488204c9f9e348b27b1257c51f3ad2a05589ac8f3af72246ff3094950441cdf826b47ac","version":196},"refund_leaf":{"output":"209916729fe59068c8544b8070a32f653ed9cb550e76a5caaeda557aadf9e2cc2fad03e48c31b1","version":196}},"lockup_address":"lq1pqw632yu95t23pa7jr4s746g68nwl0ukkfvncs3q7t66f9gjqj7ccj2nwx8verw57l2zn029vlnwjuvrpm4yxnz3tccfks9e8rdy2r9tu586g8fya887j","refund_public_key":"029916729fe59068c8544b8070a32f653ed9cb550e76a5caaeda557aadf9e2cc2f","timeout_block_height":3247332,"onchain_amount":1071,"blinding_key":"605f50d0c0516c800594e1d44b9ceaeb7fa7a4258d6357043cc2daaa13e48895"}"#.to_string(),
|
||||
claim_private_key: "2f23dbb3c13e30ac8df594369b62ef1eb34a50197d7acc15db413961d90810e5".to_string(),
|
||||
invoice: "lnbc11u1pn65lr9sp5xfmwgmaddn2acwc7rr4xhj3k5dy4tyfhma57tpfp0z7eyp90fdjspp5a48w03jc5dtzqnyyqw727naffpcdvhj7s9hen45zh9m3auhfmx2qdpz2djkuepqw3hjqnpdgf2yxgrpv3j8yetnwvxqyp2xqcqz95rzjqfxfl8353vnmzftu28e662s9tzdv3ua0wgjxlucff9gyg8xlsf45wzzxeyqq28qqqqqqqqqqqqqqq9gq2y9qyysgq37h36xnz7khazpus03846hml4q8y8qekrzwh5ql36fy6l7dmgyuq3d9jyvnmm3h8tmxn7ae20wgte2elq4akpu3mqnyj626zy69drmqq95tqch".to_string(),
|
||||
bolt12_offer: None,
|
||||
payment_hash: Some("ed4ee7c658a356204c8403bcaf4fa94870d65e5e816f99d682b9771ef2e9d994".to_string()),
|
||||
destination_pubkey: Some("03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f".to_string()),
|
||||
description: Some("Test payment".to_string()),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,9 +8,10 @@ use crate::{
|
||||
use anyhow::{anyhow, Result};
|
||||
use boltz_client::{
|
||||
boltz::{
|
||||
self, BoltzApiClientV2, ChainPair, Cooperative, CreateChainRequest, CreateChainResponse,
|
||||
CreateReverseRequest, CreateReverseResponse, CreateSubmarineRequest,
|
||||
CreateSubmarineResponse, ReversePair, SubmarineClaimTxResponse, SubmarinePair,
|
||||
self, BoltzApiClientV2, ChainPair, Cooperative, CreateBolt12OfferRequest,
|
||||
CreateChainRequest, CreateChainResponse, CreateReverseRequest, CreateReverseResponse,
|
||||
CreateSubmarineRequest, CreateSubmarineResponse, GetBolt12ParamsResponse, GetNodesResponse,
|
||||
ReversePair, SubmarineClaimTxResponse, SubmarinePair, UpdateBolt12OfferRequest, WsRequest,
|
||||
},
|
||||
elements::secp256k1_zkp::{MusigPartialSignature, MusigPubNonce},
|
||||
network::Chain,
|
||||
@@ -43,14 +44,16 @@ pub struct BoltzSwapper<P: ProxyUrlFetcher> {
|
||||
liquid_client: OnceLock<LiquidClient>,
|
||||
bitcoin_client: OnceLock<BitcoinClient>,
|
||||
proxy_url: Arc<P>,
|
||||
subscription_notifier: broadcast::Sender<String>,
|
||||
request_notifier: broadcast::Sender<WsRequest>,
|
||||
update_notifier: broadcast::Sender<boltz::SwapStatus>,
|
||||
invoice_request_notifier: broadcast::Sender<boltz::InvoiceRequest>,
|
||||
}
|
||||
|
||||
impl<P: ProxyUrlFetcher> BoltzSwapper<P> {
|
||||
pub fn new(config: Config, proxy_url: Arc<P>) -> Result<Self, SdkError> {
|
||||
let (subscription_notifier, _) = broadcast::channel::<String>(30);
|
||||
let (request_notifier, _) = broadcast::channel::<WsRequest>(30);
|
||||
let (update_notifier, _) = broadcast::channel::<boltz::SwapStatus>(30);
|
||||
let (invoice_request_notifier, _) = broadcast::channel::<boltz::InvoiceRequest>(30);
|
||||
|
||||
Ok(Self {
|
||||
proxy_url,
|
||||
@@ -58,8 +61,9 @@ impl<P: ProxyUrlFetcher> BoltzSwapper<P> {
|
||||
boltz_client: OnceLock::new(),
|
||||
liquid_client: OnceLock::new(),
|
||||
bitcoin_client: OnceLock::new(),
|
||||
subscription_notifier,
|
||||
request_notifier,
|
||||
update_notifier,
|
||||
invoice_request_notifier,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -442,7 +446,7 @@ impl<P: ProxyUrlFetcher> Swapper for BoltzSwapper<P> {
|
||||
Swap::Chain(chain_swap) => match chain_swap.direction {
|
||||
Direction::Incoming => {
|
||||
let Some(broadcast_fee_rate_sat_per_vb) = broadcast_fee_rate_sat_per_vb else {
|
||||
return Err(PaymentError::generic(&format!("No broadcast fee rate provided when refunding incoming Chain Swap {swap_id}")));
|
||||
return Err(PaymentError::generic(format!("No broadcast fee rate provided when refunding incoming Chain Swap {swap_id}")));
|
||||
};
|
||||
|
||||
Transaction::Bitcoin(
|
||||
@@ -520,4 +524,46 @@ impl<P: ProxyUrlFetcher> Swapper for BoltzSwapper<P> {
|
||||
info!("Received BOLT12 invoice response: {invoice_res:?}");
|
||||
Ok(invoice_res.invoice)
|
||||
}
|
||||
|
||||
async fn create_bolt12_offer(&self, req: CreateBolt12OfferRequest) -> Result<(), SdkError> {
|
||||
self.get_boltz_client()
|
||||
.await?
|
||||
.inner
|
||||
.post_bolt12_offer(req)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_bolt12_offer(&self, req: UpdateBolt12OfferRequest) -> Result<(), SdkError> {
|
||||
self.get_boltz_client()
|
||||
.await?
|
||||
.inner
|
||||
.patch_bolt12_offer(req)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_bolt12_offer(&self, offer: &str, signature: &str) -> Result<(), SdkError> {
|
||||
self.get_boltz_client()
|
||||
.await?
|
||||
.inner
|
||||
.delete_bolt12_offer(offer, signature)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_bolt12_params(&self) -> Result<GetBolt12ParamsResponse, SdkError> {
|
||||
let res = self
|
||||
.get_boltz_client()
|
||||
.await?
|
||||
.inner
|
||||
.get_bolt12_params()
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn get_nodes(&self) -> Result<GetNodesResponse, SdkError> {
|
||||
let res = self.get_boltz_client().await?.inner.get_nodes().await?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use anyhow::Result;
|
||||
use boltz_client::boltz::{
|
||||
self,
|
||||
tokio_tungstenite_wasm::{Message, WebSocketStream},
|
||||
WsRequest, WsResponse,
|
||||
InvoiceRequestParams, WsRequest, WsResponse,
|
||||
};
|
||||
use futures_util::{stream::SplitSink, SinkExt, StreamExt};
|
||||
use log::{debug, error, info, warn};
|
||||
@@ -17,20 +17,17 @@ use tokio::sync::{broadcast, watch};
|
||||
use tokio_with_wasm::alias as tokio;
|
||||
|
||||
impl<P: ProxyUrlFetcher> BoltzSwapper<P> {
|
||||
async fn send_subscription(
|
||||
async fn send_request(
|
||||
&self,
|
||||
swap_id: String,
|
||||
ws_request: WsRequest,
|
||||
sender: &mut SplitSink<WebSocketStream, Message>,
|
||||
) {
|
||||
info!("Subscribing to status updates for swap ID {swap_id}");
|
||||
|
||||
let subscription = WsRequest::subscribe_swap_request(&swap_id);
|
||||
match serde_json::to_string(&subscription) {
|
||||
Ok(subscribe_json) => match sender.send(Message::Text(subscribe_json.into())).await {
|
||||
Ok(_) => info!("Subscribed"),
|
||||
Err(e) => error!("Failed to subscribe to {swap_id}: {e:?}"),
|
||||
match serde_json::to_string(&ws_request) {
|
||||
Ok(ref req_json) => match sender.send(Message::Text(req_json.into())).await {
|
||||
Ok(_) => debug!("Sent request: {req_json}"),
|
||||
Err(e) => error!("Failed to send {req_json}: {e:?}"),
|
||||
},
|
||||
Err(e) => error!("Invalid subscription msg: {e:?}"),
|
||||
Err(e) => error!("Error encoding request: {e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,10 +57,10 @@ impl<P: ProxyUrlFetcher> SwapperStatusStream for BoltzSwapper<P> {
|
||||
Ok(ws_stream) => {
|
||||
let (mut sender, mut receiver) = ws_stream.split();
|
||||
|
||||
let mut tracked_swap_ids: HashSet<String> = HashSet::new();
|
||||
let mut subscription_stream = self.subscription_notifier.subscribe();
|
||||
let mut tracked_ids: HashSet<String> = HashSet::new();
|
||||
let mut request_stream = self.request_notifier.subscribe();
|
||||
|
||||
callback.subscribe_swaps().await;
|
||||
callback.track_subscriptions().await;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -84,15 +81,21 @@ impl<P: ProxyUrlFetcher> SwapperStatusStream for BoltzSwapper<P> {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
swap_res = subscription_stream.recv() => match swap_res {
|
||||
Ok(swap_id) => {
|
||||
if !tracked_swap_ids.contains(&swap_id) {
|
||||
self.send_subscription(swap_id.clone(), &mut sender).await;
|
||||
tracked_swap_ids.insert(swap_id.clone());
|
||||
}
|
||||
ws_request_res = request_stream.recv() => match ws_request_res {
|
||||
Ok(WsRequest::Subscribe(subscribe)) => {
|
||||
let id = match subscribe.clone() {
|
||||
boltz::SubscribeRequest::SwapUpdate { args } => args.first().cloned(),
|
||||
boltz::SubscribeRequest::InvoiceRequest { args } => args.first().map(|p| p.offer.clone()),
|
||||
};
|
||||
if let Some(id) = id {
|
||||
if !tracked_ids.contains(&id) {
|
||||
self.send_request(WsRequest::Subscribe(subscribe), &mut sender).await;
|
||||
tracked_ids.insert(id);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => error!("Received error on subscription stream: {e:?}"),
|
||||
Ok(ws_request) => self.send_request(ws_request, &mut sender).await,
|
||||
Err(e) => error!("Received error on request stream: {e:?}"),
|
||||
},
|
||||
|
||||
maybe_next = receiver.next() => match maybe_next {
|
||||
@@ -104,18 +107,30 @@ impl<P: ProxyUrlFetcher> SwapperStatusStream for BoltzSwapper<P> {
|
||||
},
|
||||
Ok(Message::Text(payload)) => {
|
||||
let payload = payload.as_str();
|
||||
info!("Received text msg: {payload:?}");
|
||||
debug!("Received text msg: {payload:?}");
|
||||
match serde_json::from_str::<WsResponse>(payload) {
|
||||
// Subscribing/unsubscribing confirmation
|
||||
Ok(WsResponse::Subscribe { .. }) | Ok(WsResponse::Unsubscribe { .. }) => {}
|
||||
|
||||
// Status update(s)
|
||||
// Swap status update(s)
|
||||
Ok(WsResponse::Update(update)) => {
|
||||
for update in update.args {
|
||||
let _ = self.update_notifier.send(update);
|
||||
}
|
||||
}
|
||||
|
||||
// Invoice requests(s)
|
||||
Ok(WsResponse::InvoiceRequest(invoice_request)) => {
|
||||
for invoice_request in invoice_request.args {
|
||||
let _ = self.invoice_request_notifier.send(invoice_request);
|
||||
}
|
||||
}
|
||||
|
||||
// Error response
|
||||
Ok(WsResponse::Error(error)) => {
|
||||
error!("Received error msg: {error:?}");
|
||||
}
|
||||
|
||||
// A response to one of our pings
|
||||
Ok(WsResponse::Pong) => debug!("Received pong"),
|
||||
|
||||
@@ -150,11 +165,41 @@ impl<P: ProxyUrlFetcher> SwapperStatusStream for BoltzSwapper<P> {
|
||||
}
|
||||
|
||||
fn track_swap_id(&self, swap_id: &str) -> Result<()> {
|
||||
let _ = self.subscription_notifier.send(swap_id.to_string());
|
||||
let _ =
|
||||
self.request_notifier
|
||||
.send(WsRequest::Subscribe(boltz::SubscribeRequest::SwapUpdate {
|
||||
args: vec![swap_id.to_string()],
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn track_offer(&self, offer: &str, signature: &str) -> Result<()> {
|
||||
let _ = self.request_notifier.send(WsRequest::Subscribe(
|
||||
boltz::SubscribeRequest::InvoiceRequest {
|
||||
args: vec![InvoiceRequestParams {
|
||||
offer: offer.to_string(),
|
||||
signature: signature.to_string(),
|
||||
}],
|
||||
},
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_invoice_created(&self, id: &str, invoice: &str) -> Result<()> {
|
||||
let _ = self
|
||||
.request_notifier
|
||||
.send(WsRequest::Invoice(boltz::InvoiceCreated {
|
||||
id: id.to_string(),
|
||||
invoice: invoice.to_string(),
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_swap_updates(&self) -> broadcast::Receiver<boltz::SwapStatus> {
|
||||
self.update_notifier.subscribe()
|
||||
}
|
||||
|
||||
fn subscribe_invoice_requests(&self) -> broadcast::Receiver<boltz::InvoiceRequest> {
|
||||
self.invoice_request_notifier.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use boltz_client::{
|
||||
boltz::{
|
||||
ChainPair, CreateChainRequest, CreateChainResponse, CreateReverseRequest,
|
||||
CreateReverseResponse, CreateSubmarineRequest, CreateSubmarineResponse, ReversePair,
|
||||
SubmarineClaimTxResponse, SubmarinePair,
|
||||
ChainPair, CreateBolt12OfferRequest, CreateChainRequest, CreateChainResponse,
|
||||
CreateReverseRequest, CreateReverseResponse, CreateSubmarineRequest,
|
||||
CreateSubmarineResponse, GetBolt12ParamsResponse, GetNodesResponse, ReversePair,
|
||||
SubmarineClaimTxResponse, SubmarinePair, UpdateBolt12OfferRequest,
|
||||
},
|
||||
network::Chain,
|
||||
Amount,
|
||||
@@ -129,6 +130,16 @@ pub trait Swapper: MaybeSend + MaybeSync {
|
||||
offer: &str,
|
||||
amount_sat: u64,
|
||||
) -> Result<String, PaymentError>;
|
||||
|
||||
async fn create_bolt12_offer(&self, req: CreateBolt12OfferRequest) -> Result<(), SdkError>;
|
||||
|
||||
async fn update_bolt12_offer(&self, req: UpdateBolt12OfferRequest) -> Result<(), SdkError>;
|
||||
|
||||
async fn delete_bolt12_offer(&self, offer: &str, signature: &str) -> Result<(), SdkError>;
|
||||
|
||||
async fn get_bolt12_params(&self) -> Result<GetBolt12ParamsResponse, SdkError>;
|
||||
|
||||
async fn get_nodes(&self) -> Result<GetNodesResponse, SdkError>;
|
||||
}
|
||||
|
||||
pub trait SwapperStatusStream: MaybeSend + MaybeSync {
|
||||
@@ -137,8 +148,16 @@ pub trait SwapperStatusStream: MaybeSend + MaybeSync {
|
||||
callback: Box<dyn SubscriptionHandler>,
|
||||
shutdown: watch::Receiver<()>,
|
||||
);
|
||||
|
||||
fn track_swap_id(&self, swap_id: &str) -> Result<()>;
|
||||
fn track_offer(&self, offer: &str, signature: &str) -> Result<()>;
|
||||
|
||||
fn send_invoice_created(&self, id: &str, invoice: &str) -> Result<()>;
|
||||
|
||||
fn subscribe_swap_updates(&self) -> broadcast::Receiver<boltz_client::boltz::SwapStatus>;
|
||||
fn subscribe_invoice_requests(
|
||||
&self,
|
||||
) -> broadcast::Receiver<boltz_client::boltz::InvoiceRequest>;
|
||||
}
|
||||
|
||||
#[sdk_macros::async_trait]
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use log::{error, info};
|
||||
use maybe_sync::{MaybeSend, MaybeSync};
|
||||
|
||||
use crate::persist::Persister;
|
||||
|
||||
use sdk_common::bitcoin::hashes::hex::ToHex;
|
||||
use sdk_common::utils::Arc;
|
||||
|
||||
use crate::{persist::Persister, utils};
|
||||
|
||||
use super::SwapperStatusStream;
|
||||
|
||||
#[sdk_macros::async_trait]
|
||||
pub trait SubscriptionHandler: MaybeSend + MaybeSync {
|
||||
async fn subscribe_swaps(&self);
|
||||
async fn track_subscriptions(&self);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -32,7 +32,7 @@ impl SwapperSubscriptionHandler {
|
||||
|
||||
#[sdk_macros::async_trait]
|
||||
impl SubscriptionHandler for SwapperSubscriptionHandler {
|
||||
async fn subscribe_swaps(&self) {
|
||||
async fn track_subscriptions(&self) {
|
||||
match self.persister.list_ongoing_swaps() {
|
||||
Ok(initial_ongoing_swaps) => {
|
||||
info!(
|
||||
@@ -48,5 +48,35 @@ impl SubscriptionHandler for SwapperSubscriptionHandler {
|
||||
}
|
||||
Err(e) => error!("Failed to list initial ongoing swaps: {e:?}"),
|
||||
}
|
||||
|
||||
match self.persister.list_bolt12_offers() {
|
||||
Ok(initial_bolt12_offers) => {
|
||||
info!(
|
||||
"On stream reconnection, got {} initial BOLT12 offers",
|
||||
initial_bolt12_offers.len()
|
||||
);
|
||||
for bolt12_offer in initial_bolt12_offers {
|
||||
let offer = &bolt12_offer.id;
|
||||
let Ok(keypair) = bolt12_offer.get_keypair() else {
|
||||
error!("Failed to get keypair for BOLT12 offer: {offer}");
|
||||
continue;
|
||||
};
|
||||
let Ok(subscribe_hash_sig) = utils::sign_message_hash("SUBSCRIBE", &keypair)
|
||||
else {
|
||||
error!("Failed to sign hash for BOLT12 offer: {offer}");
|
||||
continue;
|
||||
};
|
||||
|
||||
match self
|
||||
.status_stream
|
||||
.track_offer(offer, &subscribe_hash_sig.to_hex())
|
||||
{
|
||||
Ok(_) => info!("Tracking bolt12 offer: {offer}"),
|
||||
Err(e) => error!("Failed to track bolt12 offer: {e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => error!("Failed to list initial bolt12 offers: {e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use self::model::{ListenChangesRequest, Notification, SyncOutgoingChanges};
|
||||
use crate::prelude::Swap;
|
||||
use crate::recover::recoverer::Recoverer;
|
||||
use crate::sync::model::data::{
|
||||
ChainSyncData, PaymentDetailsSyncData, ReceiveSyncData, SendSyncData,
|
||||
Bolt12OfferSyncData, ChainSyncData, PaymentDetailsSyncData, ReceiveSyncData, SendSyncData,
|
||||
};
|
||||
use crate::sync::model::{DecryptionInfo, Record, SetRecordRequest, SetRecordStatus};
|
||||
use crate::utils;
|
||||
@@ -243,6 +243,13 @@ impl SyncService {
|
||||
*last_commit_time,
|
||||
)
|
||||
}
|
||||
SyncData::Bolt12Offer(bolt12_offer_data) => {
|
||||
self.persister.commit_incoming_bolt12_offer(
|
||||
bolt12_offer_data.into(),
|
||||
new_sync_state,
|
||||
*last_commit_time,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,6 +293,14 @@ impl SyncService {
|
||||
.into();
|
||||
SyncData::PaymentDetails(payment_details_data)
|
||||
}
|
||||
RecordType::Bolt12Offer => {
|
||||
let bolt12_offer_data: Bolt12OfferSyncData = self
|
||||
.persister
|
||||
.fetch_bolt12_offer_by_id(data_id)?
|
||||
.ok_or(anyhow!("Could not find Bolt12 Offer {data_id}"))?
|
||||
.into();
|
||||
SyncData::Bolt12Offer(bolt12_offer_data)
|
||||
}
|
||||
};
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use anyhow::bail;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
model::Bolt12Offer,
|
||||
persist::model::PaymentTxDetails,
|
||||
prelude::{ChainSwap, Direction, LnUrlInfo, PaymentState, ReceiveSwap, SendSwap, Swap},
|
||||
};
|
||||
@@ -231,6 +232,7 @@ pub(crate) struct ReceiveSyncData {
|
||||
#[serde(default)]
|
||||
pub(crate) timeout_block_height: u32,
|
||||
pub(crate) created_at: u32,
|
||||
pub(crate) bolt12_offer: Option<String>,
|
||||
pub(crate) payment_hash: Option<String>,
|
||||
pub(crate) description: Option<String>,
|
||||
pub(crate) destination_pubkey: Option<String>,
|
||||
@@ -255,6 +257,7 @@ impl From<ReceiveSwap> for ReceiveSyncData {
|
||||
fn from(value: ReceiveSwap) -> Self {
|
||||
Self {
|
||||
swap_id: value.id,
|
||||
bolt12_offer: value.bolt12_offer,
|
||||
payment_hash: value.payment_hash,
|
||||
invoice: value.invoice,
|
||||
preimage: value.preimage,
|
||||
@@ -282,6 +285,7 @@ impl From<ReceiveSyncData> for ReceiveSwap {
|
||||
pair_fees_json: val.pair_fees_json,
|
||||
claim_private_key: val.claim_private_key,
|
||||
invoice: val.invoice,
|
||||
bolt12_offer: val.bolt12_offer,
|
||||
payment_hash: val.payment_hash,
|
||||
destination_pubkey: val.destination_pubkey,
|
||||
description: val.description,
|
||||
@@ -352,6 +356,66 @@ impl From<PaymentDetailsSyncData> for PaymentTxDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub(crate) struct Bolt12OfferSyncData {
|
||||
pub(crate) id: String,
|
||||
pub(crate) description: String,
|
||||
pub(crate) private_key: String,
|
||||
pub(crate) webhook_url: Option<String>,
|
||||
pub(crate) created_at: u32,
|
||||
}
|
||||
|
||||
impl Bolt12OfferSyncData {
|
||||
pub(crate) fn merge(&mut self, other: &Self, updated_fields: &[String]) {
|
||||
for field in updated_fields {
|
||||
match field.as_str() {
|
||||
"webhook_url" => self.webhook_url.clone_from(&other.webhook_url),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn updated_fields(
|
||||
bolt12_offer: Option<Bolt12Offer>,
|
||||
update: &Bolt12Offer,
|
||||
) -> Option<Vec<String>> {
|
||||
match bolt12_offer {
|
||||
Some(bolt12_offer) => {
|
||||
let mut updated_fields = vec![];
|
||||
if update.webhook_url != bolt12_offer.webhook_url {
|
||||
updated_fields.push("webhook_url".to_string());
|
||||
}
|
||||
Some(updated_fields)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bolt12Offer> for Bolt12OfferSyncData {
|
||||
fn from(value: Bolt12Offer) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
description: value.description,
|
||||
private_key: value.private_key,
|
||||
webhook_url: value.webhook_url,
|
||||
created_at: value.created_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bolt12OfferSyncData> for Bolt12Offer {
|
||||
fn from(val: Bolt12OfferSyncData) -> Self {
|
||||
Bolt12Offer {
|
||||
id: val.id,
|
||||
description: val.description,
|
||||
private_key: val.private_key,
|
||||
webhook_url: val.webhook_url,
|
||||
created_at: val.created_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(tag = "data_type", content = "data")]
|
||||
pub(crate) enum SyncData {
|
||||
@@ -360,6 +424,7 @@ pub(crate) enum SyncData {
|
||||
Receive(ReceiveSyncData),
|
||||
LastDerivationIndex(u32),
|
||||
PaymentDetails(PaymentDetailsSyncData),
|
||||
Bolt12Offer(Bolt12OfferSyncData),
|
||||
}
|
||||
|
||||
impl SyncData {
|
||||
@@ -370,6 +435,7 @@ impl SyncData {
|
||||
SyncData::Receive(receive_data) => &receive_data.swap_id,
|
||||
SyncData::LastDerivationIndex(_) => LAST_DERIVATION_INDEX_DATA_ID,
|
||||
SyncData::PaymentDetails(payment_details) => &payment_details.tx_id,
|
||||
SyncData::Bolt12Offer(bolt12_offer_data) => &bolt12_offer_data.id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +446,9 @@ impl SyncData {
|
||||
/// Whether the data is a swap
|
||||
pub(crate) fn is_swap(&self) -> bool {
|
||||
match self {
|
||||
SyncData::LastDerivationIndex(_) | SyncData::PaymentDetails(_) => false,
|
||||
SyncData::Bolt12Offer(_)
|
||||
| SyncData::LastDerivationIndex(_)
|
||||
| SyncData::PaymentDetails(_) => false,
|
||||
SyncData::Chain(_) | SyncData::Send(_) | SyncData::Receive(_) => true,
|
||||
}
|
||||
}
|
||||
@@ -405,6 +473,9 @@ impl SyncData {
|
||||
(SyncData::PaymentDetails(ref mut base), SyncData::PaymentDetails(other)) => {
|
||||
base.merge(other, updated_fields)
|
||||
}
|
||||
(SyncData::Bolt12Offer(ref mut base), SyncData::Bolt12Offer(other)) => {
|
||||
base.merge(other, updated_fields)
|
||||
}
|
||||
_ => return Err(anyhow::anyhow!("Cannot merge data from two separate types")),
|
||||
};
|
||||
Ok(())
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(crate) mod data;
|
||||
|
||||
const MESSAGE_PREFIX: &[u8; 13] = b"realtimesync:";
|
||||
lazy_static! {
|
||||
static ref CURRENT_SCHEMA_VERSION: Version = Version::parse("0.5.0").unwrap();
|
||||
static ref CURRENT_SCHEMA_VERSION: Version = Version::parse("0.6.0").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
@@ -29,6 +29,7 @@ pub(crate) enum RecordType {
|
||||
Chain = 2,
|
||||
LastDerivationIndex = 3,
|
||||
PaymentDetails = 4,
|
||||
Bolt12Offer = 5,
|
||||
}
|
||||
|
||||
impl ToSql for RecordType {
|
||||
@@ -46,6 +47,7 @@ impl FromSql for RecordType {
|
||||
2 => Ok(Self::Chain),
|
||||
3 => Ok(Self::LastDerivationIndex),
|
||||
4 => Ok(Self::PaymentDetails),
|
||||
5 => Ok(Self::Bolt12Offer),
|
||||
_ => Err(FromSqlError::OutOfRange(i)),
|
||||
},
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
@@ -261,6 +263,7 @@ impl Record {
|
||||
SyncData::Receive(_) => "receive-swap",
|
||||
SyncData::LastDerivationIndex(_) => "derivation-index",
|
||||
SyncData::PaymentDetails(_) => "payment-details",
|
||||
SyncData::Bolt12Offer(_) => "bolt12-offer",
|
||||
}
|
||||
.to_string();
|
||||
Self::id(prefix, data.id())
|
||||
@@ -273,6 +276,7 @@ impl Record {
|
||||
RecordType::Receive => "receive-swap",
|
||||
RecordType::LastDerivationIndex => "derivation-index",
|
||||
RecordType::PaymentDetails => "payment-details",
|
||||
RecordType::Bolt12Offer => "bolt12-offer",
|
||||
}
|
||||
.to_string();
|
||||
Self::id(prefix, data_id)
|
||||
|
||||
11
lib/core/src/test_utils/bolt12_offer.rs
Normal file
11
lib/core/src/test_utils/bolt12_offer.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::{model::Bolt12Offer, test_utils::generate_random_string, utils};
|
||||
|
||||
pub fn new_bolt12_offer(description: Option<String>, webhook_url: Option<String>) -> Bolt12Offer {
|
||||
Bolt12Offer {
|
||||
id: generate_random_string(32),
|
||||
description: description.unwrap_or("default".to_string()),
|
||||
private_key: "945affeef55f12227f1d4a3f80a17062a05b229ddc5a01591eb5ddf882df92e3".to_string(),
|
||||
webhook_url,
|
||||
created_at: utils::now(),
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use bip39::rand::{self, distributions::Alphanumeric, Rng};
|
||||
|
||||
pub(crate) mod bolt12_offer;
|
||||
pub(crate) mod chain;
|
||||
pub(crate) mod chain_swap;
|
||||
pub mod persist;
|
||||
|
||||
@@ -106,6 +106,7 @@ pub fn new_receive_swap(
|
||||
create_response_json: r#"{"swap_tree":{"claim_leaf":{"output":"82012088a91476089e96a323d103b4d9546ab0b64505672197f58820d5272b21c51e7fe6a2e0d6b3ddafde514ff2b31ca70399a3a0960f19f3b1853dac","version":196},"refund_leaf":{"output":"20859b5e5e3b66c76e0920a21a41f4a64246caf2cf0084307c447ba94a2e3a483dad03842231b1","version":196}},"lockup_address":"lq1pq0ka6jmyx62herardll0ccu3zze4qvmh04vnzdw4c5338rp3yquggh47wr29jh6akr6mtw2zzrgn6nuv68setq76d2uk9fqs0l84z7t2jhw58m0crqu4","refund_public_key":"03859b5e5e3b66c76e0920a21a41f4a64246caf2cf0084307c447ba94a2e3a483d","timeout_block_height":3220100,"onchain_amount":971,"blinding_key":"ef121ccd2906a4cc80f8a9b33b18fa2ba4de7e9032b7b143bfc816494d46dc66"}"#.to_string(),
|
||||
claim_private_key: "08e4555d4388552fe6a72a89953b3d333ddbb66b7ae2167f5f66327ec66cede1".to_string(),
|
||||
invoice: "lnbc10u1pnez5ulsp5szkn8zq25p99m3kkhcyv5xfaszvya80gca2efduhp9v0g3qy9spqpp52m3vvah5xj8mzu6knwl4gtcymzg9w7lmm90yctwr39kae36sjpsqdpz2djkuepqw3hjqnpdgf2yxgrpv3j8yetnwvxqyp2xqcqz95rzjqt2jw2epc508le4zurtt8hd0meg5lu4nrjns8xdr5ztq7x0nkxzn6zzxeyqq28qqqqqqqqqqqqqqq9gq2y9qyysgq4kue7c5mrla8cxgzlpddvl62a3quzpkhlza84tkrxea3hmvq4zcnn2rcve7l9cu5xdxglflerp5rcyeyc88j33mht4fea60jj9e7cqspe058nk".to_string(),
|
||||
bolt12_offer: None,
|
||||
payment_hash: Some("56e2c676f4348fb173569bbf542f04d890577bfbd95e4c2dc3896ddcc7509060".to_string()),
|
||||
destination_pubkey: Some("02d96eadea3d780104449aca5c93461ce67c1564e2e1d73225fa67dd3b997a6018".to_string()),
|
||||
payer_amount_sat: 1000,
|
||||
|
||||
@@ -9,13 +9,18 @@ use crate::swapper::{SubscriptionHandler, SwapperStatusStream};
|
||||
|
||||
pub(crate) struct MockStatusStream {
|
||||
pub update_notifier: broadcast::Sender<boltz::SwapStatus>,
|
||||
pub invoice_request_notifier: broadcast::Sender<boltz::InvoiceRequest>,
|
||||
}
|
||||
|
||||
impl MockStatusStream {
|
||||
pub(crate) fn new() -> Self {
|
||||
let (update_notifier, _) = broadcast::channel::<boltz::SwapStatus>(30);
|
||||
let (invoice_request_notifier, _) = broadcast::channel::<boltz::InvoiceRequest>(30);
|
||||
|
||||
Self { update_notifier }
|
||||
Self {
|
||||
update_notifier,
|
||||
invoice_request_notifier,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_mock_update(self: Arc<Self>, update: boltz::SwapStatus) -> Result<()> {
|
||||
@@ -39,7 +44,19 @@ impl SwapperStatusStream for MockStatusStream {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn track_offer(&self, _offer: &str, _signature: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_invoice_created(&self, _id: &str, _invoice: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_swap_updates(&self) -> broadcast::Receiver<boltz::SwapStatus> {
|
||||
self.update_notifier.subscribe()
|
||||
}
|
||||
|
||||
fn subscribe_invoice_requests(&self) -> broadcast::Receiver<boltz::InvoiceRequest> {
|
||||
self.invoice_request_notifier.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
use anyhow::Result;
|
||||
use boltz_client::{
|
||||
boltz::{
|
||||
ChainFees, ChainMinerFees, ChainPair, ChainSwapDetails, CreateChainResponse,
|
||||
CreateReverseResponse, CreateSubmarineResponse, Leaf, PairLimits, PairMinerFees,
|
||||
ReverseFees, ReverseLimits, ReversePair, SubmarineClaimTxResponse, SubmarineFees,
|
||||
SubmarinePair, SubmarinePairLimits, SwapTree,
|
||||
ChainFees, ChainMinerFees, ChainPair, ChainSwapDetails, CreateBolt12OfferRequest,
|
||||
CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse,
|
||||
GetBolt12ParamsResponse, GetNodesResponse, Leaf, MagicRoutingHint, Node, PairLimits,
|
||||
PairMinerFees, ReverseFees, ReverseLimits, ReversePair, SubmarineClaimTxResponse,
|
||||
SubmarineFees, SubmarinePair, SubmarinePairLimits, SwapTree, UpdateBolt12OfferRequest,
|
||||
},
|
||||
util::secrets::Preimage,
|
||||
Amount, PublicKey,
|
||||
};
|
||||
use lwk_wollet::secp256k1;
|
||||
use sdk_common::invoice::parse_invoice;
|
||||
use std::sync::Mutex;
|
||||
use std::{collections::HashMap, str::FromStr, sync::Mutex};
|
||||
|
||||
use crate::{
|
||||
ensure_sdk,
|
||||
@@ -126,7 +128,7 @@ impl Swapper for MockSwapper {
|
||||
req: boltz_client::swaps::boltz::CreateSubmarineRequest,
|
||||
) -> Result<CreateSubmarineResponse, PaymentError> {
|
||||
let invoice = parse_invoice(&req.invoice)
|
||||
.map_err(|err| PaymentError::invalid_invoice(&err.to_string()))?;
|
||||
.map_err(|err| PaymentError::invalid_invoice(err.to_string()))?;
|
||||
let Some(amount_msat) = invoice.amount_msat else {
|
||||
return Err(PaymentError::invalid_invoice(
|
||||
"Invoice does not contain an amount",
|
||||
@@ -294,11 +296,11 @@ impl Swapper for MockSwapper {
|
||||
|
||||
async fn create_receive_swap(
|
||||
&self,
|
||||
_req: boltz_client::swaps::boltz::CreateReverseRequest,
|
||||
req: boltz_client::swaps::boltz::CreateReverseRequest,
|
||||
) -> Result<CreateReverseResponse, PaymentError> {
|
||||
Ok(CreateReverseResponse {
|
||||
id: generate_random_string(4),
|
||||
invoice: "".to_string(),
|
||||
invoice: req.invoice.map_or(Some("".to_string()), |_| None),
|
||||
swap_tree: Self::mock_swap_tree(),
|
||||
lockup_address: "".to_string(),
|
||||
refund_public_key: Self::mock_public_key(),
|
||||
@@ -351,6 +353,44 @@ impl Swapper for MockSwapper {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn create_bolt12_offer(&self, _req: CreateBolt12OfferRequest) -> Result<(), SdkError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_bolt12_offer(&self, _req: UpdateBolt12OfferRequest) -> Result<(), SdkError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_bolt12_offer(&self, _offer: &str, _signature: &str) -> Result<(), SdkError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_bolt12_params(&self) -> Result<GetBolt12ParamsResponse, SdkError> {
|
||||
Ok(GetBolt12ParamsResponse {
|
||||
min_cltv: 180,
|
||||
magic_routing_hint: MagicRoutingHint {
|
||||
channel_id: "596385002596073472".to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_nodes(&self) -> Result<GetNodesResponse, SdkError> {
|
||||
Ok(GetNodesResponse {
|
||||
btc: HashMap::from([(
|
||||
"CLN".to_string(),
|
||||
Node {
|
||||
public_key: secp256k1::PublicKey::from_str(
|
||||
"02d96eadea3d780104449aca5c93461ce67c1564e2e1d73225fa67dd3b997a6018",
|
||||
)
|
||||
.unwrap(),
|
||||
uris: vec![
|
||||
"02d96eadea3d780104449aca5c93461ce67c1564e2e1d73225fa67dd3b997a6018@143.202.162.204:9736".to_string(),
|
||||
"02d96eadea3d780104449aca5c93461ce67c1564e2e1d73225fa67dd3b997a6018@2803:6900:581::1:c175:f0ad:9736".to_string()],
|
||||
},
|
||||
)]),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_zero_amount_chain_swap_quote(&self, _swap_id: &str) -> Result<Amount, SdkError> {
|
||||
let server_lockup_amount_sat = self.get_zero_amount_swap_server_lockup_sat().await;
|
||||
Ok(Amount::from_sat(server_lockup_amount_sat))
|
||||
|
||||
@@ -125,6 +125,7 @@ pub(crate) fn new_receive_sync_data() -> ReceiveSyncData {
|
||||
claim_private_key: "".to_string(),
|
||||
mrh_address: "".to_string(),
|
||||
preimage: "".to_string(),
|
||||
bolt12_offer: None,
|
||||
payment_hash: None,
|
||||
description: None,
|
||||
destination_pubkey: None,
|
||||
|
||||
41
lib/core/src/utils/bolt12.rs
Normal file
41
lib/core/src/utils/bolt12.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use sdk_common::bitcoin::bech32::ToBase32;
|
||||
use sdk_common::bitcoin::bech32::{self, FromBase32};
|
||||
use sdk_common::lightning_with_bolt12::offers::invoice::Bolt12Invoice;
|
||||
use sdk_common::lightning_with_bolt12::offers::invoice_request::InvoiceRequest;
|
||||
use sdk_common::lightning_with_bolt12::offers::offer::Offer;
|
||||
use sdk_common::lightning_with_bolt12::util::ser::Writeable;
|
||||
|
||||
pub fn encode_invoice(invoice: &Bolt12Invoice) -> Result<String> {
|
||||
let mut writer = Vec::new();
|
||||
invoice.write(&mut writer)?;
|
||||
|
||||
Ok(bech32::encode_without_checksum("lni", writer.to_base32())?)
|
||||
}
|
||||
|
||||
pub fn encode_offer(offer: &Offer) -> Result<String> {
|
||||
let mut writer = Vec::new();
|
||||
offer.write(&mut writer)?;
|
||||
|
||||
Ok(bech32::encode_without_checksum("lno", writer.to_base32())?)
|
||||
}
|
||||
|
||||
/// Parsing logic that decodes a string into a [Bolt12Invoice].
|
||||
///
|
||||
/// It matches the encoding logic on Boltz side.
|
||||
pub(crate) fn decode_invoice(invoice: &str) -> Result<Bolt12Invoice> {
|
||||
let (hrp, data) = bech32::decode_without_checksum(invoice)?;
|
||||
ensure!(hrp.as_str() == "lni", "Invalid HRP");
|
||||
|
||||
let data = Vec::<u8>::from_base32(&data)?;
|
||||
|
||||
sdk_common::lightning_with_bolt12::offers::invoice::Bolt12Invoice::try_from(data)
|
||||
.map_err(|e| anyhow!("Failed to parse BOLT12: {e:?}"))
|
||||
}
|
||||
|
||||
pub(crate) fn decode_invoice_request(invoice_request: &str) -> Result<InvoiceRequest> {
|
||||
InvoiceRequest::try_from(
|
||||
hex::decode(invoice_request).map_err(|e| anyhow!("Cannot decode invoice request: {e}"))?,
|
||||
)
|
||||
.map_err(|e| anyhow!("Cannot parse invoice request: {e:?}"))
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
pub(crate) mod bolt12;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::ensure_sdk;
|
||||
use crate::error::{PaymentError, SdkResult};
|
||||
use crate::prelude::LiquidNetwork;
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use anyhow::{anyhow, Result};
|
||||
use bip39::rand::{self, RngCore};
|
||||
use boltz_client::boltz::SubmarinePair;
|
||||
use boltz_client::util::secrets::Preimage;
|
||||
use boltz_client::ToHex;
|
||||
use boltz_client::{Keypair, ToHex};
|
||||
use lazy_static::lazy_static;
|
||||
use lwk_wollet::bitcoin::secp256k1::Message;
|
||||
use lwk_wollet::elements::encode::deserialize;
|
||||
use lwk_wollet::elements::hex::FromHex;
|
||||
use lwk_wollet::elements::AssetId;
|
||||
@@ -16,10 +20,9 @@ use lwk_wollet::elements::{
|
||||
LockTime::{self, *},
|
||||
Transaction,
|
||||
};
|
||||
use sdk_common::bitcoin::bech32;
|
||||
use sdk_common::bitcoin::bech32::FromBase32;
|
||||
use sdk_common::lightning_invoice::Bolt11Invoice;
|
||||
use sdk_common::lightning_with_bolt12::offers::invoice::Bolt12Invoice;
|
||||
use lwk_wollet::hashes::{sha256, Hash};
|
||||
use lwk_wollet::secp256k1::schnorr::Signature;
|
||||
use sdk_common::lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription};
|
||||
use web_time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
lazy_static! {
|
||||
@@ -51,6 +54,13 @@ pub(crate) fn generate_keypair() -> boltz_client::Keypair {
|
||||
boltz_client::Keypair::from_secret_key(&secp, &secret_key)
|
||||
}
|
||||
|
||||
pub(crate) fn generate_entropy() -> [u8; 32] {
|
||||
let mut entropy_bytes = [0u8; 32];
|
||||
let mut rng = rand::thread_rng();
|
||||
rng.fill_bytes(&mut entropy_bytes);
|
||||
entropy_bytes
|
||||
}
|
||||
|
||||
pub(crate) fn decode_keypair(secret_key: &str) -> SdkResult<boltz_client::Keypair> {
|
||||
let secp = boltz_client::Secp256k1::new();
|
||||
let secret_key = lwk_wollet::secp256k1::SecretKey::from_str(secret_key)?;
|
||||
@@ -71,24 +81,16 @@ pub(crate) fn deserialize_tx_hex(tx_hex: &str) -> Result<Transaction> {
|
||||
)?)?)
|
||||
}
|
||||
|
||||
/// Parsing logic that decodes a string into a [Bolt12Invoice].
|
||||
///
|
||||
/// It matches the encoding logic on Boltz side.
|
||||
pub(crate) fn parse_bolt12_invoice(invoice: &str) -> Result<Bolt12Invoice> {
|
||||
let (hrp, data) = bech32::decode_without_checksum(invoice)?;
|
||||
ensure!(hrp.as_str() == "lni", "Invalid HRP");
|
||||
|
||||
let data = Vec::<u8>::from_base32(&data)?;
|
||||
|
||||
sdk_common::lightning_with_bolt12::offers::invoice::Bolt12Invoice::try_from(data)
|
||||
.map_err(|e| anyhow!("Failed to parse BOLT12: {e:?}"))
|
||||
pub(crate) fn sign_message_hash<S: AsRef<str>>(msg: S, keypair: &Keypair) -> Result<Signature> {
|
||||
let msg_hash = sha256::Hash::hash(msg.as_ref().as_bytes());
|
||||
Ok(keypair.sign_schnorr(Message::from_digest_slice(msg_hash.as_byte_array())?))
|
||||
}
|
||||
|
||||
/// Parse and extract the destination pubkey from the invoice.
|
||||
/// The payee pubkey for Bolt11 and signing pubkey for Bolt12.
|
||||
pub(crate) fn get_invoice_destination_pubkey(invoice: &str, is_bolt12: bool) -> Result<String> {
|
||||
if is_bolt12 {
|
||||
parse_bolt12_invoice(invoice).map(|i| i.signing_pubkey().to_hex())
|
||||
bolt12::decode_invoice(invoice).map(|i| i.signing_pubkey().to_hex())
|
||||
} else {
|
||||
invoice
|
||||
.trim()
|
||||
@@ -98,6 +100,23 @@ pub(crate) fn get_invoice_destination_pubkey(invoice: &str, is_bolt12: bool) ->
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse and extract the description from the BOLT11/12 invoice
|
||||
pub(crate) fn get_invoice_description(invoice: &str) -> Result<Option<String>, PaymentError> {
|
||||
let invoice = invoice.trim();
|
||||
match Bolt11Invoice::from_str(invoice) {
|
||||
Ok(invoice) => match invoice.description() {
|
||||
Bolt11InvoiceDescription::Direct(d) => Ok(Some(d.to_string())),
|
||||
Bolt11InvoiceDescription::Hash(_) => Ok(None),
|
||||
},
|
||||
Err(_) => match bolt12::decode_invoice(invoice) {
|
||||
Ok(invoice) => Ok(invoice.description().map(|d| d.to_string())),
|
||||
Err(e) => Err(PaymentError::InvalidInvoice {
|
||||
err: format!("Could not parse invoice: {e:?}"),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies a BOLT11/12 invoice against a preimage
|
||||
pub(crate) fn verify_payment_hash(
|
||||
preimage: &str,
|
||||
@@ -108,7 +127,7 @@ pub(crate) fn verify_payment_hash(
|
||||
|
||||
let invoice_payment_hash = match Bolt11Invoice::from_str(invoice) {
|
||||
Ok(invoice) => Ok(invoice.payment_hash().to_string()),
|
||||
Err(_) => match parse_bolt12_invoice(invoice) {
|
||||
Err(_) => match bolt12::decode_invoice(invoice) {
|
||||
Ok(invoice) => Ok(invoice.payment_hash().to_string()),
|
||||
Err(e) => Err(PaymentError::InvalidInvoice {
|
||||
err: format!("Could not parse invoice: {e:?}"),
|
||||
160
lib/core/tests/regtest/bolt12.rs
Normal file
160
lib/core/tests/regtest/bolt12.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use breez_sdk_liquid::model::{
|
||||
PayAmount, PaymentDetails, PaymentMethod, PaymentState, PaymentType, PrepareReceiveRequest,
|
||||
PrepareSendRequest, SdkEvent,
|
||||
};
|
||||
use serial_test::serial;
|
||||
use tokio_with_wasm::alias as tokio;
|
||||
|
||||
use crate::regtest::{
|
||||
utils::{self, mine_blocks},
|
||||
ChainBackend, SdkNodeHandle, TIMEOUT,
|
||||
};
|
||||
|
||||
#[cfg(feature = "browser-tests")]
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[sdk_macros::async_test_not_wasm]
|
||||
#[serial]
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
async fn bolt12_electrum() {
|
||||
let handle_alice = SdkNodeHandle::init_node(ChainBackend::Electrum)
|
||||
.await
|
||||
.unwrap();
|
||||
let handle_bob = SdkNodeHandle::init_node(ChainBackend::Electrum)
|
||||
.await
|
||||
.unwrap();
|
||||
bolt12(handle_alice, handle_bob).await;
|
||||
}
|
||||
|
||||
#[sdk_macros::async_test_all]
|
||||
#[serial]
|
||||
async fn bolt12_esplora() {
|
||||
let handle_alice = SdkNodeHandle::init_node(ChainBackend::Esplora)
|
||||
.await
|
||||
.unwrap();
|
||||
let handle_bob = SdkNodeHandle::init_node(ChainBackend::Esplora)
|
||||
.await
|
||||
.unwrap();
|
||||
bolt12(handle_alice, handle_bob).await;
|
||||
}
|
||||
|
||||
async fn bolt12(mut handle_alice: SdkNodeHandle, mut handle_bob: SdkNodeHandle) {
|
||||
handle_alice
|
||||
.wait_for_event(|e| matches!(e, SdkEvent::Synced { .. }), TIMEOUT)
|
||||
.await
|
||||
.unwrap();
|
||||
handle_bob
|
||||
.wait_for_event(|e| matches!(e, SdkEvent::Synced { .. }), TIMEOUT)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// -------------------SETUP-------------------
|
||||
// Setup Alice with some funds
|
||||
let (_, receive_response) = handle_alice
|
||||
.receive_payment(&PrepareReceiveRequest {
|
||||
payment_method: PaymentMethod::LiquidAddress,
|
||||
amount: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let address = receive_response.destination;
|
||||
let amount_sat = 200_000;
|
||||
|
||||
utils::send_to_address_elementsd(&address, amount_sat)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
handle_alice
|
||||
.wait_for_event(
|
||||
|e| matches!(e, SdkEvent::PaymentWaitingConfirmation { .. }),
|
||||
TIMEOUT,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
utils::mine_blocks(1).await.unwrap();
|
||||
|
||||
handle_alice
|
||||
.wait_for_event(|e| matches!(e, SdkEvent::PaymentSucceeded { .. }), TIMEOUT)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// -------------------CREATE BOLT12 OFFER-------------------
|
||||
let (_, receive_response) = handle_bob
|
||||
.receive_payment(&PrepareReceiveRequest {
|
||||
payment_method: PaymentMethod::Bolt12Offer,
|
||||
amount: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let offer = receive_response.destination;
|
||||
|
||||
// -------------------SEND SWAP-------------------
|
||||
// TODO: Pay an offer using the CLN node
|
||||
|
||||
// -------------------MRH-------------------
|
||||
let receiver_amount_sat = 50_000;
|
||||
|
||||
let (_, _) = handle_alice
|
||||
.send_payment(&PrepareSendRequest {
|
||||
destination: offer,
|
||||
amount: Some(PayAmount::Bitcoin {
|
||||
receiver_amount_sat,
|
||||
}),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
mine_blocks(1).await.unwrap();
|
||||
|
||||
handle_bob
|
||||
.wait_for_event(|e| matches!(e, SdkEvent::PaymentSucceeded { .. }), TIMEOUT)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// TODO: this shouldn't be needed, but without it, sometimes get_balance_sat isn't updated in time
|
||||
// https://github.com/breez/breez-sdk-liquid/issues/828
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
handle_alice.sdk.sync(false).await.unwrap();
|
||||
|
||||
assert_eq!(handle_bob.get_pending_receive_sat().await.unwrap(), 0);
|
||||
assert_eq!(handle_bob.get_pending_send_sat().await.unwrap(), 0);
|
||||
assert_eq!(
|
||||
handle_bob.get_balance_sat().await.unwrap(),
|
||||
receiver_amount_sat
|
||||
);
|
||||
|
||||
let alice_payments = handle_alice.get_payments().await.unwrap();
|
||||
assert_eq!(alice_payments.len(), 2);
|
||||
let alice_payment = &alice_payments[0];
|
||||
assert_eq!(alice_payment.amount_sat, receiver_amount_sat);
|
||||
// The prepare response gives the fees for a swap, so instead we test the Liquid fee
|
||||
assert_eq!(alice_payment.fees_sat, 26);
|
||||
assert_eq!(alice_payment.payment_type, PaymentType::Send);
|
||||
assert_eq!(alice_payment.status, PaymentState::Complete);
|
||||
assert!(matches!(
|
||||
alice_payment.details,
|
||||
PaymentDetails::Liquid { .. }
|
||||
));
|
||||
|
||||
let bob_payments = handle_bob.get_payments().await.unwrap();
|
||||
assert_eq!(bob_payments.len(), 1);
|
||||
let bob_payment = &bob_payments[0];
|
||||
assert_eq!(bob_payment.amount_sat, receiver_amount_sat);
|
||||
assert_eq!(bob_payment.fees_sat, 0);
|
||||
assert_eq!(bob_payment.payment_type, PaymentType::Receive);
|
||||
assert_eq!(bob_payment.status, PaymentState::Complete);
|
||||
// TODO: figure out why occasionally this fails (details = Liquid)
|
||||
// https://github.com/breez/breez-sdk-liquid/issues/829
|
||||
/*assert!(matches!(
|
||||
bob_payment.details,
|
||||
PaymentDetails::Lightning { .. }
|
||||
));*/
|
||||
|
||||
// On node.js, without disconnecting the sdk, the wasm-pack test process fails after the test succeeds
|
||||
handle_alice.sdk.disconnect().await.unwrap();
|
||||
handle_bob.sdk.disconnect().await.unwrap();
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
mod bitcoin;
|
||||
mod bolt11;
|
||||
mod bolt12;
|
||||
mod liquid;
|
||||
mod utils;
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@ init:
|
||||
clippy: clippy-default clippy-browser clippy-node
|
||||
|
||||
clippy-default:
|
||||
$(CLANG_PREFIX) cargo clippy --all-targets --target=wasm32-unknown-unknown -- -D warnings
|
||||
$(CLANG_PREFIX) cargo clippy --all-targets --target=wasm32-unknown-unknown -- -A deprecated -D warnings
|
||||
|
||||
clippy-browser:
|
||||
$(CLANG_PREFIX) cargo clippy --all-targets --target=wasm32-unknown-unknown --features browser -- -D warnings
|
||||
$(CLANG_PREFIX) cargo clippy --all-targets --target=wasm32-unknown-unknown --features browser -- -A deprecated -D warnings
|
||||
|
||||
clippy-node:
|
||||
$(CLANG_PREFIX) cargo clippy --all-targets --target=wasm32-unknown-unknown --features node-js -- -D warnings
|
||||
$(CLANG_PREFIX) cargo clippy --all-targets --target=wasm32-unknown-unknown --features node-js -- -A deprecated -D warnings
|
||||
|
||||
build: build-bundle build-deno build-node build-web
|
||||
|
||||
|
||||
@@ -202,6 +202,14 @@ impl BindingLiquidSdk {
|
||||
Ok(self.sdk.receive_payment(&req.into()).await?.into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "createBolt12Invoice")]
|
||||
pub async fn create_bolt12_invoice(
|
||||
&self,
|
||||
req: CreateBolt12InvoiceRequest,
|
||||
) -> WasmResult<CreateBolt12InvoiceResponse> {
|
||||
Ok(self.sdk.create_bolt12_invoice(&req.into()).await?.into())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "fetchLightningLimits")]
|
||||
pub async fn fetch_lightning_limits(&self) -> WasmResult<LightningPaymentLimitsResponse> {
|
||||
Ok(self.sdk.fetch_lightning_limits().await?.into())
|
||||
|
||||
@@ -356,6 +356,8 @@ pub struct ConnectWithSignerRequest {
|
||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::PaymentMethod)]
|
||||
pub enum PaymentMethod {
|
||||
Lightning,
|
||||
Bolt11Invoice,
|
||||
Bolt12Offer,
|
||||
BitcoinAddress,
|
||||
LiquidAddress,
|
||||
}
|
||||
@@ -380,8 +382,8 @@ pub struct PrepareReceiveRequest {
|
||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::PrepareReceiveResponse)]
|
||||
pub struct PrepareReceiveResponse {
|
||||
pub payment_method: PaymentMethod,
|
||||
pub amount: Option<ReceiveAmount>,
|
||||
pub fees_sat: u64,
|
||||
pub amount: Option<ReceiveAmount>,
|
||||
pub min_payer_amount_sat: Option<u64>,
|
||||
pub max_payer_amount_sat: Option<u64>,
|
||||
pub swapper_feerate: Option<f64>,
|
||||
@@ -399,6 +401,17 @@ pub struct ReceivePaymentResponse {
|
||||
pub destination: String,
|
||||
}
|
||||
|
||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::CreateBolt12InvoiceRequest)]
|
||||
pub struct CreateBolt12InvoiceRequest {
|
||||
pub offer: String,
|
||||
pub invoice_request: String,
|
||||
}
|
||||
|
||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::CreateBolt12InvoiceResponse)]
|
||||
pub struct CreateBolt12InvoiceResponse {
|
||||
pub invoice: String,
|
||||
}
|
||||
|
||||
#[sdk_macros::extern_wasm_bindgen(breez_sdk_liquid::prelude::Limits)]
|
||||
pub struct Limits {
|
||||
pub min_sat: u64,
|
||||
|
||||
@@ -39,6 +39,8 @@ abstract class BindingLiquidSdk implements RustOpaqueInterface {
|
||||
|
||||
CheckMessageResponse checkMessage({required CheckMessageRequest req});
|
||||
|
||||
Future<CreateBolt12InvoiceResponse> createBolt12Invoice({required CreateBolt12InvoiceRequest req});
|
||||
|
||||
Future<void> disconnect();
|
||||
|
||||
void emptyWalletCache();
|
||||
|
||||
@@ -53,7 +53,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||
String get codegenVersion => '2.9.0';
|
||||
|
||||
@override
|
||||
int get rustContentHash => 1264782025;
|
||||
int get rustContentHash => 464449310;
|
||||
|
||||
static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig(
|
||||
stem: 'breez_sdk_liquid',
|
||||
@@ -82,6 +82,11 @@ abstract class RustLibApi extends BaseApi {
|
||||
required CheckMessageRequest req,
|
||||
});
|
||||
|
||||
Future<CreateBolt12InvoiceResponse> crateBindingsBindingLiquidSdkCreateBolt12Invoice({
|
||||
required BindingLiquidSdk that,
|
||||
required CreateBolt12InvoiceRequest req,
|
||||
});
|
||||
|
||||
Future<void> crateBindingsBindingLiquidSdkDisconnect({required BindingLiquidSdk that});
|
||||
|
||||
void crateBindingsBindingLiquidSdkEmptyWalletCache({required BindingLiquidSdk that});
|
||||
@@ -370,6 +375,35 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
TaskConstMeta get kCrateBindingsBindingLiquidSdkCheckMessageConstMeta =>
|
||||
const TaskConstMeta(debugName: "BindingLiquidSdk_check_message", argNames: ["that", "req"]);
|
||||
|
||||
@override
|
||||
Future<CreateBolt12InvoiceResponse> crateBindingsBindingLiquidSdkCreateBolt12Invoice({
|
||||
required BindingLiquidSdk that,
|
||||
required CreateBolt12InvoiceRequest 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_create_bolt_12_invoice_request(req);
|
||||
return wire.wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice(port_, arg0, arg1);
|
||||
},
|
||||
codec: DcoCodec(
|
||||
decodeSuccessData: dco_decode_create_bolt_12_invoice_response,
|
||||
decodeErrorData: dco_decode_payment_error,
|
||||
),
|
||||
constMeta: kCrateBindingsBindingLiquidSdkCreateBolt12InvoiceConstMeta,
|
||||
argValues: [that, req],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateBindingsBindingLiquidSdkCreateBolt12InvoiceConstMeta =>
|
||||
const TaskConstMeta(debugName: "BindingLiquidSdk_create_bolt12_invoice", argNames: ["that", "req"]);
|
||||
|
||||
@override
|
||||
Future<void> crateBindingsBindingLiquidSdkDisconnect({required BindingLiquidSdk that}) {
|
||||
return handler.executeNormal(
|
||||
@@ -1637,6 +1671,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return dco_decode_connect_request(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceRequest dco_decode_box_autoadd_create_bolt_12_invoice_request(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return dco_decode_create_bolt_12_invoice_request(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
double dco_decode_box_autoadd_f_64(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
@@ -1962,6 +2002,25 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceRequest dco_decode_create_bolt_12_invoice_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 CreateBolt12InvoiceRequest(
|
||||
offer: dco_decode_String(arr[0]),
|
||||
invoiceRequest: dco_decode_String(arr[1]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceResponse dco_decode_create_bolt_12_invoice_response(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 CreateBolt12InvoiceResponse(invoice: dco_decode_String(arr[0]));
|
||||
}
|
||||
|
||||
@protected
|
||||
CurrencyInfo dco_decode_currency_info(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
@@ -2975,8 +3034,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
if (arr.length != 6) throw Exception('unexpected arr length: expect 6 but see ${arr.length}');
|
||||
return PrepareReceiveResponse(
|
||||
paymentMethod: dco_decode_payment_method(arr[0]),
|
||||
amount: dco_decode_opt_box_autoadd_receive_amount(arr[1]),
|
||||
feesSat: dco_decode_u_64(arr[2]),
|
||||
feesSat: dco_decode_u_64(arr[1]),
|
||||
amount: dco_decode_opt_box_autoadd_receive_amount(arr[2]),
|
||||
minPayerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[3]),
|
||||
maxPayerAmountSat: dco_decode_opt_box_autoadd_u_64(arr[4]),
|
||||
swapperFeerate: dco_decode_opt_box_autoadd_f_64(arr[5]),
|
||||
@@ -3682,6 +3741,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return (sse_decode_connect_request(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceRequest sse_decode_box_autoadd_create_bolt_12_invoice_request(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return (sse_decode_create_bolt_12_invoice_request(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
double sse_decode_box_autoadd_f_64(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -4017,6 +4084,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceRequest sse_decode_create_bolt_12_invoice_request(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_offer = sse_decode_String(deserializer);
|
||||
var var_invoiceRequest = sse_decode_String(deserializer);
|
||||
return CreateBolt12InvoiceRequest(offer: var_offer, invoiceRequest: var_invoiceRequest);
|
||||
}
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceResponse sse_decode_create_bolt_12_invoice_response(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_invoice = sse_decode_String(deserializer);
|
||||
return CreateBolt12InvoiceResponse(invoice: var_invoice);
|
||||
}
|
||||
|
||||
@protected
|
||||
CurrencyInfo sse_decode_currency_info(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -5356,15 +5438,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
PrepareReceiveResponse sse_decode_prepare_receive_response(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_paymentMethod = sse_decode_payment_method(deserializer);
|
||||
var var_amount = sse_decode_opt_box_autoadd_receive_amount(deserializer);
|
||||
var var_feesSat = sse_decode_u_64(deserializer);
|
||||
var var_amount = sse_decode_opt_box_autoadd_receive_amount(deserializer);
|
||||
var var_minPayerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer);
|
||||
var var_maxPayerAmountSat = sse_decode_opt_box_autoadd_u_64(deserializer);
|
||||
var var_swapperFeerate = sse_decode_opt_box_autoadd_f_64(deserializer);
|
||||
return PrepareReceiveResponse(
|
||||
paymentMethod: var_paymentMethod,
|
||||
amount: var_amount,
|
||||
feesSat: var_feesSat,
|
||||
amount: var_amount,
|
||||
minPayerAmountSat: var_minPayerAmountSat,
|
||||
maxPayerAmountSat: var_maxPayerAmountSat,
|
||||
swapperFeerate: var_swapperFeerate,
|
||||
@@ -6171,6 +6253,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_connect_request(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_create_bolt_12_invoice_request(
|
||||
CreateBolt12InvoiceRequest self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_create_bolt_12_invoice_request(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_f_64(double self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -6502,6 +6593,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_opt_list_prim_u_8_strict(self.seed, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_create_bolt_12_invoice_request(CreateBolt12InvoiceRequest self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_String(self.offer, serializer);
|
||||
sse_encode_String(self.invoiceRequest, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_create_bolt_12_invoice_response(
|
||||
CreateBolt12InvoiceResponse self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_String(self.invoice, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_currency_info(CurrencyInfo self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -7589,8 +7696,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
void sse_encode_prepare_receive_response(PrepareReceiveResponse self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_payment_method(self.paymentMethod, serializer);
|
||||
sse_encode_opt_box_autoadd_receive_amount(self.amount, serializer);
|
||||
sse_encode_u_64(self.feesSat, serializer);
|
||||
sse_encode_opt_box_autoadd_receive_amount(self.amount, serializer);
|
||||
sse_encode_opt_box_autoadd_u_64(self.minPayerAmountSat, serializer);
|
||||
sse_encode_opt_box_autoadd_u_64(self.maxPayerAmountSat, serializer);
|
||||
sse_encode_opt_box_autoadd_f_64(self.swapperFeerate, serializer);
|
||||
@@ -7948,6 +8055,9 @@ class BindingLiquidSdkImpl extends RustOpaque implements BindingLiquidSdk {
|
||||
CheckMessageResponse checkMessage({required CheckMessageRequest req}) =>
|
||||
RustLib.instance.api.crateBindingsBindingLiquidSdkCheckMessage(that: this, req: req);
|
||||
|
||||
Future<CreateBolt12InvoiceResponse> createBolt12Invoice({required CreateBolt12InvoiceRequest req}) =>
|
||||
RustLib.instance.api.crateBindingsBindingLiquidSdkCreateBolt12Invoice(that: this, req: req);
|
||||
|
||||
Future<void> disconnect() => RustLib.instance.api.crateBindingsBindingLiquidSdkDisconnect(that: this);
|
||||
|
||||
void emptyWalletCache() => RustLib.instance.api.crateBindingsBindingLiquidSdkEmptyWalletCache(that: this);
|
||||
|
||||
@@ -134,6 +134,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ConnectRequest dco_decode_box_autoadd_connect_request(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceRequest dco_decode_box_autoadd_create_bolt_12_invoice_request(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_box_autoadd_f_64(dynamic raw);
|
||||
|
||||
@@ -278,6 +281,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ConnectRequest dco_decode_connect_request(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceRequest dco_decode_create_bolt_12_invoice_request(dynamic raw);
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceResponse dco_decode_create_bolt_12_invoice_response(dynamic raw);
|
||||
|
||||
@protected
|
||||
CurrencyInfo dco_decode_currency_info(dynamic raw);
|
||||
|
||||
@@ -780,6 +789,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ConnectRequest sse_decode_box_autoadd_connect_request(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceRequest sse_decode_box_autoadd_create_bolt_12_invoice_request(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
double sse_decode_box_autoadd_f_64(SseDeserializer deserializer);
|
||||
|
||||
@@ -926,6 +940,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
ConnectRequest sse_decode_connect_request(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceRequest sse_decode_create_bolt_12_invoice_request(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
CreateBolt12InvoiceResponse sse_decode_create_bolt_12_invoice_response(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
CurrencyInfo sse_decode_currency_info(SseDeserializer deserializer);
|
||||
|
||||
@@ -1466,6 +1486,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@protected
|
||||
ffi.Pointer<wire_cst_create_bolt_12_invoice_request> cst_encode_box_autoadd_create_bolt_12_invoice_request(
|
||||
CreateBolt12InvoiceRequest raw,
|
||||
) {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
final ptr = wire.cst_new_box_autoadd_create_bolt_12_invoice_request();
|
||||
cst_api_fill_to_wire_create_bolt_12_invoice_request(raw, ptr.ref);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@protected
|
||||
ffi.Pointer<ffi.Double> cst_encode_box_autoadd_f_64(double raw) {
|
||||
// Codec=Cst (C-struct based), see doc to use other codecs
|
||||
@@ -2390,6 +2420,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
cst_api_fill_to_wire_connect_request(apiObj, wireObj.ref);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_box_autoadd_create_bolt_12_invoice_request(
|
||||
CreateBolt12InvoiceRequest apiObj,
|
||||
ffi.Pointer<wire_cst_create_bolt_12_invoice_request> wireObj,
|
||||
) {
|
||||
cst_api_fill_to_wire_create_bolt_12_invoice_request(apiObj, wireObj.ref);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_box_autoadd_fetch_payment_proposed_fees_request(
|
||||
FetchPaymentProposedFeesRequest apiObj,
|
||||
@@ -2737,6 +2775,23 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
wireObj.seed = cst_encode_opt_list_prim_u_8_strict(apiObj.seed);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_create_bolt_12_invoice_request(
|
||||
CreateBolt12InvoiceRequest apiObj,
|
||||
wire_cst_create_bolt_12_invoice_request wireObj,
|
||||
) {
|
||||
wireObj.offer = cst_encode_String(apiObj.offer);
|
||||
wireObj.invoice_request = cst_encode_String(apiObj.invoiceRequest);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_create_bolt_12_invoice_response(
|
||||
CreateBolt12InvoiceResponse apiObj,
|
||||
wire_cst_create_bolt_12_invoice_response wireObj,
|
||||
) {
|
||||
wireObj.invoice = cst_encode_String(apiObj.invoice);
|
||||
}
|
||||
|
||||
@protected
|
||||
void cst_api_fill_to_wire_currency_info(CurrencyInfo apiObj, wire_cst_currency_info wireObj) {
|
||||
wireObj.name = cst_encode_String(apiObj.name);
|
||||
@@ -3625,8 +3680,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
wire_cst_prepare_receive_response wireObj,
|
||||
) {
|
||||
wireObj.payment_method = cst_encode_payment_method(apiObj.paymentMethod);
|
||||
wireObj.amount = cst_encode_opt_box_autoadd_receive_amount(apiObj.amount);
|
||||
wireObj.fees_sat = cst_encode_u_64(apiObj.feesSat);
|
||||
wireObj.amount = cst_encode_opt_box_autoadd_receive_amount(apiObj.amount);
|
||||
wireObj.min_payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.minPayerAmountSat);
|
||||
wireObj.max_payer_amount_sat = cst_encode_opt_box_autoadd_u_64(apiObj.maxPayerAmountSat);
|
||||
wireObj.swapper_feerate = cst_encode_opt_box_autoadd_f_64(apiObj.swapperFeerate);
|
||||
@@ -4167,6 +4222,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_box_autoadd_connect_request(ConnectRequest self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_create_bolt_12_invoice_request(
|
||||
CreateBolt12InvoiceRequest self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_f_64(double self, SseSerializer serializer);
|
||||
|
||||
@@ -4332,6 +4393,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_connect_request(ConnectRequest self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_create_bolt_12_invoice_request(CreateBolt12InvoiceRequest self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_create_bolt_12_invoice_response(CreateBolt12InvoiceResponse self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_currency_info(CurrencyInfo self, SseSerializer serializer);
|
||||
|
||||
@@ -4840,6 +4907,23 @@ class RustLibWire implements BaseWire {
|
||||
_wire__crate__bindings__BindingLiquidSdk_check_messagePtr
|
||||
.asFunction<WireSyncRust2DartDco Function(int, ffi.Pointer<wire_cst_check_message_request>)>();
|
||||
|
||||
void wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice(
|
||||
int port_,
|
||||
int that,
|
||||
ffi.Pointer<wire_cst_create_bolt_12_invoice_request> req,
|
||||
) {
|
||||
return _wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice(port_, that, req);
|
||||
}
|
||||
|
||||
late final _wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoicePtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(ffi.Int64, ffi.UintPtr, ffi.Pointer<wire_cst_create_bolt_12_invoice_request>)
|
||||
>
|
||||
>('frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice');
|
||||
late final _wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice =
|
||||
_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoicePtr
|
||||
.asFunction<void Function(int, int, ffi.Pointer<wire_cst_create_bolt_12_invoice_request>)>();
|
||||
|
||||
void wire__crate__bindings__BindingLiquidSdk_disconnect(int port_, int that) {
|
||||
return _wire__crate__bindings__BindingLiquidSdk_disconnect(port_, that);
|
||||
}
|
||||
@@ -5552,6 +5636,18 @@ class RustLibWire implements BaseWire {
|
||||
late final _cst_new_box_autoadd_connect_request =
|
||||
_cst_new_box_autoadd_connect_requestPtr.asFunction<ffi.Pointer<wire_cst_connect_request> Function()>();
|
||||
|
||||
ffi.Pointer<wire_cst_create_bolt_12_invoice_request> cst_new_box_autoadd_create_bolt_12_invoice_request() {
|
||||
return _cst_new_box_autoadd_create_bolt_12_invoice_request();
|
||||
}
|
||||
|
||||
late final _cst_new_box_autoadd_create_bolt_12_invoice_requestPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<wire_cst_create_bolt_12_invoice_request> Function()>>(
|
||||
'frbgen_breez_liquid_cst_new_box_autoadd_create_bolt_12_invoice_request',
|
||||
);
|
||||
late final _cst_new_box_autoadd_create_bolt_12_invoice_request =
|
||||
_cst_new_box_autoadd_create_bolt_12_invoice_requestPtr
|
||||
.asFunction<ffi.Pointer<wire_cst_create_bolt_12_invoice_request> Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Double> cst_new_box_autoadd_f_64(double value) {
|
||||
return _cst_new_box_autoadd_f_64(value);
|
||||
}
|
||||
@@ -6292,6 +6388,12 @@ final class wire_cst_check_message_request extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> signature;
|
||||
}
|
||||
|
||||
final class wire_cst_create_bolt_12_invoice_request extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> offer;
|
||||
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> invoice_request;
|
||||
}
|
||||
|
||||
final class wire_cst_fetch_payment_proposed_fees_request extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> swap_id;
|
||||
}
|
||||
@@ -6795,11 +6897,11 @@ final class wire_cst_prepare_receive_response extends ffi.Struct {
|
||||
@ffi.Int32()
|
||||
external int payment_method;
|
||||
|
||||
external ffi.Pointer<wire_cst_receive_amount> amount;
|
||||
|
||||
@ffi.Uint64()
|
||||
external int fees_sat;
|
||||
|
||||
external ffi.Pointer<wire_cst_receive_amount> amount;
|
||||
|
||||
external ffi.Pointer<ffi.Uint64> min_payer_amount_sat;
|
||||
|
||||
external ffi.Pointer<ffi.Uint64> max_payer_amount_sat;
|
||||
@@ -7375,6 +7477,10 @@ final class wire_cst_check_message_response extends ffi.Struct {
|
||||
external bool is_valid;
|
||||
}
|
||||
|
||||
final class wire_cst_create_bolt_12_invoice_response extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> invoice;
|
||||
}
|
||||
|
||||
final class wire_cst_wallet_info extends ffi.Struct {
|
||||
@ffi.Uint64()
|
||||
external int balance_sat;
|
||||
|
||||
@@ -391,6 +391,44 @@ class ConnectRequest {
|
||||
seed == other.seed;
|
||||
}
|
||||
|
||||
/// An argument when calling [crate::sdk::LiquidSdk::create_bolt12_invoice].
|
||||
class CreateBolt12InvoiceRequest {
|
||||
/// The BOLT12 offer
|
||||
final String offer;
|
||||
|
||||
/// The invoice request created from the offer
|
||||
final String invoiceRequest;
|
||||
|
||||
const CreateBolt12InvoiceRequest({required this.offer, required this.invoiceRequest});
|
||||
|
||||
@override
|
||||
int get hashCode => offer.hashCode ^ invoiceRequest.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is CreateBolt12InvoiceRequest &&
|
||||
runtimeType == other.runtimeType &&
|
||||
offer == other.offer &&
|
||||
invoiceRequest == other.invoiceRequest;
|
||||
}
|
||||
|
||||
/// Returned when calling [crate::sdk::LiquidSdk::create_bolt12_invoice].
|
||||
class CreateBolt12InvoiceResponse {
|
||||
/// The BOLT12 invoice
|
||||
final String invoice;
|
||||
|
||||
const CreateBolt12InvoiceResponse({required this.invoice});
|
||||
|
||||
@override
|
||||
int get hashCode => invoice.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is CreateBolt12InvoiceResponse && runtimeType == other.runtimeType && invoice == other.invoice;
|
||||
}
|
||||
|
||||
/// An argument when calling [crate::sdk::LiquidSdk::fetch_payment_proposed_fees].
|
||||
class FetchPaymentProposedFeesRequest {
|
||||
final String swapId;
|
||||
@@ -964,7 +1002,7 @@ sealed class PaymentDetails with _$PaymentDetails {
|
||||
}
|
||||
|
||||
/// The send/receive methods supported by the SDK
|
||||
enum PaymentMethod { lightning, bitcoinAddress, liquidAddress }
|
||||
enum PaymentMethod { lightning, bolt11Invoice, bolt12Offer, bitcoinAddress, liquidAddress }
|
||||
|
||||
/// The payment state of an individual payment.
|
||||
enum PaymentState {
|
||||
@@ -1248,7 +1286,6 @@ class PrepareReceiveRequest {
|
||||
/// Returned when calling [crate::sdk::LiquidSdk::prepare_receive_payment].
|
||||
class PrepareReceiveResponse {
|
||||
final PaymentMethod paymentMethod;
|
||||
final ReceiveAmount? amount;
|
||||
|
||||
/// Generally represents the total fees that would be paid to send or receive this payment.
|
||||
///
|
||||
@@ -1260,6 +1297,9 @@ class PrepareReceiveResponse {
|
||||
/// In all other types of swaps, the swapper service fee is included in `fees_sat`.
|
||||
final BigInt feesSat;
|
||||
|
||||
/// The amount to be paid in either Bitcoin or another asset
|
||||
final ReceiveAmount? amount;
|
||||
|
||||
/// The minimum amount the payer can send for this swap to succeed.
|
||||
///
|
||||
/// When the method is [PaymentMethod::LiquidAddress], this is empty.
|
||||
@@ -1277,8 +1317,8 @@ class PrepareReceiveResponse {
|
||||
|
||||
const PrepareReceiveResponse({
|
||||
required this.paymentMethod,
|
||||
this.amount,
|
||||
required this.feesSat,
|
||||
this.amount,
|
||||
this.minPayerAmountSat,
|
||||
this.maxPayerAmountSat,
|
||||
this.swapperFeerate,
|
||||
@@ -1287,8 +1327,8 @@ class PrepareReceiveResponse {
|
||||
@override
|
||||
int get hashCode =>
|
||||
paymentMethod.hashCode ^
|
||||
amount.hashCode ^
|
||||
feesSat.hashCode ^
|
||||
amount.hashCode ^
|
||||
minPayerAmountSat.hashCode ^
|
||||
maxPayerAmountSat.hashCode ^
|
||||
swapperFeerate.hashCode;
|
||||
@@ -1299,8 +1339,8 @@ class PrepareReceiveResponse {
|
||||
other is PrepareReceiveResponse &&
|
||||
runtimeType == other.runtimeType &&
|
||||
paymentMethod == other.paymentMethod &&
|
||||
amount == other.amount &&
|
||||
feesSat == other.feesSat &&
|
||||
amount == other.amount &&
|
||||
minPayerAmountSat == other.minPayerAmountSat &&
|
||||
maxPayerAmountSat == other.maxPayerAmountSat &&
|
||||
swapperFeerate == other.swapperFeerate;
|
||||
|
||||
@@ -135,6 +135,27 @@ class FlutterBreezLiquidBindings {
|
||||
_frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_check_messagePtr
|
||||
.asFunction<WireSyncRust2DartDco Function(int, ffi.Pointer<wire_cst_check_message_request>)>();
|
||||
|
||||
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice(
|
||||
int port_,
|
||||
int that,
|
||||
ffi.Pointer<wire_cst_create_bolt_12_invoice_request> req,
|
||||
) {
|
||||
return _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice(
|
||||
port_,
|
||||
that,
|
||||
req,
|
||||
);
|
||||
}
|
||||
|
||||
late final _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoicePtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Void Function(
|
||||
ffi.Int64, ffi.UintPtr, ffi.Pointer<wire_cst_create_bolt_12_invoice_request>)>>(
|
||||
'frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice');
|
||||
late final _frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoice =
|
||||
_frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_create_bolt12_invoicePtr
|
||||
.asFunction<void Function(int, int, ffi.Pointer<wire_cst_create_bolt_12_invoice_request>)>();
|
||||
|
||||
void frbgen_breez_liquid_wire__crate__bindings__BindingLiquidSdk_disconnect(
|
||||
int port_,
|
||||
int that,
|
||||
@@ -1009,6 +1030,18 @@ class FlutterBreezLiquidBindings {
|
||||
_frbgen_breez_liquid_cst_new_box_autoadd_connect_requestPtr
|
||||
.asFunction<ffi.Pointer<wire_cst_connect_request> Function()>();
|
||||
|
||||
ffi.Pointer<wire_cst_create_bolt_12_invoice_request>
|
||||
frbgen_breez_liquid_cst_new_box_autoadd_create_bolt_12_invoice_request() {
|
||||
return _frbgen_breez_liquid_cst_new_box_autoadd_create_bolt_12_invoice_request();
|
||||
}
|
||||
|
||||
late final _frbgen_breez_liquid_cst_new_box_autoadd_create_bolt_12_invoice_requestPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<wire_cst_create_bolt_12_invoice_request> Function()>>(
|
||||
'frbgen_breez_liquid_cst_new_box_autoadd_create_bolt_12_invoice_request');
|
||||
late final _frbgen_breez_liquid_cst_new_box_autoadd_create_bolt_12_invoice_request =
|
||||
_frbgen_breez_liquid_cst_new_box_autoadd_create_bolt_12_invoice_requestPtr
|
||||
.asFunction<ffi.Pointer<wire_cst_create_bolt_12_invoice_request> Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Double> frbgen_breez_liquid_cst_new_box_autoadd_f_64(
|
||||
double value,
|
||||
) {
|
||||
@@ -1882,6 +1915,26 @@ class FlutterBreezLiquidBindings {
|
||||
_uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_check_messagePtr
|
||||
.asFunction<RustBuffer Function(ffi.Pointer<ffi.Void>, RustBuffer, ffi.Pointer<RustCallStatus>)>();
|
||||
|
||||
RustBuffer uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_create_bolt12_invoice(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
RustBuffer req,
|
||||
ffi.Pointer<RustCallStatus> out_status,
|
||||
) {
|
||||
return _uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_create_bolt12_invoice(
|
||||
ptr,
|
||||
req,
|
||||
out_status,
|
||||
);
|
||||
}
|
||||
|
||||
late final _uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_create_bolt12_invoicePtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
RustBuffer Function(ffi.Pointer<ffi.Void>, RustBuffer, ffi.Pointer<RustCallStatus>)>>(
|
||||
'uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_create_bolt12_invoice');
|
||||
late final _uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_create_bolt12_invoice =
|
||||
_uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_create_bolt12_invoicePtr
|
||||
.asFunction<RustBuffer Function(ffi.Pointer<ffi.Void>, RustBuffer, ffi.Pointer<RustCallStatus>)>();
|
||||
|
||||
void uniffi_breez_sdk_liquid_bindings_fn_method_bindingliquidsdk_disconnect(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
ffi.Pointer<RustCallStatus> out_status,
|
||||
@@ -3646,6 +3699,17 @@ class FlutterBreezLiquidBindings {
|
||||
_uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_check_messagePtr
|
||||
.asFunction<int Function()>();
|
||||
|
||||
int uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_create_bolt12_invoice() {
|
||||
return _uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_create_bolt12_invoice();
|
||||
}
|
||||
|
||||
late final _uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_create_bolt12_invoicePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Uint16 Function()>>(
|
||||
'uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_create_bolt12_invoice');
|
||||
late final _uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_create_bolt12_invoice =
|
||||
_uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_create_bolt12_invoicePtr
|
||||
.asFunction<int Function()>();
|
||||
|
||||
int uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_disconnect() {
|
||||
return _uniffi_breez_sdk_liquid_bindings_checksum_method_bindingliquidsdk_disconnect();
|
||||
}
|
||||
@@ -4185,6 +4249,12 @@ final class wire_cst_check_message_request extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> signature;
|
||||
}
|
||||
|
||||
final class wire_cst_create_bolt_12_invoice_request extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> offer;
|
||||
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> invoice_request;
|
||||
}
|
||||
|
||||
final class wire_cst_fetch_payment_proposed_fees_request extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> swap_id;
|
||||
}
|
||||
@@ -4688,11 +4758,11 @@ final class wire_cst_prepare_receive_response extends ffi.Struct {
|
||||
@ffi.Int32()
|
||||
external int payment_method;
|
||||
|
||||
external ffi.Pointer<wire_cst_receive_amount> amount;
|
||||
|
||||
@ffi.Uint64()
|
||||
external int fees_sat;
|
||||
|
||||
external ffi.Pointer<wire_cst_receive_amount> amount;
|
||||
|
||||
external ffi.Pointer<ffi.Uint64> min_payer_amount_sat;
|
||||
|
||||
external ffi.Pointer<ffi.Uint64> max_payer_amount_sat;
|
||||
@@ -5268,6 +5338,10 @@ final class wire_cst_check_message_response extends ffi.Struct {
|
||||
external bool is_valid;
|
||||
}
|
||||
|
||||
final class wire_cst_create_bolt_12_invoice_response extends ffi.Struct {
|
||||
external ffi.Pointer<wire_cst_list_prim_u_8_strict> invoice;
|
||||
}
|
||||
|
||||
final class wire_cst_wallet_info extends ffi.Struct {
|
||||
@ffi.Uint64()
|
||||
external int balance_sat;
|
||||
|
||||
@@ -594,6 +594,69 @@ fun asConnectWithSignerRequestList(arr: ReadableArray): List<ConnectWithSignerRe
|
||||
return list
|
||||
}
|
||||
|
||||
fun asCreateBolt12InvoiceRequest(createBolt12InvoiceRequest: ReadableMap): CreateBolt12InvoiceRequest? {
|
||||
if (!validateMandatoryFields(
|
||||
createBolt12InvoiceRequest,
|
||||
arrayOf(
|
||||
"offer",
|
||||
"invoiceRequest",
|
||||
),
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
val offer = createBolt12InvoiceRequest.getString("offer")!!
|
||||
val invoiceRequest = createBolt12InvoiceRequest.getString("invoiceRequest")!!
|
||||
return CreateBolt12InvoiceRequest(offer, invoiceRequest)
|
||||
}
|
||||
|
||||
fun readableMapOf(createBolt12InvoiceRequest: CreateBolt12InvoiceRequest): ReadableMap =
|
||||
readableMapOf(
|
||||
"offer" to createBolt12InvoiceRequest.offer,
|
||||
"invoiceRequest" to createBolt12InvoiceRequest.invoiceRequest,
|
||||
)
|
||||
|
||||
fun asCreateBolt12InvoiceRequestList(arr: ReadableArray): List<CreateBolt12InvoiceRequest> {
|
||||
val list = ArrayList<CreateBolt12InvoiceRequest>()
|
||||
for (value in arr.toList()) {
|
||||
when (value) {
|
||||
is ReadableMap -> list.add(asCreateBolt12InvoiceRequest(value)!!)
|
||||
else -> throw SdkException.Generic(errUnexpectedType(value))
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun asCreateBolt12InvoiceResponse(createBolt12InvoiceResponse: ReadableMap): CreateBolt12InvoiceResponse? {
|
||||
if (!validateMandatoryFields(
|
||||
createBolt12InvoiceResponse,
|
||||
arrayOf(
|
||||
"invoice",
|
||||
),
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
val invoice = createBolt12InvoiceResponse.getString("invoice")!!
|
||||
return CreateBolt12InvoiceResponse(invoice)
|
||||
}
|
||||
|
||||
fun readableMapOf(createBolt12InvoiceResponse: CreateBolt12InvoiceResponse): ReadableMap =
|
||||
readableMapOf(
|
||||
"invoice" to createBolt12InvoiceResponse.invoice,
|
||||
)
|
||||
|
||||
fun asCreateBolt12InvoiceResponseList(arr: ReadableArray): List<CreateBolt12InvoiceResponse> {
|
||||
val list = ArrayList<CreateBolt12InvoiceResponse>()
|
||||
for (value in arr.toList()) {
|
||||
when (value) {
|
||||
is ReadableMap -> list.add(asCreateBolt12InvoiceResponse(value)!!)
|
||||
else -> throw SdkException.Generic(errUnexpectedType(value))
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
fun asCurrencyInfo(currencyInfo: ReadableMap): CurrencyInfo? {
|
||||
if (!validateMandatoryFields(
|
||||
currencyInfo,
|
||||
|
||||
@@ -292,6 +292,24 @@ class BreezSDKLiquidModule(
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun createBolt12Invoice(
|
||||
req: ReadableMap,
|
||||
promise: Promise,
|
||||
) {
|
||||
executor.execute {
|
||||
try {
|
||||
val createBolt12InvoiceRequest =
|
||||
asCreateBolt12InvoiceRequest(req)
|
||||
?: run { throw SdkException.Generic(errMissingMandatoryField("req", "CreateBolt12InvoiceRequest")) }
|
||||
val res = getBindingLiquidSdk().createBolt12Invoice(createBolt12InvoiceRequest)
|
||||
promise.resolve(readableMapOf(res))
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e.javaClass.simpleName.replace("Exception", "Error"), e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun fetchLightningLimits(promise: Promise) {
|
||||
executor.execute {
|
||||
|
||||
@@ -713,6 +713,72 @@ enum BreezSDKLiquidMapper {
|
||||
return connectWithSignerRequestList.map { v -> [String: Any?] in return dictionaryOf(connectWithSignerRequest: v) }
|
||||
}
|
||||
|
||||
static func asCreateBolt12InvoiceRequest(createBolt12InvoiceRequest: [String: Any?]) throws -> CreateBolt12InvoiceRequest {
|
||||
guard let offer = createBolt12InvoiceRequest["offer"] as? String else {
|
||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "offer", typeName: "CreateBolt12InvoiceRequest"))
|
||||
}
|
||||
guard let invoiceRequest = createBolt12InvoiceRequest["invoiceRequest"] as? String else {
|
||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "invoiceRequest", typeName: "CreateBolt12InvoiceRequest"))
|
||||
}
|
||||
|
||||
return CreateBolt12InvoiceRequest(offer: offer, invoiceRequest: invoiceRequest)
|
||||
}
|
||||
|
||||
static func dictionaryOf(createBolt12InvoiceRequest: CreateBolt12InvoiceRequest) -> [String: Any?] {
|
||||
return [
|
||||
"offer": createBolt12InvoiceRequest.offer,
|
||||
"invoiceRequest": createBolt12InvoiceRequest.invoiceRequest,
|
||||
]
|
||||
}
|
||||
|
||||
static func asCreateBolt12InvoiceRequestList(arr: [Any]) throws -> [CreateBolt12InvoiceRequest] {
|
||||
var list = [CreateBolt12InvoiceRequest]()
|
||||
for value in arr {
|
||||
if let val = value as? [String: Any?] {
|
||||
var createBolt12InvoiceRequest = try asCreateBolt12InvoiceRequest(createBolt12InvoiceRequest: val)
|
||||
list.append(createBolt12InvoiceRequest)
|
||||
} else {
|
||||
throw SdkError.Generic(message: errUnexpectedType(typeName: "CreateBolt12InvoiceRequest"))
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
static func arrayOf(createBolt12InvoiceRequestList: [CreateBolt12InvoiceRequest]) -> [Any] {
|
||||
return createBolt12InvoiceRequestList.map { v -> [String: Any?] in return dictionaryOf(createBolt12InvoiceRequest: v) }
|
||||
}
|
||||
|
||||
static func asCreateBolt12InvoiceResponse(createBolt12InvoiceResponse: [String: Any?]) throws -> CreateBolt12InvoiceResponse {
|
||||
guard let invoice = createBolt12InvoiceResponse["invoice"] as? String else {
|
||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "invoice", typeName: "CreateBolt12InvoiceResponse"))
|
||||
}
|
||||
|
||||
return CreateBolt12InvoiceResponse(invoice: invoice)
|
||||
}
|
||||
|
||||
static func dictionaryOf(createBolt12InvoiceResponse: CreateBolt12InvoiceResponse) -> [String: Any?] {
|
||||
return [
|
||||
"invoice": createBolt12InvoiceResponse.invoice,
|
||||
]
|
||||
}
|
||||
|
||||
static func asCreateBolt12InvoiceResponseList(arr: [Any]) throws -> [CreateBolt12InvoiceResponse] {
|
||||
var list = [CreateBolt12InvoiceResponse]()
|
||||
for value in arr {
|
||||
if let val = value as? [String: Any?] {
|
||||
var createBolt12InvoiceResponse = try asCreateBolt12InvoiceResponse(createBolt12InvoiceResponse: val)
|
||||
list.append(createBolt12InvoiceResponse)
|
||||
} else {
|
||||
throw SdkError.Generic(message: errUnexpectedType(typeName: "CreateBolt12InvoiceResponse"))
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
static func arrayOf(createBolt12InvoiceResponseList: [CreateBolt12InvoiceResponse]) -> [Any] {
|
||||
return createBolt12InvoiceResponseList.map { v -> [String: Any?] in return dictionaryOf(createBolt12InvoiceResponse: v) }
|
||||
}
|
||||
|
||||
static func asCurrencyInfo(currencyInfo: [String: Any?]) throws -> CurrencyInfo {
|
||||
guard let name = currencyInfo["name"] as? String else {
|
||||
throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "name", typeName: "CurrencyInfo"))
|
||||
@@ -4450,6 +4516,12 @@ enum BreezSDKLiquidMapper {
|
||||
case "lightning":
|
||||
return PaymentMethod.lightning
|
||||
|
||||
case "bolt11Invoice":
|
||||
return PaymentMethod.bolt11Invoice
|
||||
|
||||
case "bolt12Offer":
|
||||
return PaymentMethod.bolt12Offer
|
||||
|
||||
case "bitcoinAddress":
|
||||
return PaymentMethod.bitcoinAddress
|
||||
|
||||
@@ -4465,6 +4537,12 @@ enum BreezSDKLiquidMapper {
|
||||
case .lightning:
|
||||
return "lightning"
|
||||
|
||||
case .bolt11Invoice:
|
||||
return "bolt11Invoice"
|
||||
|
||||
case .bolt12Offer:
|
||||
return "bolt12Offer"
|
||||
|
||||
case .bitcoinAddress:
|
||||
return "bitcoinAddress"
|
||||
|
||||
|
||||
@@ -85,6 +85,12 @@ RCT_EXTERN_METHOD(
|
||||
reject: (RCTPromiseRejectBlock)reject
|
||||
)
|
||||
|
||||
RCT_EXTERN_METHOD(
|
||||
createBolt12Invoice: (NSDictionary*)req
|
||||
resolve: (RCTPromiseResolveBlock)resolve
|
||||
reject: (RCTPromiseRejectBlock)reject
|
||||
)
|
||||
|
||||
RCT_EXTERN_METHOD(
|
||||
fetchLightningLimits: (RCTPromiseResolveBlock)resolve
|
||||
reject: (RCTPromiseRejectBlock)reject
|
||||
|
||||
@@ -231,6 +231,17 @@ class RNBreezSDKLiquid: RCTEventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
@objc(createBolt12Invoice:resolve:reject:)
|
||||
func createBolt12Invoice(_ req: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
do {
|
||||
let createBolt12InvoiceRequest = try BreezSDKLiquidMapper.asCreateBolt12InvoiceRequest(createBolt12InvoiceRequest: req)
|
||||
var res = try getBindingLiquidSdk().createBolt12Invoice(req: createBolt12InvoiceRequest)
|
||||
resolve(BreezSDKLiquidMapper.dictionaryOf(createBolt12InvoiceResponse: res))
|
||||
} catch let err {
|
||||
rejectErr(err: err, reject: reject)
|
||||
}
|
||||
}
|
||||
|
||||
@objc(fetchLightningLimits:reject:)
|
||||
func fetchLightningLimits(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
do {
|
||||
|
||||
@@ -117,6 +117,15 @@ export interface ConnectWithSignerRequest {
|
||||
config: Config
|
||||
}
|
||||
|
||||
export interface CreateBolt12InvoiceRequest {
|
||||
offer: string
|
||||
invoiceRequest: string
|
||||
}
|
||||
|
||||
export interface CreateBolt12InvoiceResponse {
|
||||
invoice: string
|
||||
}
|
||||
|
||||
export interface CurrencyInfo {
|
||||
name: string
|
||||
fractionSize: number
|
||||
@@ -735,6 +744,8 @@ export type PaymentDetails = {
|
||||
|
||||
export enum PaymentMethod {
|
||||
LIGHTNING = "lightning",
|
||||
BOLT11_INVOICE = "bolt11Invoice",
|
||||
BOLT12_OFFER = "bolt12Offer",
|
||||
BITCOIN_ADDRESS = "bitcoinAddress",
|
||||
LIQUID_ADDRESS = "liquidAddress"
|
||||
}
|
||||
@@ -949,6 +960,11 @@ export const receivePayment = async (req: ReceivePaymentRequest): Promise<Receiv
|
||||
return response
|
||||
}
|
||||
|
||||
export const createBolt12Invoice = async (req: CreateBolt12InvoiceRequest): Promise<CreateBolt12InvoiceResponse> => {
|
||||
const response = await BreezSDKLiquid.createBolt12Invoice(req)
|
||||
return response
|
||||
}
|
||||
|
||||
export const fetchLightningLimits = async (): Promise<LightningPaymentLimitsResponse> => {
|
||||
const response = await BreezSDKLiquid.fetchLightningLimits()
|
||||
return response
|
||||
|
||||
Submodule regtest/boltz updated: 9036dcb31e...abcc1526bf
Reference in New Issue
Block a user