diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 4d0b8f50..5a2e1bcf 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -194,6 +194,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "asn1-rs" version = "0.6.2" @@ -355,6 +361,16 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals 0.3.0", + "bitcoin_hashes 0.14.0", +] + [[package]] name = "base64" version = "0.13.1" @@ -385,6 +401,12 @@ version = "0.10.0-beta" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "bip21" version = "0.2.0" @@ -440,14 +462,31 @@ checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" dependencies = [ "base64 0.21.7", "bech32 0.10.0-beta", - "bitcoin-internals", + "bitcoin-internals 0.2.0", "bitcoin_hashes 0.13.0", - "hex-conservative", + "hex-conservative 0.1.2", "hex_lit", "secp256k1 0.28.2", "serde", ] +[[package]] +name = "bitcoin" +version = "0.32.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788902099d47c8682efe6a7afb01c8d58b9794ba66c06affd81c3d6b560743eb" +dependencies = [ + "base58ck", + "bech32 0.11.0", + "bitcoin-internals 0.3.0", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes 0.14.0", + "hex-conservative 0.2.1", + "hex_lit", + "secp256k1 0.29.1", +] + [[package]] name = "bitcoin-internals" version = "0.2.0" @@ -457,12 +496,33 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" + +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + [[package]] name = "bitcoin-private" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals 0.3.0", +] + [[package]] name = "bitcoin_hashes" version = "0.11.0" @@ -484,11 +544,21 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" dependencies = [ - "bitcoin-internals", - "hex-conservative", + "bitcoin-internals 0.2.0", + "hex-conservative 0.1.2", "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.1", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -539,7 +609,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/SatoshiPortal/boltz-rust?branch=trunk#8b8bddeb362b6d62ae92ed9a6cb759e1d0506a12" +source = "git+https://github.com/ok300/boltz-rust?branch=ok300-support-bolt12#5e5181552e822ce2b6c2b6642f903acba3118b16" dependencies = [ "bip39", "bitcoin 0.31.2", @@ -588,6 +658,7 @@ dependencies = [ "futures-util", "glob", "hex", + "lightning 0.0.125", "log", "lwk_common", "lwk_signer", @@ -1422,6 +1493,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex_lit" version = "0.1.1" @@ -1799,7 +1879,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d9b36ae12b379905bfc429ce5d4e8ca4a55c8dd3de73074309bd0bcc053bcac" dependencies = [ "bitcoin 0.30.2", - "hex-conservative", + "hex-conservative 0.1.2", +] + +[[package]] +name = "lightning" +version = "0.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "767f388e50251da71f95a3737d6db32c9729f9de6427a54fa92bb994d04d793f" +dependencies = [ + "bech32 0.9.1", + "bitcoin 0.32.4", + "lightning-invoice 0.32.0", + "lightning-types", ] [[package]] @@ -1829,6 +1921,28 @@ dependencies = [ "secp256k1 0.27.0", ] +[[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.4", + "lightning-types", +] + +[[package]] +name = "lightning-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1083b8d9137000edf3bfcb1ff011c0d25e0cdd2feb98cc21d6765e64a494148f" +dependencies = [ + "bech32 0.9.1", + "bitcoin 0.32.4", + "hex-conservative 0.2.1", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1981,7 +2095,7 @@ checksum = "3127e10529a57a8f7fa9b1332c4c2f72baadaca6777798f910dff3c922620b14" dependencies = [ "bech32 0.10.0-beta", "bitcoin 0.31.2", - "bitcoin-internals", + "bitcoin-internals 0.2.0", ] [[package]] @@ -2931,8 +3045,8 @@ dependencies = [ [[package]] name = "sdk-common" -version = "0.5.2" -source = "git+https://github.com/breez/breez-sdk?rev=441a9fd50c32098b2887e960c8a4bcc5956da1af#441a9fd50c32098b2887e960c8a4bcc5956da1af" +version = "0.6.2" +source = "git+https://github.com/breez/breez-sdk?branch=ok300-sdk-common-parse-bolt12-offer#caf483b6eebf860e079c239858f2900c82302955" dependencies = [ "aes 0.8.4", "anyhow", @@ -2992,6 +3106,16 @@ dependencies = [ "serde", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes 0.13.0", + "secp256k1-sys 0.10.1", +] + [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -3019,6 +3143,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-zkp" version = "0.10.0" @@ -3996,7 +4129,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/cli/src/commands.rs b/cli/src/commands.rs index 766feb5d..78cf7422 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::thread; use std::time::Duration; -use anyhow::Result; +use anyhow::{anyhow, Result}; use breez_sdk_liquid::prelude::*; use clap::{arg, Parser}; use qrcode_rs::render::unicode; @@ -21,10 +21,14 @@ use serde_json::to_string_pretty; pub(crate) enum Command { /// Send a payment directly or via a swap SendPayment { - /// Invoice which has to be paid + /// Invoice which has to be paid (BOLT11) #[arg(long)] invoice: Option, + /// BOLT12 offer. If specified, amount_sat must also be set. + #[arg(long)] + offer: Option, + /// Either BIP21 URI or Liquid address we intend to pay to #[arg(long)] address: Option, @@ -290,12 +294,12 @@ pub(crate) async fn handle_command( InputType::Bolt11 { invoice } => result.push_str(&build_qr_text(&invoice.bolt11)), InputType::LiquidAddress { address } => { result.push_str(&build_qr_text(&address.to_uri().map_err(|e| { - anyhow::anyhow!("Could not build BIP21 from address data: {e:?}") + anyhow!("Could not build BIP21 from address data: {e:?}") })?)) } InputType::BitcoinAddress { address } => { result.push_str(&build_qr_text(&address.to_uri().map_err(|e| { - anyhow::anyhow!("Could not build BIP21 from address data: {e:?}") + anyhow!("Could not build BIP21 from address data: {e:?}") })?)) } _ => {} @@ -312,25 +316,30 @@ pub(crate) async fn handle_command( } Command::SendPayment { invoice, + offer, address, amount_sat, drain, delay, } => { - let destination = match (invoice, address) { - (None, None) => { - return Err(anyhow::anyhow!( - "Must specify either an invoice or a direct/BIP21 address." + let destination = match (invoice, offer, address) { + (Some(invoice), None, None) => Ok(invoice), + (None, Some(offer), None) => match amount_sat { + Some(_) => Ok(offer), + None => Err(anyhow!( + "Must specify an amount for a BOLT12 offer." )) - } - (Some(invoice), None) => invoice, - (None, Some(address)) => address, - (Some(_), Some(_)) => { - return Err(anyhow::anyhow!( + }, + (None, None, Some(address)) => Ok(address), + (Some(_), _, Some(_)) => { + Err(anyhow::anyhow!( "Cannot specify both invoice and address at the same time." )) } - }; + _ => Err(anyhow!( + "Must specify either a BOLT11 invoice, a BOLT12 offer or a direct/BIP21 address." + )) + }?; let amount = match (amount_sat, drain.unwrap_or(false)) { (Some(amount_sat), _) => Some(PayAmount::Receiver { amount_sat }), (_, true) => Some(PayAmount::Drain), @@ -492,7 +501,7 @@ pub(crate) async fn handle_command( match maybe_payment { Some(payment) => command_result!(payment), None => { - return Err(anyhow::anyhow!("Payment not found.")); + return Err(anyhow!("Payment not found.")); } } } @@ -595,7 +604,7 @@ pub(crate) async fn handle_command( .await?; Ok(pay_res) } - _ => Err(anyhow::anyhow!("Invalid input")), + _ => Err(anyhow!("Invalid input")), }?; command_result!(res) @@ -619,7 +628,7 @@ pub(crate) async fn handle_command( .await?; Ok(withdraw_res) } - _ => Err(anyhow::anyhow!("Invalid input")), + _ => Err(anyhow!("Invalid input")), }?; command_result!(res) @@ -632,7 +641,7 @@ pub(crate) async fn handle_command( let auth_res = sdk.lnurl_auth(ad).await?; serde_json::to_string_pretty(&auth_res).map_err(|e| e.into()) } - _ => Err(anyhow::anyhow!("Unexpected result type")), + _ => Err(anyhow!("Unexpected result type")), }?; command_result!(res) diff --git a/lib/Cargo.lock b/lib/Cargo.lock index 28dfd6c8..bb064bb2 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -188,6 +188,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "askama" version = "0.11.1" @@ -429,6 +435,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals 0.3.0", + "bitcoin_hashes 0.14.0", +] + [[package]] name = "base64" version = "0.13.1" @@ -468,6 +484,12 @@ version = "0.10.0-beta" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "bincode" version = "1.3.3" @@ -554,14 +576,31 @@ checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" dependencies = [ "base64 0.21.7", "bech32 0.10.0-beta", - "bitcoin-internals", + "bitcoin-internals 0.2.0", "bitcoin_hashes 0.13.0", - "hex-conservative", + "hex-conservative 0.1.2", "hex_lit", "secp256k1 0.28.2", "serde", ] +[[package]] +name = "bitcoin" +version = "0.32.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788902099d47c8682efe6a7afb01c8d58b9794ba66c06affd81c3d6b560743eb" +dependencies = [ + "base58ck", + "bech32 0.11.0", + "bitcoin-internals 0.3.0", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes 0.14.0", + "hex-conservative 0.2.1", + "hex_lit", + "secp256k1 0.29.1", +] + [[package]] name = "bitcoin-internals" version = "0.2.0" @@ -571,12 +610,33 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" + +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + [[package]] name = "bitcoin-private" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals 0.3.0", +] + [[package]] name = "bitcoin_hashes" version = "0.11.0" @@ -598,11 +658,21 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" dependencies = [ - "bitcoin-internals", - "hex-conservative", + "bitcoin-internals 0.2.0", + "hex-conservative 0.1.2", "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.1", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -653,7 +723,7 @@ dependencies = [ [[package]] name = "boltz-client" version = "0.1.3" -source = "git+https://github.com/SatoshiPortal/boltz-rust?branch=trunk#8b8bddeb362b6d62ae92ed9a6cb759e1d0506a12" +source = "git+https://github.com/ok300/boltz-rust?branch=ok300-support-bolt12#5e5181552e822ce2b6c2b6642f903acba3118b16" dependencies = [ "bip39", "bitcoin 0.31.2", @@ -687,6 +757,7 @@ dependencies = [ "glob", "hex", "lazy_static", + "lightning 0.0.125", "log", "lwk_common", "lwk_signer", @@ -1606,6 +1677,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex_lit" version = "0.1.1" @@ -2002,7 +2082,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d9b36ae12b379905bfc429ce5d4e8ca4a55c8dd3de73074309bd0bcc053bcac" dependencies = [ "bitcoin 0.30.2", - "hex-conservative", + "hex-conservative 0.1.2", +] + +[[package]] +name = "lightning" +version = "0.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "767f388e50251da71f95a3737d6db32c9729f9de6427a54fa92bb994d04d793f" +dependencies = [ + "bech32 0.9.1", + "bitcoin 0.32.4", + "lightning-invoice 0.32.0", + "lightning-types", ] [[package]] @@ -2032,6 +2124,28 @@ dependencies = [ "secp256k1 0.27.0", ] +[[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.4", + "lightning-types", +] + +[[package]] +name = "lightning-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1083b8d9137000edf3bfcb1ff011c0d25e0cdd2feb98cc21d6765e64a494148f" +dependencies = [ + "bech32 0.9.1", + "bitcoin 0.32.4", + "hex-conservative 0.2.1", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2194,7 +2308,7 @@ checksum = "3127e10529a57a8f7fa9b1332c4c2f72baadaca6777798f910dff3c922620b14" dependencies = [ "bech32 0.10.0-beta", "bitcoin 0.31.2", - "bitcoin-internals", + "bitcoin-internals 0.2.0", ] [[package]] @@ -3179,8 +3293,8 @@ dependencies = [ [[package]] name = "sdk-common" -version = "0.5.2" -source = "git+https://github.com/breez/breez-sdk?rev=441a9fd50c32098b2887e960c8a4bcc5956da1af#441a9fd50c32098b2887e960c8a4bcc5956da1af" +version = "0.6.2" +source = "git+https://github.com/breez/breez-sdk?branch=ok300-sdk-common-parse-bolt12-offer#caf483b6eebf860e079c239858f2900c82302955" dependencies = [ "aes 0.8.4", "anyhow", @@ -3240,6 +3354,16 @@ dependencies = [ "serde", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes 0.13.0", + "secp256k1-sys 0.10.1", +] + [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -3267,6 +3391,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-zkp" version = "0.10.0" diff --git a/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h b/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h index e887ca4f..76701b41 100644 --- a/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h +++ b/lib/bindings/langs/flutter/breez_sdk_liquid/include/breez_sdk_liquid.h @@ -171,9 +171,49 @@ typedef struct wire_cst_SendDestination_Bolt11 { struct wire_cst_ln_invoice *invoice; } wire_cst_SendDestination_Bolt11; +typedef struct wire_cst_list_String { + struct wire_cst_list_prim_u_8_strict **ptr; + int32_t len; +} wire_cst_list_String; + +typedef struct wire_cst_Amount_Bitcoin { + uint64_t amount_msat; +} wire_cst_Amount_Bitcoin; + +typedef struct wire_cst_Amount_Currency { + struct wire_cst_list_prim_u_8_strict *iso4217_code; + uint64_t fractional_amount; +} wire_cst_Amount_Currency; + +typedef union AmountKind { + struct wire_cst_Amount_Bitcoin Bitcoin; + struct wire_cst_Amount_Currency Currency; +} AmountKind; + +typedef struct wire_cst_amount { + int32_t tag; + union AmountKind kind; +} wire_cst_amount; + +typedef struct wire_cst_ln_offer { + struct wire_cst_list_prim_u_8_strict *offer; + struct wire_cst_list_String *chains; + struct wire_cst_amount *min_amount; + struct wire_cst_list_prim_u_8_strict *description; + uint64_t *absolute_expiry; + struct wire_cst_list_prim_u_8_strict *issuer; + struct wire_cst_list_prim_u_8_strict *signing_pubkey; +} wire_cst_ln_offer; + +typedef struct wire_cst_SendDestination_Bolt12 { + struct wire_cst_ln_offer *offer; + uint64_t receiver_amount_sat; +} wire_cst_SendDestination_Bolt12; + typedef union SendDestinationKind { struct wire_cst_SendDestination_LiquidAddress LiquidAddress; struct wire_cst_SendDestination_Bolt11 Bolt11; + struct wire_cst_SendDestination_Bolt12 Bolt12; } SendDestinationKind; typedef struct wire_cst_send_destination { @@ -621,6 +661,10 @@ typedef struct wire_cst_InputType_Bolt11 { struct wire_cst_ln_invoice *invoice; } wire_cst_InputType_Bolt11; +typedef struct wire_cst_InputType_Bolt12Offer { + struct wire_cst_ln_offer *offer; +} wire_cst_InputType_Bolt12Offer; + typedef struct wire_cst_InputType_NodeId { struct wire_cst_list_prim_u_8_strict *node_id; } wire_cst_InputType_NodeId; @@ -649,6 +693,7 @@ typedef union InputTypeKind { struct wire_cst_InputType_BitcoinAddress BitcoinAddress; struct wire_cst_InputType_LiquidAddress LiquidAddress; struct wire_cst_InputType_Bolt11 Bolt11; + struct wire_cst_InputType_Bolt12Offer Bolt12Offer; struct wire_cst_InputType_NodeId NodeId; struct wire_cst_InputType_Url Url; struct wire_cst_InputType_LnUrlPay LnUrlPay; @@ -1120,6 +1165,8 @@ struct wire_cst_aes_success_action_data_decrypted *frbgen_breez_liquid_cst_new_b struct wire_cst_aes_success_action_data_result *frbgen_breez_liquid_cst_new_box_autoadd_aes_success_action_data_result(void); +struct wire_cst_amount *frbgen_breez_liquid_cst_new_box_autoadd_amount(void); + struct wire_cst_backup_request *frbgen_breez_liquid_cst_new_box_autoadd_backup_request(void); struct wire_cst_binding_event_listener *frbgen_breez_liquid_cst_new_box_autoadd_binding_event_listener(void); @@ -1146,6 +1193,8 @@ struct wire_cst_list_payments_request *frbgen_breez_liquid_cst_new_box_autoadd_l struct wire_cst_ln_invoice *frbgen_breez_liquid_cst_new_box_autoadd_ln_invoice(void); +struct wire_cst_ln_offer *frbgen_breez_liquid_cst_new_box_autoadd_ln_offer(void); + struct wire_cst_ln_url_auth_request_data *frbgen_breez_liquid_cst_new_box_autoadd_ln_url_auth_request_data(void); struct wire_cst_ln_url_error_data *frbgen_breez_liquid_cst_new_box_autoadd_ln_url_error_data(void); @@ -1208,6 +1257,8 @@ uint64_t *frbgen_breez_liquid_cst_new_box_autoadd_u_64(uint64_t value); struct wire_cst_url_success_action_data *frbgen_breez_liquid_cst_new_box_autoadd_url_success_action_data(void); +struct wire_cst_list_String *frbgen_breez_liquid_cst_new_list_String(int32_t len); + struct wire_cst_list_fiat_currency *frbgen_breez_liquid_cst_new_list_fiat_currency(int32_t len); struct wire_cst_list_locale_overrides *frbgen_breez_liquid_cst_new_list_locale_overrides(int32_t len); @@ -1232,6 +1283,7 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_aes_success_action_data); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_aes_success_action_data_decrypted); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_aes_success_action_data_result); + dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_amount); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_backup_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_binding_event_listener); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_bitcoin_address_data); @@ -1245,6 +1297,7 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_list_payment_details); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_list_payments_request); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_invoice); + dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_offer); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_url_auth_request_data); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_url_error_data); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_ln_url_pay_error_data); @@ -1276,6 +1329,7 @@ static int64_t dummy_method_to_enforce_bundling(void) { dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_u_32); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_u_64); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_box_autoadd_url_success_action_data); + dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_String); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_fiat_currency); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_locale_overrides); dummy_var ^= ((int64_t) (void*) frbgen_breez_liquid_cst_new_list_localized_name); diff --git a/lib/bindings/src/breez_sdk_liquid.udl b/lib/bindings/src/breez_sdk_liquid.udl index e994e7d2..f7f9b942 100644 --- a/lib/bindings/src/breez_sdk_liquid.udl +++ b/lib/bindings/src/breez_sdk_liquid.udl @@ -38,11 +38,28 @@ dictionary RouteHintHop { u64? htlc_maximum_msat; }; +[Enum] +interface Amount { + Bitcoin(u64 amount_msat); + Currency(string iso4217_code, u64 fractional_amount); +}; + +dictionary LNOffer { + string offer; + sequence chains; + string? description; + string? signing_pubkey; + Amount? min_amount; + u64? absolute_expiry; + string? issuer; +}; + [Enum] interface InputType { BitcoinAddress(BitcoinAddressData address); LiquidAddress(LiquidAddressData address); Bolt11(LNInvoice invoice); + Bolt12Offer(LNOffer offer); NodeId(string node_id); Url(string url); LnUrlPay(LnUrlPayRequestData data); @@ -380,6 +397,7 @@ dictionary PrepareSendRequest { interface SendDestination { LiquidAddress(LiquidAddressData address_data); Bolt11(LNInvoice invoice); + Bolt12(LNOffer offer, u64 receiver_amount_sat); }; dictionary PrepareSendResponse { diff --git a/lib/core/Cargo.toml b/lib/core/Cargo.toml index 9a9fb5ad..8ac29d36 100644 --- a/lib/core/Cargo.toml +++ b/lib/core/Cargo.toml @@ -14,12 +14,15 @@ frb = ["dep:flutter_rust_bridge"] [dependencies] anyhow = { workspace = true } bip39 = "2.0.0" -boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", branch = "trunk" } +#boltz-client = { git = "https://github.com/SatoshiPortal/boltz-rust", branch = "trunk" } +boltz-client = { git = "https://github.com/ok300/boltz-rust", branch = "ok300-support-bolt12" } chrono = "0.4" env_logger = "0.11" flutter_rust_bridge = { version = "=2.4.0", features = [ "chrono", ], optional = true } +# We need at least lightning v0.0.125 for the Bolt12 structs. The lightning version from sdk-common is too old (v0.0.118, matching vls-core). +lightning = "0.0.125" log = { workspace = true } lwk_common = "0.7.0" lwk_signer = "0.7.0" @@ -27,7 +30,8 @@ lwk_wollet = { git = "https://github.com/dangeross/lwk", branch = "savage-full-s #lwk_wollet = "0.7.0" rusqlite = { version = "0.31", features = ["backup", "bundled"] } rusqlite_migration = "1.0" -sdk-common = { git = "https://github.com/breez/breez-sdk", rev = "441a9fd50c32098b2887e960c8a4bcc5956da1af", features = ["liquid"]} +#sdk-common = { git = "https://github.com/breez/breez-sdk", rev = "441a9fd50c32098b2887e960c8a4bcc5956da1af", features = ["liquid"]} +sdk-common = { git = "https://github.com/breez/breez-sdk", branch = "ok300-sdk-common-parse-bolt12-offer", features = ["liquid"]} serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.116" strum = "0.25" diff --git a/lib/core/src/bindings.rs b/lib/core/src/bindings.rs index 7b82badf..53b99de1 100644 --- a/lib/core/src/bindings.rs +++ b/lib/core/src/bindings.rs @@ -329,11 +329,34 @@ pub struct _RouteHintHop { pub htlc_maximum_msat: Option, } +#[frb(mirror(Amount))] +pub enum _Amount { + Bitcoin { + amount_msat: u64, + }, + Currency { + iso4217_code: String, + fractional_amount: u64, + }, +} + +#[frb(mirror(LNOffer))] +pub struct _LNOffer { + pub offer: String, + pub chains: Vec, + pub min_amount: Option, + pub description: Option, + pub absolute_expiry: Option, + pub issuer: Option, + pub signing_pubkey: Option, +} + #[frb(mirror(InputType))] pub enum _InputType { BitcoinAddress { address: BitcoinAddressData }, LiquidAddress { address: LiquidAddressData }, Bolt11 { invoice: LNInvoice }, + Bolt12Offer { offer: LNOffer }, NodeId { node_id: String }, Url { url: String }, LnUrlPay { data: LnUrlPayRequestData }, diff --git a/lib/core/src/error.rs b/lib/core/src/error.rs index 5a0c3bec..ae5a3cc3 100644 --- a/lib/core/src/error.rs +++ b/lib/core/src/error.rs @@ -125,6 +125,12 @@ pub enum PaymentError { SignerError { err: String }, } impl PaymentError { + pub(crate) fn generic(err: &str) -> Self { + Self::Generic { + err: err.to_string(), + } + } + pub(crate) fn invalid_invoice(err: &str) -> Self { Self::InvalidInvoice { err: err.to_string(), @@ -136,6 +142,12 @@ impl PaymentError { err: err.to_string(), } } + + pub(crate) fn amount_missing(err: &str) -> Self { + Self::AmountMissing { + err: err.to_string(), + } + } } impl From for PaymentError { diff --git a/lib/core/src/frb_generated.rs b/lib/core/src/frb_generated.rs index 9e25cab2..18f8da19 100644 --- a/lib/core/src/frb_generated.rs +++ b/lib/core/src/frb_generated.rs @@ -1765,6 +1765,18 @@ const _: fn() = || { let _: String = reason; } } + match None::.unwrap() { + crate::bindings::Amount::Bitcoin { amount_msat } => { + let _: u64 = amount_msat; + } + crate::bindings::Amount::Currency { + iso4217_code, + fractional_amount, + } => { + let _: String = iso4217_code; + let _: u64 = fractional_amount; + } + } { let BitcoinAddressData = None::.unwrap(); let _: String = BitcoinAddressData.address; @@ -1798,6 +1810,9 @@ const _: fn() = || { crate::bindings::InputType::Bolt11 { invoice } => { let _: crate::bindings::LNInvoice = invoice; } + crate::bindings::InputType::Bolt12Offer { offer } => { + let _: crate::bindings::LNOffer = offer; + } crate::bindings::InputType::NodeId { node_id } => { let _: String = node_id; } @@ -1841,6 +1856,16 @@ const _: fn() = || { let _: Vec = LNInvoice.payment_secret; let _: u64 = LNInvoice.min_final_cltv_expiry_delta; } + { + let LNOffer = None::.unwrap(); + let _: String = LNOffer.offer; + let _: Vec = LNOffer.chains; + let _: Option = LNOffer.min_amount; + let _: Option = LNOffer.description; + let _: Option = LNOffer.absolute_expiry; + let _: Option = LNOffer.issuer; + let _: Option = LNOffer.signing_pubkey; + } { let LnUrlAuthRequestData = None::.unwrap(); let _: String = LnUrlAuthRequestData.k1; @@ -2182,6 +2207,32 @@ impl SseDecode for crate::bindings::AesSuccessActionDataResult { } } +impl SseDecode for crate::bindings::Amount { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut tag_ = ::sse_decode(deserializer); + match tag_ { + 0 => { + let mut var_amountMsat = ::sse_decode(deserializer); + return crate::bindings::Amount::Bitcoin { + amount_msat: var_amountMsat, + }; + } + 1 => { + let mut var_iso4217Code = ::sse_decode(deserializer); + let mut var_fractionalAmount = ::sse_decode(deserializer); + return crate::bindings::Amount::Currency { + iso4217_code: var_iso4217Code, + fractional_amount: var_fractionalAmount, + }; + } + _ => { + unimplemented!(""); + } + } + } +} + impl SseDecode for crate::model::BackupRequest { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -2432,30 +2483,34 @@ impl SseDecode for crate::bindings::InputType { }; } 3 => { + let mut var_offer = ::sse_decode(deserializer); + return crate::bindings::InputType::Bolt12Offer { offer: var_offer }; + } + 4 => { let mut var_nodeId = ::sse_decode(deserializer); return crate::bindings::InputType::NodeId { node_id: var_nodeId, }; } - 4 => { + 5 => { let mut var_url = ::sse_decode(deserializer); return crate::bindings::InputType::Url { url: var_url }; } - 5 => { + 6 => { let mut var_data = ::sse_decode(deserializer); return crate::bindings::InputType::LnUrlPay { data: var_data }; } - 6 => { + 7 => { let mut var_data = ::sse_decode(deserializer); return crate::bindings::InputType::LnUrlWithdraw { data: var_data }; } - 7 => { + 8 => { let mut var_data = ::sse_decode(deserializer); return crate::bindings::InputType::LnUrlAuth { data: var_data }; } - 8 => { + 9 => { let mut var_data = ::sse_decode(deserializer); return crate::bindings::InputType::LnUrlError { data: var_data }; } @@ -2524,6 +2579,18 @@ impl SseDecode for crate::model::LiquidNetwork { } } +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -2720,6 +2787,28 @@ impl SseDecode for crate::bindings::LNInvoice { } } +impl SseDecode for crate::bindings::LNOffer { + // 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 = ::sse_decode(deserializer); + let mut var_chains = >::sse_decode(deserializer); + let mut var_minAmount = >::sse_decode(deserializer); + let mut var_description = >::sse_decode(deserializer); + let mut var_absoluteExpiry = >::sse_decode(deserializer); + let mut var_issuer = >::sse_decode(deserializer); + let mut var_signingPubkey = >::sse_decode(deserializer); + return crate::bindings::LNOffer { + offer: var_offer, + chains: var_chains, + min_amount: var_minAmount, + description: var_description, + absolute_expiry: var_absoluteExpiry, + issuer: var_issuer, + signing_pubkey: var_signingPubkey, + }; + } +} + impl SseDecode for crate::bindings::duplicates::LnUrlAuthError { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -3147,6 +3236,17 @@ impl SseDecode for Option { } } +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + impl SseDecode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -3898,6 +3998,14 @@ impl SseDecode for crate::model::SendDestination { invoice: var_invoice, }; } + 2 => { + let mut var_offer = ::sse_decode(deserializer); + let mut var_receiverAmountSat = ::sse_decode(deserializer); + return crate::model::SendDestination::Bolt12 { + offer: var_offer, + receiver_amount_sat: var_receiverAmountSat, + }; + } _ => { unimplemented!(""); } @@ -4182,6 +4290,39 @@ impl flutter_rust_bridge::IntoIntoDart { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self.0 { + crate::bindings::Amount::Bitcoin { amount_msat } => { + [0.into_dart(), amount_msat.into_into_dart().into_dart()].into_dart() + } + crate::bindings::Amount::Currency { + iso4217_code, + fractional_amount, + } => [ + 1.into_dart(), + iso4217_code.into_into_dart().into_dart(), + fractional_amount.into_into_dart().into_dart(), + ] + .into_dart(), + _ => { + unimplemented!(""); + } + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::bindings::Amount +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::model::BackupRequest { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [self.backup_path.into_into_dart().into_dart()].into_dart() @@ -4464,23 +4605,26 @@ impl flutter_rust_bridge::IntoDart for FrbWrapper { crate::bindings::InputType::Bolt11 { invoice } => { [2.into_dart(), invoice.into_into_dart().into_dart()].into_dart() } + crate::bindings::InputType::Bolt12Offer { offer } => { + [3.into_dart(), offer.into_into_dart().into_dart()].into_dart() + } crate::bindings::InputType::NodeId { node_id } => { - [3.into_dart(), node_id.into_into_dart().into_dart()].into_dart() + [4.into_dart(), node_id.into_into_dart().into_dart()].into_dart() } crate::bindings::InputType::Url { url } => { - [4.into_dart(), url.into_into_dart().into_dart()].into_dart() + [5.into_dart(), url.into_into_dart().into_dart()].into_dart() } crate::bindings::InputType::LnUrlPay { data } => { - [5.into_dart(), data.into_into_dart().into_dart()].into_dart() + [6.into_dart(), data.into_into_dart().into_dart()].into_dart() } crate::bindings::InputType::LnUrlWithdraw { data } => { - [6.into_dart(), data.into_into_dart().into_dart()].into_dart() + [7.into_dart(), data.into_into_dart().into_dart()].into_dart() } crate::bindings::InputType::LnUrlAuth { data } => { - [7.into_dart(), data.into_into_dart().into_dart()].into_dart() + [8.into_dart(), data.into_into_dart().into_dart()].into_dart() } crate::bindings::InputType::LnUrlError { data } => { - [8.into_dart(), data.into_into_dart().into_dart()].into_dart() + [9.into_dart(), data.into_into_dart().into_dart()].into_dart() } _ => { unimplemented!(""); @@ -4667,6 +4811,32 @@ impl flutter_rust_bridge::IntoIntoDart> } } // Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.0.offer.into_into_dart().into_dart(), + self.0.chains.into_into_dart().into_dart(), + self.0.min_amount.into_into_dart().into_dart(), + self.0.description.into_into_dart().into_dart(), + self.0.absolute_expiry.into_into_dart().into_dart(), + self.0.issuer.into_into_dart().into_dart(), + self.0.signing_pubkey.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} +impl flutter_rust_bridge::IntoIntoDart> + for crate::bindings::LNOffer +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::bindings::duplicates::LnUrlAuthError { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { match self { @@ -5941,6 +6111,15 @@ impl flutter_rust_bridge::IntoDart for crate::model::SendDestination { crate::model::SendDestination::Bolt11 { invoice } => { [1.into_dart(), invoice.into_into_dart().into_dart()].into_dart() } + crate::model::SendDestination::Bolt12 { + offer, + receiver_amount_sat, + } => [ + 2.into_dart(), + offer.into_into_dart().into_dart(), + receiver_amount_sat.into_into_dart().into_dart(), + ] + .into_dart(), _ => { unimplemented!(""); } @@ -6215,6 +6394,29 @@ impl SseEncode for crate::bindings::AesSuccessActionDataResult { } } +impl SseEncode for crate::bindings::Amount { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + match self { + crate::bindings::Amount::Bitcoin { amount_msat } => { + ::sse_encode(0, serializer); + ::sse_encode(amount_msat, serializer); + } + crate::bindings::Amount::Currency { + iso4217_code, + fractional_amount, + } => { + ::sse_encode(1, serializer); + ::sse_encode(iso4217_code, serializer); + ::sse_encode(fractional_amount, serializer); + } + _ => { + unimplemented!(""); + } + } + } +} + impl SseEncode for crate::model::BackupRequest { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -6393,28 +6595,32 @@ impl SseEncode for crate::bindings::InputType { ::sse_encode(2, serializer); ::sse_encode(invoice, serializer); } - crate::bindings::InputType::NodeId { node_id } => { + crate::bindings::InputType::Bolt12Offer { offer } => { ::sse_encode(3, serializer); + ::sse_encode(offer, serializer); + } + crate::bindings::InputType::NodeId { node_id } => { + ::sse_encode(4, serializer); ::sse_encode(node_id, serializer); } crate::bindings::InputType::Url { url } => { - ::sse_encode(4, serializer); + ::sse_encode(5, serializer); ::sse_encode(url, serializer); } crate::bindings::InputType::LnUrlPay { data } => { - ::sse_encode(5, serializer); + ::sse_encode(6, serializer); ::sse_encode(data, serializer); } crate::bindings::InputType::LnUrlWithdraw { data } => { - ::sse_encode(6, serializer); + ::sse_encode(7, serializer); ::sse_encode(data, serializer); } crate::bindings::InputType::LnUrlAuth { data } => { - ::sse_encode(7, serializer); + ::sse_encode(8, serializer); ::sse_encode(data, serializer); } crate::bindings::InputType::LnUrlError { data } => { - ::sse_encode(8, serializer); + ::sse_encode(9, serializer); ::sse_encode(data, serializer); } _ => { @@ -6469,6 +6675,16 @@ impl SseEncode for crate::model::LiquidNetwork { } } +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + impl SseEncode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -6618,6 +6834,19 @@ impl SseEncode for crate::bindings::LNInvoice { } } +impl SseEncode for crate::bindings::LNOffer { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.offer, serializer); + >::sse_encode(self.chains, serializer); + >::sse_encode(self.min_amount, serializer); + >::sse_encode(self.description, serializer); + >::sse_encode(self.absolute_expiry, serializer); + >::sse_encode(self.issuer, serializer); + >::sse_encode(self.signing_pubkey, serializer); + } +} + impl SseEncode for crate::bindings::duplicates::LnUrlAuthError { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -6955,6 +7184,16 @@ impl SseEncode for Option { } } +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + impl SseEncode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -7559,6 +7798,14 @@ impl SseEncode for crate::model::SendDestination { ::sse_encode(1, serializer); ::sse_encode(invoice, serializer); } + crate::model::SendDestination::Bolt12 { + offer, + receiver_amount_sat, + } => { + ::sse_encode(2, serializer); + ::sse_encode(offer, serializer); + ::sse_encode(receiver_amount_sat, serializer); + } _ => { unimplemented!(""); } @@ -7833,6 +8080,27 @@ mod io { } } } + impl CstDecode for wire_cst_amount { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::bindings::Amount { + match self.tag { + 0 => { + let ans = unsafe { self.kind.Bitcoin }; + crate::bindings::Amount::Bitcoin { + amount_msat: ans.amount_msat.cst_decode(), + } + } + 1 => { + let ans = unsafe { self.kind.Currency }; + crate::bindings::Amount::Currency { + iso4217_code: ans.iso4217_code.cst_decode(), + fractional_amount: ans.fractional_amount.cst_decode(), + } + } + _ => unreachable!(), + } + } + } impl CstDecode for wire_cst_backup_request { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::model::BackupRequest { @@ -7886,6 +8154,13 @@ mod io { CstDecode::::cst_decode(*wrap).into() } } + impl CstDecode for *mut wire_cst_amount { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::bindings::Amount { + let wrap = unsafe { flutter_rust_bridge::for_generated::box_from_leak_ptr(self) }; + CstDecode::::cst_decode(*wrap).into() + } + } impl CstDecode for *mut wire_cst_backup_request { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::model::BackupRequest { @@ -7975,6 +8250,13 @@ mod io { CstDecode::::cst_decode(*wrap).into() } } + impl CstDecode for *mut wire_cst_ln_offer { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::bindings::LNOffer { + let wrap = unsafe { flutter_rust_bridge::for_generated::box_from_leak_ptr(self) }; + CstDecode::::cst_decode(*wrap).into() + } + } impl CstDecode for *mut wire_cst_ln_url_auth_request_data { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::bindings::LnUrlAuthRequestData { @@ -8325,36 +8607,42 @@ mod io { } } 3 => { + let ans = unsafe { self.kind.Bolt12Offer }; + crate::bindings::InputType::Bolt12Offer { + offer: ans.offer.cst_decode(), + } + } + 4 => { let ans = unsafe { self.kind.NodeId }; crate::bindings::InputType::NodeId { node_id: ans.node_id.cst_decode(), } } - 4 => { + 5 => { let ans = unsafe { self.kind.Url }; crate::bindings::InputType::Url { url: ans.url.cst_decode(), } } - 5 => { + 6 => { let ans = unsafe { self.kind.LnUrlPay }; crate::bindings::InputType::LnUrlPay { data: ans.data.cst_decode(), } } - 6 => { + 7 => { let ans = unsafe { self.kind.LnUrlWithdraw }; crate::bindings::InputType::LnUrlWithdraw { data: ans.data.cst_decode(), } } - 7 => { + 8 => { let ans = unsafe { self.kind.LnUrlAuth }; crate::bindings::InputType::LnUrlAuth { data: ans.data.cst_decode(), } } - 8 => { + 9 => { let ans = unsafe { self.kind.LnUrlError }; crate::bindings::InputType::LnUrlError { data: ans.data.cst_decode(), @@ -8398,6 +8686,16 @@ mod io { } } } + impl CstDecode> for *mut wire_cst_list_String { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> Vec { + let vec = unsafe { + let wrap = flutter_rust_bridge::for_generated::box_from_leak_ptr(self); + flutter_rust_bridge::for_generated::vec_from_leak_ptr(wrap.ptr, wrap.len) + }; + vec.into_iter().map(CstDecode::cst_decode).collect() + } + } impl CstDecode> for *mut wire_cst_list_fiat_currency { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> Vec { @@ -8549,6 +8847,20 @@ mod io { } } } + impl CstDecode for wire_cst_ln_offer { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::bindings::LNOffer { + crate::bindings::LNOffer { + offer: self.offer.cst_decode(), + chains: self.chains.cst_decode(), + min_amount: self.min_amount.cst_decode(), + description: self.description.cst_decode(), + absolute_expiry: self.absolute_expiry.cst_decode(), + issuer: self.issuer.cst_decode(), + signing_pubkey: self.signing_pubkey.cst_decode(), + } + } + } impl CstDecode for wire_cst_ln_url_auth_error { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> crate::bindings::duplicates::LnUrlAuthError { @@ -9358,6 +9670,13 @@ mod io { invoice: ans.invoice.cst_decode(), } } + 2 => { + let ans = unsafe { self.kind.Bolt12 }; + crate::model::SendDestination::Bolt12 { + offer: ans.offer.cst_decode(), + receiver_amount_sat: ans.receiver_amount_sat.cst_decode(), + } + } _ => unreachable!(), } } @@ -9507,6 +9826,19 @@ mod io { Self::new_with_null_ptr() } } + impl NewWithNullPtr for wire_cst_amount { + fn new_with_null_ptr() -> Self { + Self { + tag: -1, + kind: AmountKind { nil__: () }, + } + } + } + impl Default for wire_cst_amount { + fn default() -> Self { + Self::new_with_null_ptr() + } + } impl NewWithNullPtr for wire_cst_backup_request { fn new_with_null_ptr() -> Self { Self { @@ -9789,6 +10121,24 @@ mod io { Self::new_with_null_ptr() } } + impl NewWithNullPtr for wire_cst_ln_offer { + fn new_with_null_ptr() -> Self { + Self { + offer: core::ptr::null_mut(), + chains: core::ptr::null_mut(), + min_amount: core::ptr::null_mut(), + description: core::ptr::null_mut(), + absolute_expiry: core::ptr::null_mut(), + issuer: core::ptr::null_mut(), + signing_pubkey: core::ptr::null_mut(), + } + } + } + impl Default for wire_cst_ln_offer { + fn default() -> Self { + Self::new_with_null_ptr() + } + } impl NewWithNullPtr for wire_cst_ln_url_auth_error { fn new_with_null_ptr() -> Self { Self { @@ -10953,6 +11303,11 @@ mod io { ) } + #[no_mangle] + pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_amount() -> *mut wire_cst_amount { + flutter_rust_bridge::for_generated::new_leak_box_ptr(wire_cst_amount::new_with_null_ptr()) + } + #[no_mangle] pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_backup_request( ) -> *mut wire_cst_backup_request { @@ -11051,6 +11406,11 @@ mod io { ) } + #[no_mangle] + pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_ln_offer() -> *mut wire_cst_ln_offer { + flutter_rust_bridge::for_generated::new_leak_box_ptr(wire_cst_ln_offer::new_with_null_ptr()) + } + #[no_mangle] pub extern "C" fn frbgen_breez_liquid_cst_new_box_autoadd_ln_url_auth_request_data( ) -> *mut wire_cst_ln_url_auth_request_data { @@ -11285,6 +11645,20 @@ mod io { ) } + #[no_mangle] + pub extern "C" fn frbgen_breez_liquid_cst_new_list_String( + len: i32, + ) -> *mut wire_cst_list_String { + let wrap = wire_cst_list_String { + ptr: flutter_rust_bridge::for_generated::new_leak_vec_ptr( + <*mut wire_cst_list_prim_u_8_strict>::new_with_null_ptr(), + len, + ), + len, + }; + flutter_rust_bridge::for_generated::new_leak_box_ptr(wrap) + } + #[no_mangle] pub extern "C" fn frbgen_breez_liquid_cst_new_list_fiat_currency( len: i32, @@ -11455,6 +11829,30 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_amount { + tag: i32, + kind: AmountKind, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub union AmountKind { + Bitcoin: wire_cst_Amount_Bitcoin, + Currency: wire_cst_Amount_Currency, + nil__: (), + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_Amount_Bitcoin { + amount_msat: u64, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct wire_cst_Amount_Currency { + iso4217_code: *mut wire_cst_list_prim_u_8_strict, + fractional_amount: u64, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_backup_request { backup_path: *mut wire_cst_list_prim_u_8_strict, } @@ -11564,6 +11962,7 @@ mod io { BitcoinAddress: wire_cst_InputType_BitcoinAddress, LiquidAddress: wire_cst_InputType_LiquidAddress, Bolt11: wire_cst_InputType_Bolt11, + Bolt12Offer: wire_cst_InputType_Bolt12Offer, NodeId: wire_cst_InputType_NodeId, Url: wire_cst_InputType_Url, LnUrlPay: wire_cst_InputType_LnUrlPay, @@ -11589,6 +11988,11 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_InputType_Bolt12Offer { + offer: *mut wire_cst_ln_offer, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_InputType_NodeId { node_id: *mut wire_cst_list_prim_u_8_strict, } @@ -11642,6 +12046,12 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_list_String { + ptr: *mut *mut wire_cst_list_prim_u_8_strict, + len: i32, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_list_fiat_currency { ptr: *mut wire_cst_fiat_currency, len: i32, @@ -11751,6 +12161,17 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_ln_offer { + offer: *mut wire_cst_list_prim_u_8_strict, + chains: *mut wire_cst_list_String, + min_amount: *mut wire_cst_amount, + description: *mut wire_cst_list_prim_u_8_strict, + absolute_expiry: *mut u64, + issuer: *mut wire_cst_list_prim_u_8_strict, + signing_pubkey: *mut wire_cst_list_prim_u_8_strict, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_ln_url_auth_error { tag: i32, kind: LnUrlAuthErrorKind, @@ -12445,6 +12866,7 @@ mod io { pub union SendDestinationKind { LiquidAddress: wire_cst_SendDestination_LiquidAddress, Bolt11: wire_cst_SendDestination_Bolt11, + Bolt12: wire_cst_SendDestination_Bolt12, nil__: (), } #[repr(C)] @@ -12459,6 +12881,12 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_SendDestination_Bolt12 { + offer: *mut wire_cst_ln_offer, + receiver_amount_sat: u64, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_send_payment_request { prepare_response: wire_cst_prepare_send_response, } diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index 5abe63f8..03353141 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -345,7 +345,7 @@ pub struct OnchainPaymentLimitsResponse { #[derive(Debug, Serialize, Clone)] pub struct PrepareSendRequest { /// The destination we intend to pay to. - /// Supports BIP21 URIs, BOLT11 invoices and Liquid addresses + /// Supports BIP21 URIs, BOLT11 invoices, BOLT12 offers and Liquid addresses pub destination: String, /// Should only be set when paying directly onchain or to a BIP21 URI @@ -362,6 +362,10 @@ pub enum SendDestination { Bolt11 { invoice: LNInvoice, }, + Bolt12 { + offer: LNOffer, + receiver_amount_sat: u64, + }, } /// Returned when calling [crate::sdk::LiquidSdk::prepare_send_payment]. diff --git a/lib/core/src/persist/address.rs b/lib/core/src/persist/address.rs index 8c0c4672..4e66b51d 100644 --- a/lib/core/src/persist/address.rs +++ b/lib/core/src/persist/address.rs @@ -108,7 +108,7 @@ mod tests { let (_temp_dir, storage) = new_persister()?; let address = "tlq1pq2amlulhea6ltq7x3eu9atsc2nnrer7yt7xve363zxedqwu2mk6ctcyv9awl8xf28cythreqklt5q0qqwsxzlm6wu4z6d574adl9zh2zmr0h85gt534n"; - storage.insert_or_update_reserved_address(&address, 100)?; + storage.insert_or_update_reserved_address(address, 100)?; let maybe_reserved_address = storage.next_expired_reserved_address(99)?; // Under the expiry, not popped @@ -134,13 +134,13 @@ mod tests { let (_temp_dir, storage) = new_persister()?; let address = "tlq1pq2amlulhea6ltq7x3eu9atsc2nnrer7yt7xve363zxedqwu2mk6ctcyv9awl8xf28cythreqklt5q0qqwsxzlm6wu4z6d574adl9zh2zmr0h85gt534n"; - storage.insert_or_update_reserved_address(&address, 100)?; + storage.insert_or_update_reserved_address(address, 100)?; let maybe_reserved_address = storage.next_expired_reserved_address(99)?; // Under the expiry, not popped assert!(maybe_reserved_address.is_none()); - storage.delete_reserved_address(&address)?; + storage.delete_reserved_address(address)?; let maybe_reserved_address = storage.next_expired_reserved_address(101)?; // Over the expired, but already deleted diff --git a/lib/core/src/persist/mod.rs b/lib/core/src/persist/mod.rs index 3a68af63..2971471b 100644 --- a/lib/core/src/persist/mod.rs +++ b/lib/core/src/persist/mod.rs @@ -312,11 +312,8 @@ impl Persister { preimage: maybe_send_swap_preimage, bolt11: maybe_send_swap_invoice.clone(), payment_hash: maybe_send_swap_payment_hash, - description: maybe_send_swap_description.unwrap_or_else(|| { - maybe_send_swap_invoice - .and_then(|bolt11| get_invoice_description!(bolt11)) - .unwrap_or("Lightning payment".to_string()) - }), + description: maybe_send_swap_description + .unwrap_or("Lightning payment".to_string()), payer_amount_sat: maybe_send_swap_payer_amount_sat.unwrap_or(0), receiver_amount_sat: maybe_send_swap_receiver_amount_sat.unwrap_or(0), refund_tx_id: maybe_send_swap_refund_tx_id, diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index c55ba6d7..7cd79724 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -1,3 +1,7 @@ +use std::collections::HashMap; +use std::time::Instant; +use std::{fs, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; + use anyhow::{anyhow, Result}; use boltz_client::{swaps::boltz::*, util::secrets::Preimage}; use buy::{BuyBitcoinApi, BuyBitcoinService}; @@ -19,9 +23,6 @@ use sdk_common::input_parser::InputType; use sdk_common::liquid::LiquidAddressData; use sdk_common::prelude::{FiatAPI, FiatCurrency, LnUrlPayError, LnUrlWithdrawError, Rate}; use signer::SdkSigner; -use std::collections::HashMap; -use std::time::Instant; -use std::{fs, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; use tokio::sync::{watch, Mutex, RwLock}; use tokio::time::MissedTickBehavior; use tokio_stream::wrappers::BroadcastStream; @@ -45,6 +46,7 @@ use crate::{ persist::Persister, utils, *, }; +use ::lightning::offers::offer::Offer; pub const DEFAULT_DATA_DIR: &str = ".data"; /// Number of blocks to monitor a swap after its timeout block height @@ -745,7 +747,7 @@ impl LiquidSdk { /// # Arguments /// /// * `req` - the [PrepareSendRequest] containing: - /// * `destination` - Either a Liquid BIP21 URI/address or a BOLT11 invoice + /// * `destination` - Either a Liquid BIP21 URI/address, a BOLT11 invoice or a BOLT12 offer /// * `amount` - The optional amount of type [PayAmount]. Should only be specified /// when paying directly onchain or via amount-less BIP21. /// - [PayAmount::Drain] which uses all funds @@ -766,10 +768,10 @@ impl LiquidSdk { let receiver_amount_sat; let payment_destination; - match sdk_common::input_parser::parse(&req.destination).await? { - InputType::LiquidAddress { + match Self::parse(&req.destination).await { + Ok(InputType::LiquidAddress { address: mut liquid_address_data, - } => { + }) => { let amount = match (liquid_address_data.amount_sat, req.amount.clone()) { (None, None) => { return Err(PaymentError::AmountMissing { @@ -825,13 +827,13 @@ impl LiquidSdk { address_data: liquid_address_data, }; } - InputType::Bolt11 { invoice } => { + Ok(InputType::Bolt11 { invoice }) => { self.ensure_send_is_not_self_transfer(&invoice.bolt11)?; self.validate_invoice(&invoice.bolt11)?; - receiver_amount_sat = invoice.amount_msat.ok_or(PaymentError::AmountMissing { - err: "Expected invoice with an amount".to_string(), - })? / 1000; + receiver_amount_sat = invoice.amount_msat.ok_or(PaymentError::amount_missing( + "Expected invoice with an amount", + ))? / 1000; if let Some(PayAmount::Receiver { amount_sat }) = req.amount { ensure_sdk!( @@ -860,10 +862,29 @@ impl LiquidSdk { }; payment_destination = SendDestination::Bolt11 { invoice }; } + Ok(InputType::Bolt12Offer { offer }) => { + receiver_amount_sat = match req.amount { + Some(PayAmount::Receiver { amount_sat }) => Ok(amount_sat), + _ => Err(PaymentError::amount_missing( + "Expected PayAmount of type Receiver when processing a Bolt12 offer", + )), + }?; + + let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat)?; + + let boltz_fees_total = lbtc_pair.fees.total(receiver_amount_sat); + let lockup_fees_sat = self + .estimate_lockup_tx_or_drain_tx_fee(receiver_amount_sat + boltz_fees_total) + .await?; + fees_sat = boltz_fees_total + lockup_fees_sat; + + payment_destination = SendDestination::Bolt12 { + offer, + receiver_amount_sat, + }; + } _ => { - return Err(PaymentError::Generic { - err: "Destination is not valid".to_string(), - }); + return Err(PaymentError::generic("Destination is not valid")); } }; @@ -941,18 +962,27 @@ impl LiquidSdk { .await } SendDestination::Bolt11 { invoice } => { - self.pay_invoice(&invoice.bolt11, *fees_sat).await + self.pay_bolt11_invoice(&invoice.bolt11, *fees_sat).await + } + SendDestination::Bolt12 { + offer, + receiver_amount_sat, + } => { + let bolt12_invoice = self + .swapper + .get_bolt12_invoice(&offer.offer, *receiver_amount_sat)?; + self.pay_bolt12_invoice(&bolt12_invoice, *fees_sat).await } } } - async fn pay_invoice( + async fn pay_bolt11_invoice( &self, invoice: &str, fees_sat: u64, ) -> Result { self.ensure_send_is_not_self_transfer(invoice)?; - self.validate_invoice(invoice)?; + let bolt11_invoice = self.validate_invoice(invoice)?; let amount_sat = get_invoice_amount!(invoice); let payer_amount_sat = amount_sat + fees_sat; @@ -961,6 +991,11 @@ impl LiquidSdk { PaymentError::InsufficientFunds ); + let description = match bolt11_invoice.description() { + Bolt11InvoiceDescription::Direct(msg) => Some(msg.to_string()), + Bolt11InvoiceDescription::Hash(_) => None, + }; + match self.swapper.check_for_mrh(invoice)? { // If we find a valid MRH, extract the BIP21 address and pay to it via onchain tx Some((address, _)) => { @@ -981,10 +1016,45 @@ impl LiquidSdk { } // If no MRH found, perform usual swap - None => self.send_payment_via_swap(invoice, fees_sat).await, + None => { + self.send_payment_via_swap( + invoice, + &bolt11_invoice.payment_hash().to_string(), + description, + amount_sat, + fees_sat, + ) + .await + } } } + async fn pay_bolt12_invoice( + &self, + invoice: &str, + fees_sat: u64, + ) -> Result { + // TODO Validate invoice + + let invoice_parsed = utils::parse_bolt12_invoice(invoice)?; + + let amount_sat = invoice_parsed.amount_msats() / 1_000; + let payer_amount_sat = amount_sat + fees_sat; + ensure_sdk!( + payer_amount_sat <= self.get_info().await?.balance_sat, + PaymentError::InsufficientFunds + ); + + self.send_payment_via_swap( + invoice, + &invoice_parsed.payment_hash().to_string(), + invoice_parsed.description().map(|desc| desc.to_string()), + amount_sat, + fees_sat, + ) + .await + } + /// Performs a Send Payment by doing an onchain tx to a L-BTC address async fn pay_liquid( &self, @@ -1047,20 +1117,11 @@ impl LiquidSdk { async fn send_payment_via_swap( &self, invoice: &str, + payment_hash: &str, + description: Option, + receiver_amount_sat: u64, fees_sat: u64, ) -> Result { - let bolt11_invoice = invoice - .trim() - .parse::() - .map_err(|err| PaymentError::invalid_invoice(&err.to_string()))?; - let receiver_amount_sat = - bolt11_invoice - .amount_milli_satoshis() - .ok_or(PaymentError::invalid_invoice( - "Invoice does not contain an amount", - ))? - / 1000; - let lbtc_pair = self.validate_submarine_pairs(receiver_amount_sat)?; let boltz_fees_total = lbtc_pair.fees.total(receiver_amount_sat); let user_lockup_amount_sat = receiver_amount_sat + boltz_fees_total; @@ -1118,17 +1179,12 @@ impl LiquidSdk { let swap_id = &create_response.id; let create_response_json = SendSwap::from_boltz_struct_to_json(&create_response, swap_id)?; - let payment_hash = bolt11_invoice.payment_hash().to_string(); - let description = match bolt11_invoice.description() { - Bolt11InvoiceDescription::Direct(msg) => Some(msg.to_string()), - Bolt11InvoiceDescription::Hash(_) => None, - }; let payer_amount_sat = fees_sat + receiver_amount_sat; let swap = SendSwap { id: swap_id.clone(), invoice: invoice.to_string(), - payment_hash: Some(payment_hash), + payment_hash: Some(payment_hash.to_string()), description, preimage: None, payer_amount_sat, @@ -2525,9 +2581,52 @@ impl LiquidSdk { /// Parses a string into an [InputType]. See [input_parser::parse]. pub async fn parse(input: &str) -> Result { + if let Ok(offer) = input.parse::() { + // TODO This conversion (between lightning-v0.0.125 to -v0.0.118 Amount types) + // won't be needed when Liquid SDK uses the same lightning crate version as sdk-common + let min_amount = offer + .amount() + .map(|amount| match amount { + ::lightning::offers::offer::Amount::Bitcoin { amount_msats } => { + Ok(Amount::Bitcoin { + amount_msat: amount_msats, + }) + } + ::lightning::offers::offer::Amount::Currency { + iso4217_code, + amount, + } => Ok(Amount::Currency { + iso4217_code: String::from_utf8(iso4217_code.to_vec()).map_err(|_| { + anyhow!("Expecting a valid ISO 4217 character sequence") + })?, + fractional_amount: amount, + }), + }) + .transpose() + .map_err(|e: anyhow::Error| { + PaymentError::generic(&format!("Failed to reconstruct amount: {e:?}")) + })?; + + return Ok(InputType::Bolt12Offer { + offer: LNOffer { + offer: input.to_string(), + chains: offer + .chains() + .iter() + .map(|chain| chain.to_string()) + .collect(), + min_amount, + description: offer.description().map(|d| d.to_string()), + absolute_expiry: offer.absolute_expiry().map(|expiry| expiry.as_secs()), + issuer: offer.issuer().map(|s| s.to_string()), + signing_pubkey: offer.signing_pubkey().map(|pk| pk.to_string()), + }, + }); + } + parse(input) .await - .map_err(|e| PaymentError::Generic { err: e.to_string() }) + .map_err(|e| PaymentError::generic(&e.to_string())) } /// Parses a string into an [LNInvoice]. See [invoice::parse_invoice]. diff --git a/lib/core/src/swapper/boltz/mod.rs b/lib/core/src/swapper/boltz/mod.rs index 6ee15ac9..4698d8cf 100644 --- a/lib/core/src/swapper/boltz/mod.rs +++ b/lib/core/src/swapper/boltz/mod.rs @@ -413,4 +413,10 @@ impl Swapper for BoltzSwapper { ) .map_err(Into::into) } + + fn get_bolt12_invoice(&self, offer: &str, amount_sat: u64) -> Result { + let invoice_res = self.client.get_bolt12_invoice(offer, amount_sat)?; + info!("Received BOLT12 invoice response: {invoice_res:?}"); + Ok(invoice_res.invoice) + } } diff --git a/lib/core/src/swapper/mod.rs b/lib/core/src/swapper/mod.rs index 49906941..bc8ab01f 100644 --- a/lib/core/src/swapper/mod.rs +++ b/lib/core/src/swapper/mod.rs @@ -104,6 +104,8 @@ pub trait Swapper: Send + Sync { &self, invoice: &str, ) -> Result, PaymentError>; + + fn get_bolt12_invoice(&self, offer: &str, amount_sat: u64) -> Result; } #[async_trait] diff --git a/lib/core/src/test_utils/swapper.rs b/lib/core/src/test_utils/swapper.rs index 599039be..e4ba678c 100644 --- a/lib/core/src/test_utils/swapper.rs +++ b/lib/core/src/test_utils/swapper.rs @@ -309,4 +309,8 @@ impl Swapper for MockSwapper { // Ok(Some(("".to_string(), 0.0))) unimplemented!() } + + fn get_bolt12_invoice(&self, _offer: &str, _amount_sat: u64) -> Result { + unimplemented!() + } } diff --git a/lib/core/src/utils.rs b/lib/core/src/utils.rs index 14e8e120..c10af5e7 100644 --- a/lib/core/src/utils.rs +++ b/lib/core/src/utils.rs @@ -2,13 +2,16 @@ use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; use crate::error::{PaymentError, SdkResult}; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, ensure, Result}; +use lightning::offers::invoice::Bolt12Invoice; use lwk_wollet::elements::encode::deserialize; use lwk_wollet::elements::hex::FromHex; use lwk_wollet::elements::{ LockTime::{self, *}, Transaction, }; +use sdk_common::bitcoin::bech32; +use sdk_common::bitcoin::bech32::FromBase32; pub(crate) fn now() -> u32 { SystemTime::now() @@ -49,3 +52,16 @@ pub(crate) fn deserialize_tx_hex(tx_hex: &str) -> Result { |err| anyhow!("Could not deserialize transaction: {err:?}"), )?)?) } + +/// 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 { + let (hrp, data) = bech32::decode_without_checksum(invoice)?; + ensure!(hrp.as_str() == "lni", "Invalid HRP"); + + let data = Vec::::from_base32(&data)?; + + lightning::offers::invoice::Bolt12Invoice::try_from(data) + .map_err(|e| anyhow!("Failed to parse BOLT12: {e:?}")) +} diff --git a/packages/dart/lib/src/bindings.dart b/packages/dart/lib/src/bindings.dart index c97737fb..23ac294e 100644 --- a/packages/dart/lib/src/bindings.dart +++ b/packages/dart/lib/src/bindings.dart @@ -157,6 +157,19 @@ sealed class AesSuccessActionDataResult with _$AesSuccessActionDataResult { }) = AesSuccessActionDataResult_ErrorStatus; } +@freezed +sealed class Amount with _$Amount { + const Amount._(); + + const factory Amount.bitcoin({ + required BigInt amountMsat, + }) = Amount_Bitcoin; + const factory Amount.currency({ + required String iso4217Code, + required BigInt fractionalAmount, + }) = Amount_Currency; +} + class BindingEventListener { final RustStreamSink stream; @@ -281,6 +294,9 @@ sealed class InputType with _$InputType { const factory InputType.bolt11({ required LNInvoice invoice, }) = InputType_Bolt11; + const factory InputType.bolt12Offer({ + required LNOffer offer, + }) = InputType_Bolt12Offer; const factory InputType.nodeId({ required String nodeId, }) = InputType_NodeId; @@ -403,6 +419,49 @@ class LNInvoice { minFinalCltvExpiryDelta == other.minFinalCltvExpiryDelta; } +class LNOffer { + final String offer; + final List chains; + final Amount? minAmount; + final String? description; + final BigInt? absoluteExpiry; + final String? issuer; + final String? signingPubkey; + + const LNOffer({ + required this.offer, + required this.chains, + this.minAmount, + this.description, + this.absoluteExpiry, + this.issuer, + this.signingPubkey, + }); + + @override + int get hashCode => + offer.hashCode ^ + chains.hashCode ^ + minAmount.hashCode ^ + description.hashCode ^ + absoluteExpiry.hashCode ^ + issuer.hashCode ^ + signingPubkey.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is LNOffer && + runtimeType == other.runtimeType && + offer == other.offer && + chains == other.chains && + minAmount == other.minAmount && + description == other.description && + absoluteExpiry == other.absoluteExpiry && + issuer == other.issuer && + signingPubkey == other.signingPubkey; +} + class LnUrlAuthRequestData { final String k1; final String? action; diff --git a/packages/dart/lib/src/bindings.freezed.dart b/packages/dart/lib/src/bindings.freezed.dart index bb26c07c..cf786c06 100644 --- a/packages/dart/lib/src/bindings.freezed.dart +++ b/packages/dart/lib/src/bindings.freezed.dart @@ -203,6 +203,191 @@ abstract class AesSuccessActionDataResult_ErrorStatus extends AesSuccessActionDa get copyWith => throw _privateConstructorUsedError; } +/// @nodoc +mixin _$Amount {} + +/// @nodoc +abstract class $AmountCopyWith<$Res> { + factory $AmountCopyWith(Amount value, $Res Function(Amount) then) = _$AmountCopyWithImpl<$Res, Amount>; +} + +/// @nodoc +class _$AmountCopyWithImpl<$Res, $Val extends Amount> implements $AmountCopyWith<$Res> { + _$AmountCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Amount + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$Amount_BitcoinImplCopyWith<$Res> { + factory _$$Amount_BitcoinImplCopyWith( + _$Amount_BitcoinImpl value, $Res Function(_$Amount_BitcoinImpl) then) = + __$$Amount_BitcoinImplCopyWithImpl<$Res>; + @useResult + $Res call({BigInt amountMsat}); +} + +/// @nodoc +class __$$Amount_BitcoinImplCopyWithImpl<$Res> extends _$AmountCopyWithImpl<$Res, _$Amount_BitcoinImpl> + implements _$$Amount_BitcoinImplCopyWith<$Res> { + __$$Amount_BitcoinImplCopyWithImpl(_$Amount_BitcoinImpl _value, $Res Function(_$Amount_BitcoinImpl) _then) + : super(_value, _then); + + /// Create a copy of Amount + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? amountMsat = null, + }) { + return _then(_$Amount_BitcoinImpl( + amountMsat: null == amountMsat + ? _value.amountMsat + : amountMsat // ignore: cast_nullable_to_non_nullable + as BigInt, + )); + } +} + +/// @nodoc + +class _$Amount_BitcoinImpl extends Amount_Bitcoin { + const _$Amount_BitcoinImpl({required this.amountMsat}) : super._(); + + @override + final BigInt amountMsat; + + @override + String toString() { + return 'Amount.bitcoin(amountMsat: $amountMsat)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$Amount_BitcoinImpl && + (identical(other.amountMsat, amountMsat) || other.amountMsat == amountMsat)); + } + + @override + int get hashCode => Object.hash(runtimeType, amountMsat); + + /// Create a copy of Amount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$Amount_BitcoinImplCopyWith<_$Amount_BitcoinImpl> get copyWith => + __$$Amount_BitcoinImplCopyWithImpl<_$Amount_BitcoinImpl>(this, _$identity); +} + +abstract class Amount_Bitcoin extends Amount { + const factory Amount_Bitcoin({required final BigInt amountMsat}) = _$Amount_BitcoinImpl; + const Amount_Bitcoin._() : super._(); + + BigInt get amountMsat; + + /// Create a copy of Amount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$Amount_BitcoinImplCopyWith<_$Amount_BitcoinImpl> get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$Amount_CurrencyImplCopyWith<$Res> { + factory _$$Amount_CurrencyImplCopyWith( + _$Amount_CurrencyImpl value, $Res Function(_$Amount_CurrencyImpl) then) = + __$$Amount_CurrencyImplCopyWithImpl<$Res>; + @useResult + $Res call({String iso4217Code, BigInt fractionalAmount}); +} + +/// @nodoc +class __$$Amount_CurrencyImplCopyWithImpl<$Res> extends _$AmountCopyWithImpl<$Res, _$Amount_CurrencyImpl> + implements _$$Amount_CurrencyImplCopyWith<$Res> { + __$$Amount_CurrencyImplCopyWithImpl( + _$Amount_CurrencyImpl _value, $Res Function(_$Amount_CurrencyImpl) _then) + : super(_value, _then); + + /// Create a copy of Amount + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? iso4217Code = null, + Object? fractionalAmount = null, + }) { + return _then(_$Amount_CurrencyImpl( + iso4217Code: null == iso4217Code + ? _value.iso4217Code + : iso4217Code // ignore: cast_nullable_to_non_nullable + as String, + fractionalAmount: null == fractionalAmount + ? _value.fractionalAmount + : fractionalAmount // ignore: cast_nullable_to_non_nullable + as BigInt, + )); + } +} + +/// @nodoc + +class _$Amount_CurrencyImpl extends Amount_Currency { + const _$Amount_CurrencyImpl({required this.iso4217Code, required this.fractionalAmount}) : super._(); + + @override + final String iso4217Code; + @override + final BigInt fractionalAmount; + + @override + String toString() { + return 'Amount.currency(iso4217Code: $iso4217Code, fractionalAmount: $fractionalAmount)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$Amount_CurrencyImpl && + (identical(other.iso4217Code, iso4217Code) || other.iso4217Code == iso4217Code) && + (identical(other.fractionalAmount, fractionalAmount) || + other.fractionalAmount == fractionalAmount)); + } + + @override + int get hashCode => Object.hash(runtimeType, iso4217Code, fractionalAmount); + + /// Create a copy of Amount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$Amount_CurrencyImplCopyWith<_$Amount_CurrencyImpl> get copyWith => + __$$Amount_CurrencyImplCopyWithImpl<_$Amount_CurrencyImpl>(this, _$identity); +} + +abstract class Amount_Currency extends Amount { + const factory Amount_Currency({required final String iso4217Code, required final BigInt fractionalAmount}) = + _$Amount_CurrencyImpl; + const Amount_Currency._() : super._(); + + String get iso4217Code; + BigInt get fractionalAmount; + + /// Create a copy of Amount + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$Amount_CurrencyImplCopyWith<_$Amount_CurrencyImpl> get copyWith => throw _privateConstructorUsedError; +} + /// @nodoc mixin _$InputType {} @@ -462,6 +647,85 @@ abstract class InputType_Bolt11 extends InputType { _$$InputType_Bolt11ImplCopyWith<_$InputType_Bolt11Impl> get copyWith => throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$InputType_Bolt12OfferImplCopyWith<$Res> { + factory _$$InputType_Bolt12OfferImplCopyWith( + _$InputType_Bolt12OfferImpl value, $Res Function(_$InputType_Bolt12OfferImpl) then) = + __$$InputType_Bolt12OfferImplCopyWithImpl<$Res>; + @useResult + $Res call({LNOffer offer}); +} + +/// @nodoc +class __$$InputType_Bolt12OfferImplCopyWithImpl<$Res> + extends _$InputTypeCopyWithImpl<$Res, _$InputType_Bolt12OfferImpl> + implements _$$InputType_Bolt12OfferImplCopyWith<$Res> { + __$$InputType_Bolt12OfferImplCopyWithImpl( + _$InputType_Bolt12OfferImpl _value, $Res Function(_$InputType_Bolt12OfferImpl) _then) + : super(_value, _then); + + /// Create a copy of InputType + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? offer = null, + }) { + return _then(_$InputType_Bolt12OfferImpl( + offer: null == offer + ? _value.offer + : offer // ignore: cast_nullable_to_non_nullable + as LNOffer, + )); + } +} + +/// @nodoc + +class _$InputType_Bolt12OfferImpl extends InputType_Bolt12Offer { + const _$InputType_Bolt12OfferImpl({required this.offer}) : super._(); + + @override + final LNOffer offer; + + @override + String toString() { + return 'InputType.bolt12Offer(offer: $offer)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$InputType_Bolt12OfferImpl && + (identical(other.offer, offer) || other.offer == offer)); + } + + @override + int get hashCode => Object.hash(runtimeType, offer); + + /// Create a copy of InputType + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$InputType_Bolt12OfferImplCopyWith<_$InputType_Bolt12OfferImpl> get copyWith => + __$$InputType_Bolt12OfferImplCopyWithImpl<_$InputType_Bolt12OfferImpl>(this, _$identity); +} + +abstract class InputType_Bolt12Offer extends InputType { + const factory InputType_Bolt12Offer({required final LNOffer offer}) = _$InputType_Bolt12OfferImpl; + const InputType_Bolt12Offer._() : super._(); + + LNOffer get offer; + + /// Create a copy of InputType + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$InputType_Bolt12OfferImplCopyWith<_$InputType_Bolt12OfferImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc abstract class _$$InputType_NodeIdImplCopyWith<$Res> { factory _$$InputType_NodeIdImplCopyWith( diff --git a/packages/dart/lib/src/frb_generated.dart b/packages/dart/lib/src/frb_generated.dart index 27733df6..50e794d0 100644 --- a/packages/dart/lib/src/frb_generated.dart +++ b/packages/dart/lib/src/frb_generated.dart @@ -1287,6 +1287,24 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + Amount dco_decode_amount(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + switch (raw[0]) { + case 0: + return Amount_Bitcoin( + amountMsat: dco_decode_u_64(raw[1]), + ); + case 1: + return Amount_Currency( + iso4217Code: dco_decode_String(raw[1]), + fractionalAmount: dco_decode_u_64(raw[2]), + ); + default: + throw Exception("unreachable"); + } + } + @protected BackupRequest dco_decode_backup_request(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1345,6 +1363,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_aes_success_action_data_result(raw); } + @protected + Amount dco_decode_box_autoadd_amount(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_amount(raw); + } + @protected BackupRequest dco_decode_box_autoadd_backup_request(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1423,6 +1447,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_ln_invoice(raw); } + @protected + LNOffer dco_decode_box_autoadd_ln_offer(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_ln_offer(raw); + } + @protected LnUrlAuthRequestData dco_decode_box_autoadd_ln_url_auth_request_data(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1766,26 +1796,30 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { invoice: dco_decode_box_autoadd_ln_invoice(raw[1]), ); case 3: + return InputType_Bolt12Offer( + offer: dco_decode_box_autoadd_ln_offer(raw[1]), + ); + case 4: return InputType_NodeId( nodeId: dco_decode_String(raw[1]), ); - case 4: + case 5: return InputType_Url( url: dco_decode_String(raw[1]), ); - case 5: + case 6: return InputType_LnUrlPay( data: dco_decode_box_autoadd_ln_url_pay_request_data(raw[1]), ); - case 6: + case 7: return InputType_LnUrlWithdraw( data: dco_decode_box_autoadd_ln_url_withdraw_request_data(raw[1]), ); - case 7: + case 8: return InputType_LnUrlAuth( data: dco_decode_box_autoadd_ln_url_auth_request_data(raw[1]), ); - case 8: + case 9: return InputType_LnUrlError( data: dco_decode_box_autoadd_ln_url_error_data(raw[1]), ); @@ -1838,6 +1872,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return LiquidNetwork.values[raw as int]; } + @protected + List dco_decode_list_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_String).toList(); + } + @protected List dco_decode_list_fiat_currency(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1951,6 +1991,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + LNOffer dco_decode_ln_offer(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 7) throw Exception('unexpected arr length: expect 7 but see ${arr.length}'); + return LNOffer( + offer: dco_decode_String(arr[0]), + chains: dco_decode_list_String(arr[1]), + minAmount: dco_decode_opt_box_autoadd_amount(arr[2]), + description: dco_decode_opt_String(arr[3]), + absoluteExpiry: dco_decode_opt_box_autoadd_u_64(arr[4]), + issuer: dco_decode_opt_String(arr[5]), + signingPubkey: dco_decode_opt_String(arr[6]), + ); + } + @protected LnUrlAuthError dco_decode_ln_url_auth_error(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2293,6 +2349,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw == null ? null : dco_decode_String(raw); } + @protected + Amount? dco_decode_opt_box_autoadd_amount(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_amount(raw); + } + @protected bool? dco_decode_opt_box_autoadd_bool(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -2848,6 +2910,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return SendDestination_Bolt11( invoice: dco_decode_box_autoadd_ln_invoice(raw[1]), ); + case 2: + return SendDestination_Bolt12( + offer: dco_decode_box_autoadd_ln_offer(raw[1]), + receiverAmountSat: dco_decode_u_64(raw[2]), + ); default: throw Exception("unreachable"); } @@ -3082,6 +3149,24 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + Amount sse_decode_amount(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var tag_ = sse_decode_i_32(deserializer); + switch (tag_) { + case 0: + var var_amountMsat = sse_decode_u_64(deserializer); + return Amount_Bitcoin(amountMsat: var_amountMsat); + case 1: + var var_iso4217Code = sse_decode_String(deserializer); + var var_fractionalAmount = sse_decode_u_64(deserializer); + return Amount_Currency(iso4217Code: var_iso4217Code, fractionalAmount: var_fractionalAmount); + default: + throw UnimplementedError(''); + } + } + @protected BackupRequest sse_decode_backup_request(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3138,6 +3223,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_aes_success_action_data_result(deserializer)); } + @protected + Amount sse_decode_box_autoadd_amount(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_amount(deserializer)); + } + @protected BackupRequest sse_decode_box_autoadd_backup_request(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3216,6 +3307,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_ln_invoice(deserializer)); } + @protected + LNOffer sse_decode_box_autoadd_ln_offer(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_ln_offer(deserializer)); + } + @protected LnUrlAuthRequestData sse_decode_box_autoadd_ln_url_auth_request_data(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3557,21 +3654,24 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { var var_invoice = sse_decode_box_autoadd_ln_invoice(deserializer); return InputType_Bolt11(invoice: var_invoice); case 3: + var var_offer = sse_decode_box_autoadd_ln_offer(deserializer); + return InputType_Bolt12Offer(offer: var_offer); + case 4: var var_nodeId = sse_decode_String(deserializer); return InputType_NodeId(nodeId: var_nodeId); - case 4: + case 5: var var_url = sse_decode_String(deserializer); return InputType_Url(url: var_url); - case 5: + case 6: var var_data = sse_decode_box_autoadd_ln_url_pay_request_data(deserializer); return InputType_LnUrlPay(data: var_data); - case 6: + case 7: var var_data = sse_decode_box_autoadd_ln_url_withdraw_request_data(deserializer); return InputType_LnUrlWithdraw(data: var_data); - case 7: + case 8: var var_data = sse_decode_box_autoadd_ln_url_auth_request_data(deserializer); return InputType_LnUrlAuth(data: var_data); - case 8: + case 9: var var_data = sse_decode_box_autoadd_ln_url_error_data(deserializer); return InputType_LnUrlError(data: var_data); default: @@ -3621,6 +3721,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return LiquidNetwork.values[inner]; } + @protected + List sse_decode_list_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_String(deserializer)); + } + return ans_; + } + @protected List sse_decode_list_fiat_currency(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -3801,6 +3913,26 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { minFinalCltvExpiryDelta: var_minFinalCltvExpiryDelta); } + @protected + LNOffer sse_decode_ln_offer(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_offer = sse_decode_String(deserializer); + var var_chains = sse_decode_list_String(deserializer); + var var_minAmount = sse_decode_opt_box_autoadd_amount(deserializer); + var var_description = sse_decode_opt_String(deserializer); + var var_absoluteExpiry = sse_decode_opt_box_autoadd_u_64(deserializer); + var var_issuer = sse_decode_opt_String(deserializer); + var var_signingPubkey = sse_decode_opt_String(deserializer); + return LNOffer( + offer: var_offer, + chains: var_chains, + minAmount: var_minAmount, + description: var_description, + absoluteExpiry: var_absoluteExpiry, + issuer: var_issuer, + signingPubkey: var_signingPubkey); + } + @protected LnUrlAuthError sse_decode_ln_url_auth_error(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -4106,6 +4238,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + Amount? sse_decode_opt_box_autoadd_amount(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_amount(deserializer)); + } else { + return null; + } + } + @protected bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -4684,6 +4827,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case 1: var var_invoice = sse_decode_box_autoadd_ln_invoice(deserializer); return SendDestination_Bolt11(invoice: var_invoice); + case 2: + var var_offer = sse_decode_box_autoadd_ln_offer(deserializer); + var var_receiverAmountSat = sse_decode_u_64(deserializer); + return SendDestination_Bolt12(offer: var_offer, receiverAmountSat: var_receiverAmountSat); default: throw UnimplementedError(''); } @@ -5002,6 +5149,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_amount(Amount self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + switch (self) { + case Amount_Bitcoin(amountMsat: final amountMsat): + sse_encode_i_32(0, serializer); + sse_encode_u_64(amountMsat, serializer); + case Amount_Currency(iso4217Code: final iso4217Code, fractionalAmount: final fractionalAmount): + sse_encode_i_32(1, serializer); + sse_encode_String(iso4217Code, serializer); + sse_encode_u_64(fractionalAmount, serializer); + default: + throw UnimplementedError(''); + } + } + @protected void sse_encode_backup_request(BackupRequest self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -5050,6 +5213,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_aes_success_action_data_result(self, serializer); } + @protected + void sse_encode_box_autoadd_amount(Amount self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_amount(self, serializer); + } + @protected void sse_encode_box_autoadd_backup_request(BackupRequest self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -5128,6 +5297,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_ln_invoice(self, serializer); } + @protected + void sse_encode_box_autoadd_ln_offer(LNOffer self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_ln_offer(self, serializer); + } + @protected void sse_encode_box_autoadd_ln_url_auth_request_data(LnUrlAuthRequestData self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -5441,23 +5616,26 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case InputType_Bolt11(invoice: final invoice): sse_encode_i_32(2, serializer); sse_encode_box_autoadd_ln_invoice(invoice, serializer); - case InputType_NodeId(nodeId: final nodeId): + case InputType_Bolt12Offer(offer: final offer): sse_encode_i_32(3, serializer); + sse_encode_box_autoadd_ln_offer(offer, serializer); + case InputType_NodeId(nodeId: final nodeId): + sse_encode_i_32(4, serializer); sse_encode_String(nodeId, serializer); case InputType_Url(url: final url): - sse_encode_i_32(4, serializer); + sse_encode_i_32(5, serializer); sse_encode_String(url, serializer); case InputType_LnUrlPay(data: final data): - sse_encode_i_32(5, serializer); + sse_encode_i_32(6, serializer); sse_encode_box_autoadd_ln_url_pay_request_data(data, serializer); case InputType_LnUrlWithdraw(data: final data): - sse_encode_i_32(6, serializer); + sse_encode_i_32(7, serializer); sse_encode_box_autoadd_ln_url_withdraw_request_data(data, serializer); case InputType_LnUrlAuth(data: final data): - sse_encode_i_32(7, serializer); + sse_encode_i_32(8, serializer); sse_encode_box_autoadd_ln_url_auth_request_data(data, serializer); case InputType_LnUrlError(data: final data): - sse_encode_i_32(8, serializer); + sse_encode_i_32(9, serializer); sse_encode_box_autoadd_ln_url_error_data(data, serializer); default: throw UnimplementedError(''); @@ -5497,6 +5675,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_i_32(self.index, serializer); } + @protected + void sse_encode_list_String(List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_String(item, serializer); + } + } + @protected void sse_encode_list_fiat_currency(List self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -5628,6 +5815,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_u_64(self.minFinalCltvExpiryDelta, serializer); } + @protected + void sse_encode_ln_offer(LNOffer self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.offer, serializer); + sse_encode_list_String(self.chains, serializer); + sse_encode_opt_box_autoadd_amount(self.minAmount, serializer); + sse_encode_opt_String(self.description, serializer); + sse_encode_opt_box_autoadd_u_64(self.absoluteExpiry, serializer); + sse_encode_opt_String(self.issuer, serializer); + sse_encode_opt_String(self.signingPubkey, serializer); + } + @protected void sse_encode_ln_url_auth_error(LnUrlAuthError self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -5892,6 +6091,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_opt_box_autoadd_amount(Amount? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_amount(self, serializer); + } + } + @protected void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -6385,6 +6594,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { case SendDestination_Bolt11(invoice: final invoice): sse_encode_i_32(1, serializer); sse_encode_box_autoadd_ln_invoice(invoice, serializer); + case SendDestination_Bolt12(offer: final offer, receiverAmountSat: final receiverAmountSat): + sse_encode_i_32(2, serializer); + sse_encode_box_autoadd_ln_offer(offer, serializer); + sse_encode_u_64(receiverAmountSat, serializer); default: throw UnimplementedError(''); } diff --git a/packages/dart/lib/src/frb_generated.io.dart b/packages/dart/lib/src/frb_generated.io.dart index 41fc0c08..37bfcd8e 100644 --- a/packages/dart/lib/src/frb_generated.io.dart +++ b/packages/dart/lib/src/frb_generated.io.dart @@ -59,6 +59,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AesSuccessActionDataResult dco_decode_aes_success_action_data_result(dynamic raw); + @protected + Amount dco_decode_amount(dynamic raw); + @protected BackupRequest dco_decode_backup_request(dynamic raw); @@ -80,6 +83,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AesSuccessActionDataResult dco_decode_box_autoadd_aes_success_action_data_result(dynamic raw); + @protected + Amount dco_decode_box_autoadd_amount(dynamic raw); + @protected BackupRequest dco_decode_box_autoadd_backup_request(dynamic raw); @@ -119,6 +125,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LNInvoice dco_decode_box_autoadd_ln_invoice(dynamic raw); + @protected + LNOffer dco_decode_box_autoadd_ln_offer(dynamic raw); + @protected LnUrlAuthRequestData dco_decode_box_autoadd_ln_url_auth_request_data(dynamic raw); @@ -266,6 +275,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LiquidNetwork dco_decode_liquid_network(dynamic raw); + @protected + List dco_decode_list_String(dynamic raw); + @protected List dco_decode_list_fiat_currency(dynamic raw); @@ -305,6 +317,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LNInvoice dco_decode_ln_invoice(dynamic raw); + @protected + LNOffer dco_decode_ln_offer(dynamic raw); + @protected LnUrlAuthError dco_decode_ln_url_auth_error(dynamic raw); @@ -371,6 +386,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String? dco_decode_opt_String(dynamic raw); + @protected + Amount? dco_decode_opt_box_autoadd_amount(dynamic raw); + @protected bool? dco_decode_opt_box_autoadd_bool(dynamic raw); @@ -580,6 +598,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected AesSuccessActionDataResult sse_decode_aes_success_action_data_result(SseDeserializer deserializer); + @protected + Amount sse_decode_amount(SseDeserializer deserializer); + @protected BackupRequest sse_decode_backup_request(SseDeserializer deserializer); @@ -603,6 +624,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { AesSuccessActionDataResult sse_decode_box_autoadd_aes_success_action_data_result( SseDeserializer deserializer); + @protected + Amount sse_decode_box_autoadd_amount(SseDeserializer deserializer); + @protected BackupRequest sse_decode_box_autoadd_backup_request(SseDeserializer deserializer); @@ -642,6 +666,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LNInvoice sse_decode_box_autoadd_ln_invoice(SseDeserializer deserializer); + @protected + LNOffer sse_decode_box_autoadd_ln_offer(SseDeserializer deserializer); + @protected LnUrlAuthRequestData sse_decode_box_autoadd_ln_url_auth_request_data(SseDeserializer deserializer); @@ -789,6 +816,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LiquidNetwork sse_decode_liquid_network(SseDeserializer deserializer); + @protected + List sse_decode_list_String(SseDeserializer deserializer); + @protected List sse_decode_list_fiat_currency(SseDeserializer deserializer); @@ -828,6 +858,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected LNInvoice sse_decode_ln_invoice(SseDeserializer deserializer); + @protected + LNOffer sse_decode_ln_offer(SseDeserializer deserializer); + @protected LnUrlAuthError sse_decode_ln_url_auth_error(SseDeserializer deserializer); @@ -894,6 +927,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String? sse_decode_opt_String(SseDeserializer deserializer); + @protected + Amount? sse_decode_opt_box_autoadd_amount(SseDeserializer deserializer); + @protected bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer); @@ -1129,6 +1165,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return ptr; } + @protected + ffi.Pointer cst_encode_box_autoadd_amount(Amount raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ptr = wire.cst_new_box_autoadd_amount(); + cst_api_fill_to_wire_amount(raw, ptr.ref); + return ptr; + } + @protected ffi.Pointer cst_encode_box_autoadd_backup_request(BackupRequest raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -1237,6 +1281,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return ptr; } + @protected + ffi.Pointer cst_encode_box_autoadd_ln_offer(LNOffer raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ptr = wire.cst_new_box_autoadd_ln_offer(); + cst_api_fill_to_wire_ln_offer(raw, ptr.ref); + return ptr; + } + @protected ffi.Pointer cst_encode_box_autoadd_ln_url_auth_request_data( LnUrlAuthRequestData raw) { @@ -1507,6 +1559,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return raw.toInt(); } + @protected + ffi.Pointer cst_encode_list_String(List raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ans = wire.cst_new_list_String(raw.length); + for (var i = 0; i < raw.length; ++i) { + ans.ref.ptr[i] = cst_encode_String(raw[i]); + } + return ans; + } + @protected ffi.Pointer cst_encode_list_fiat_currency(List raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -1611,6 +1673,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return raw == null ? ffi.nullptr : cst_encode_String(raw); } + @protected + ffi.Pointer cst_encode_opt_box_autoadd_amount(Amount? raw) { + // Codec=Cst (C-struct based), see doc to use other codecs + return raw == null ? ffi.nullptr : cst_encode_box_autoadd_amount(raw); + } + @protected ffi.Pointer cst_encode_opt_box_autoadd_bool(bool? raw) { // Codec=Cst (C-struct based), see doc to use other codecs @@ -1723,6 +1791,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { } } + @protected + void cst_api_fill_to_wire_amount(Amount apiObj, wire_cst_amount wireObj) { + if (apiObj is Amount_Bitcoin) { + var pre_amount_msat = cst_encode_u_64(apiObj.amountMsat); + wireObj.tag = 0; + wireObj.kind.Bitcoin.amount_msat = pre_amount_msat; + return; + } + if (apiObj is Amount_Currency) { + var pre_iso4217_code = cst_encode_String(apiObj.iso4217Code); + var pre_fractional_amount = cst_encode_u_64(apiObj.fractionalAmount); + wireObj.tag = 1; + wireObj.kind.Currency.iso4217_code = pre_iso4217_code; + wireObj.kind.Currency.fractional_amount = pre_fractional_amount; + return; + } + } + @protected void cst_api_fill_to_wire_backup_request(BackupRequest apiObj, wire_cst_backup_request wireObj) { wireObj.backup_path = cst_encode_opt_String(apiObj.backupPath); @@ -1762,6 +1848,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { cst_api_fill_to_wire_aes_success_action_data_result(apiObj, wireObj.ref); } + @protected + void cst_api_fill_to_wire_box_autoadd_amount(Amount apiObj, ffi.Pointer wireObj) { + cst_api_fill_to_wire_amount(apiObj, wireObj.ref); + } + @protected void cst_api_fill_to_wire_box_autoadd_backup_request( BackupRequest apiObj, ffi.Pointer wireObj) { @@ -1828,6 +1919,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { cst_api_fill_to_wire_ln_invoice(apiObj, wireObj.ref); } + @protected + void cst_api_fill_to_wire_box_autoadd_ln_offer(LNOffer apiObj, ffi.Pointer wireObj) { + cst_api_fill_to_wire_ln_offer(apiObj, wireObj.ref); + } + @protected void cst_api_fill_to_wire_box_autoadd_ln_url_auth_request_data( LnUrlAuthRequestData apiObj, ffi.Pointer wireObj) { @@ -2096,39 +2192,45 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.kind.Bolt11.invoice = pre_invoice; return; } + if (apiObj is InputType_Bolt12Offer) { + var pre_offer = cst_encode_box_autoadd_ln_offer(apiObj.offer); + wireObj.tag = 3; + wireObj.kind.Bolt12Offer.offer = pre_offer; + return; + } if (apiObj is InputType_NodeId) { var pre_node_id = cst_encode_String(apiObj.nodeId); - wireObj.tag = 3; + wireObj.tag = 4; wireObj.kind.NodeId.node_id = pre_node_id; return; } if (apiObj is InputType_Url) { var pre_url = cst_encode_String(apiObj.url); - wireObj.tag = 4; + wireObj.tag = 5; wireObj.kind.Url.url = pre_url; return; } if (apiObj is InputType_LnUrlPay) { var pre_data = cst_encode_box_autoadd_ln_url_pay_request_data(apiObj.data); - wireObj.tag = 5; + wireObj.tag = 6; wireObj.kind.LnUrlPay.data = pre_data; return; } if (apiObj is InputType_LnUrlWithdraw) { var pre_data = cst_encode_box_autoadd_ln_url_withdraw_request_data(apiObj.data); - wireObj.tag = 6; + wireObj.tag = 7; wireObj.kind.LnUrlWithdraw.data = pre_data; return; } if (apiObj is InputType_LnUrlAuth) { var pre_data = cst_encode_box_autoadd_ln_url_auth_request_data(apiObj.data); - wireObj.tag = 7; + wireObj.tag = 8; wireObj.kind.LnUrlAuth.data = pre_data; return; } if (apiObj is InputType_LnUrlError) { var pre_data = cst_encode_box_autoadd_ln_url_error_data(apiObj.data); - wireObj.tag = 8; + wireObj.tag = 9; wireObj.kind.LnUrlError.data = pre_data; return; } @@ -2203,6 +2305,17 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.min_final_cltv_expiry_delta = cst_encode_u_64(apiObj.minFinalCltvExpiryDelta); } + @protected + void cst_api_fill_to_wire_ln_offer(LNOffer apiObj, wire_cst_ln_offer wireObj) { + wireObj.offer = cst_encode_String(apiObj.offer); + wireObj.chains = cst_encode_list_String(apiObj.chains); + wireObj.min_amount = cst_encode_opt_box_autoadd_amount(apiObj.minAmount); + wireObj.description = cst_encode_opt_String(apiObj.description); + wireObj.absolute_expiry = cst_encode_opt_box_autoadd_u_64(apiObj.absoluteExpiry); + wireObj.issuer = cst_encode_opt_String(apiObj.issuer); + wireObj.signing_pubkey = cst_encode_opt_String(apiObj.signingPubkey); + } + @protected void cst_api_fill_to_wire_ln_url_auth_error(LnUrlAuthError apiObj, wire_cst_ln_url_auth_error wireObj) { if (apiObj is LnUrlAuthError_Generic) { @@ -2931,6 +3044,14 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { wireObj.kind.Bolt11.invoice = pre_invoice; return; } + if (apiObj is SendDestination_Bolt12) { + var pre_offer = cst_encode_box_autoadd_ln_offer(apiObj.offer); + var pre_receiver_amount_sat = cst_encode_u_64(apiObj.receiverAmountSat); + wireObj.tag = 2; + wireObj.kind.Bolt12.offer = pre_offer; + wireObj.kind.Bolt12.receiver_amount_sat = pre_receiver_amount_sat; + return; + } } @protected @@ -3103,6 +3224,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_aes_success_action_data_result(AesSuccessActionDataResult self, SseSerializer serializer); + @protected + void sse_encode_amount(Amount self, SseSerializer serializer); + @protected void sse_encode_backup_request(BackupRequest self, SseSerializer serializer); @@ -3126,6 +3250,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void sse_encode_box_autoadd_aes_success_action_data_result( AesSuccessActionDataResult self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_amount(Amount self, SseSerializer serializer); + @protected void sse_encode_box_autoadd_backup_request(BackupRequest self, SseSerializer serializer); @@ -3165,6 +3292,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_box_autoadd_ln_invoice(LNInvoice self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_ln_offer(LNOffer self, SseSerializer serializer); + @protected void sse_encode_box_autoadd_ln_url_auth_request_data(LnUrlAuthRequestData self, SseSerializer serializer); @@ -3319,6 +3449,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_liquid_network(LiquidNetwork self, SseSerializer serializer); + @protected + void sse_encode_list_String(List self, SseSerializer serializer); + @protected void sse_encode_list_fiat_currency(List self, SseSerializer serializer); @@ -3358,6 +3491,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_ln_invoice(LNInvoice self, SseSerializer serializer); + @protected + void sse_encode_ln_offer(LNOffer self, SseSerializer serializer); + @protected void sse_encode_ln_url_auth_error(LnUrlAuthError self, SseSerializer serializer); @@ -3425,6 +3561,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_opt_String(String? self, SseSerializer serializer); + @protected + void sse_encode_opt_box_autoadd_amount(Amount? self, SseSerializer serializer); + @protected void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer); @@ -4426,6 +4565,16 @@ class RustLibWire implements BaseWire { _cst_new_box_autoadd_aes_success_action_data_resultPtr .asFunction Function()>(); + ffi.Pointer cst_new_box_autoadd_amount() { + return _cst_new_box_autoadd_amount(); + } + + late final _cst_new_box_autoadd_amountPtr = + _lookup Function()>>( + 'frbgen_breez_liquid_cst_new_box_autoadd_amount'); + late final _cst_new_box_autoadd_amount = + _cst_new_box_autoadd_amountPtr.asFunction Function()>(); + ffi.Pointer cst_new_box_autoadd_backup_request() { return _cst_new_box_autoadd_backup_request(); } @@ -4564,6 +4713,16 @@ class RustLibWire implements BaseWire { late final _cst_new_box_autoadd_ln_invoice = _cst_new_box_autoadd_ln_invoicePtr.asFunction Function()>(); + ffi.Pointer cst_new_box_autoadd_ln_offer() { + return _cst_new_box_autoadd_ln_offer(); + } + + late final _cst_new_box_autoadd_ln_offerPtr = + _lookup Function()>>( + 'frbgen_breez_liquid_cst_new_box_autoadd_ln_offer'); + late final _cst_new_box_autoadd_ln_offer = + _cst_new_box_autoadd_ln_offerPtr.asFunction Function()>(); + ffi.Pointer cst_new_box_autoadd_ln_url_auth_request_data() { return _cst_new_box_autoadd_ln_url_auth_request_data(); } @@ -4888,6 +5047,20 @@ class RustLibWire implements BaseWire { late final _cst_new_box_autoadd_url_success_action_data = _cst_new_box_autoadd_url_success_action_dataPtr .asFunction Function()>(); + ffi.Pointer cst_new_list_String( + int len, + ) { + return _cst_new_list_String( + len, + ); + } + + late final _cst_new_list_StringPtr = + _lookup Function(ffi.Int32)>>( + 'frbgen_breez_liquid_cst_new_list_String'); + late final _cst_new_list_String = + _cst_new_list_StringPtr.asFunction Function(int)>(); + ffi.Pointer cst_new_list_fiat_currency( int len, ) { @@ -5238,10 +5411,67 @@ final class wire_cst_SendDestination_Bolt11 extends ffi.Struct { external ffi.Pointer invoice; } +final class wire_cst_list_String extends ffi.Struct { + external ffi.Pointer> ptr; + + @ffi.Int32() + external int len; +} + +final class wire_cst_Amount_Bitcoin extends ffi.Struct { + @ffi.Uint64() + external int amount_msat; +} + +final class wire_cst_Amount_Currency extends ffi.Struct { + external ffi.Pointer iso4217_code; + + @ffi.Uint64() + external int fractional_amount; +} + +final class AmountKind extends ffi.Union { + external wire_cst_Amount_Bitcoin Bitcoin; + + external wire_cst_Amount_Currency Currency; +} + +final class wire_cst_amount extends ffi.Struct { + @ffi.Int32() + external int tag; + + external AmountKind kind; +} + +final class wire_cst_ln_offer extends ffi.Struct { + external ffi.Pointer offer; + + external ffi.Pointer chains; + + external ffi.Pointer min_amount; + + external ffi.Pointer description; + + external ffi.Pointer absolute_expiry; + + external ffi.Pointer issuer; + + external ffi.Pointer signing_pubkey; +} + +final class wire_cst_SendDestination_Bolt12 extends ffi.Struct { + external ffi.Pointer offer; + + @ffi.Uint64() + external int receiver_amount_sat; +} + final class SendDestinationKind extends ffi.Union { external wire_cst_SendDestination_LiquidAddress LiquidAddress; external wire_cst_SendDestination_Bolt11 Bolt11; + + external wire_cst_SendDestination_Bolt12 Bolt12; } final class wire_cst_send_destination extends ffi.Struct { @@ -5858,6 +6088,10 @@ final class wire_cst_InputType_Bolt11 extends ffi.Struct { external ffi.Pointer invoice; } +final class wire_cst_InputType_Bolt12Offer extends ffi.Struct { + external ffi.Pointer offer; +} + final class wire_cst_InputType_NodeId extends ffi.Struct { external ffi.Pointer node_id; } @@ -5889,6 +6123,8 @@ final class InputTypeKind extends ffi.Union { external wire_cst_InputType_Bolt11 Bolt11; + external wire_cst_InputType_Bolt12Offer Bolt12Offer; + external wire_cst_InputType_NodeId NodeId; external wire_cst_InputType_Url Url; diff --git a/packages/dart/lib/src/model.dart b/packages/dart/lib/src/model.dart index 04ad60e8..39fab758 100644 --- a/packages/dart/lib/src/model.dart +++ b/packages/dart/lib/src/model.dart @@ -1001,7 +1001,7 @@ class PrepareRefundResponse { /// An argument when calling [crate::sdk::LiquidSdk::prepare_send_payment]. class PrepareSendRequest { /// The destination we intend to pay to. - /// Supports BIP21 URIs, BOLT11 invoices and Liquid addresses + /// Supports BIP21 URIs, BOLT11 invoices, BOLT12 offers and Liquid addresses final String destination; /// Should only be set when paying directly onchain or to a BIP21 URI @@ -1257,6 +1257,10 @@ sealed class SendDestination with _$SendDestination { const factory SendDestination.bolt11({ required LNInvoice invoice, }) = SendDestination_Bolt11; + const factory SendDestination.bolt12({ + required LNOffer offer, + required BigInt receiverAmountSat, + }) = SendDestination_Bolt12; } /// An argument when calling [crate::sdk::LiquidSdk::send_payment]. diff --git a/packages/dart/lib/src/model.freezed.dart b/packages/dart/lib/src/model.freezed.dart index 37d19aaf..e0f93d35 100644 --- a/packages/dart/lib/src/model.freezed.dart +++ b/packages/dart/lib/src/model.freezed.dart @@ -1920,3 +1920,93 @@ abstract class SendDestination_Bolt11 extends SendDestination { _$$SendDestination_Bolt11ImplCopyWith<_$SendDestination_Bolt11Impl> get copyWith => throw _privateConstructorUsedError; } + +/// @nodoc +abstract class _$$SendDestination_Bolt12ImplCopyWith<$Res> { + factory _$$SendDestination_Bolt12ImplCopyWith( + _$SendDestination_Bolt12Impl value, $Res Function(_$SendDestination_Bolt12Impl) then) = + __$$SendDestination_Bolt12ImplCopyWithImpl<$Res>; + @useResult + $Res call({LNOffer offer, BigInt receiverAmountSat}); +} + +/// @nodoc +class __$$SendDestination_Bolt12ImplCopyWithImpl<$Res> + extends _$SendDestinationCopyWithImpl<$Res, _$SendDestination_Bolt12Impl> + implements _$$SendDestination_Bolt12ImplCopyWith<$Res> { + __$$SendDestination_Bolt12ImplCopyWithImpl( + _$SendDestination_Bolt12Impl _value, $Res Function(_$SendDestination_Bolt12Impl) _then) + : super(_value, _then); + + /// Create a copy of SendDestination + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? offer = null, + Object? receiverAmountSat = null, + }) { + return _then(_$SendDestination_Bolt12Impl( + offer: null == offer + ? _value.offer + : offer // ignore: cast_nullable_to_non_nullable + as LNOffer, + receiverAmountSat: null == receiverAmountSat + ? _value.receiverAmountSat + : receiverAmountSat // ignore: cast_nullable_to_non_nullable + as BigInt, + )); + } +} + +/// @nodoc + +class _$SendDestination_Bolt12Impl extends SendDestination_Bolt12 { + const _$SendDestination_Bolt12Impl({required this.offer, required this.receiverAmountSat}) : super._(); + + @override + final LNOffer offer; + @override + final BigInt receiverAmountSat; + + @override + String toString() { + return 'SendDestination.bolt12(offer: $offer, receiverAmountSat: $receiverAmountSat)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SendDestination_Bolt12Impl && + (identical(other.offer, offer) || other.offer == offer) && + (identical(other.receiverAmountSat, receiverAmountSat) || + other.receiverAmountSat == receiverAmountSat)); + } + + @override + int get hashCode => Object.hash(runtimeType, offer, receiverAmountSat); + + /// Create a copy of SendDestination + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SendDestination_Bolt12ImplCopyWith<_$SendDestination_Bolt12Impl> get copyWith => + __$$SendDestination_Bolt12ImplCopyWithImpl<_$SendDestination_Bolt12Impl>(this, _$identity); +} + +abstract class SendDestination_Bolt12 extends SendDestination { + const factory SendDestination_Bolt12( + {required final LNOffer offer, required final BigInt receiverAmountSat}) = _$SendDestination_Bolt12Impl; + const SendDestination_Bolt12._() : super._(); + + LNOffer get offer; + BigInt get receiverAmountSat; + + /// Create a copy of SendDestination + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SendDestination_Bolt12ImplCopyWith<_$SendDestination_Bolt12Impl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart index e623c13a..2a515e3e 100644 --- a/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart +++ b/packages/flutter/lib/flutter_breez_liquid_bindings_generated.dart @@ -846,6 +846,17 @@ class FlutterBreezLiquidBindings { _frbgen_breez_liquid_cst_new_box_autoadd_aes_success_action_data_resultPtr .asFunction Function()>(); + ffi.Pointer frbgen_breez_liquid_cst_new_box_autoadd_amount() { + return _frbgen_breez_liquid_cst_new_box_autoadd_amount(); + } + + late final _frbgen_breez_liquid_cst_new_box_autoadd_amountPtr = + _lookup Function()>>( + 'frbgen_breez_liquid_cst_new_box_autoadd_amount'); + late final _frbgen_breez_liquid_cst_new_box_autoadd_amount = + _frbgen_breez_liquid_cst_new_box_autoadd_amountPtr + .asFunction Function()>(); + ffi.Pointer frbgen_breez_liquid_cst_new_box_autoadd_backup_request() { return _frbgen_breez_liquid_cst_new_box_autoadd_backup_request(); } @@ -998,6 +1009,17 @@ class FlutterBreezLiquidBindings { _frbgen_breez_liquid_cst_new_box_autoadd_ln_invoicePtr .asFunction Function()>(); + ffi.Pointer frbgen_breez_liquid_cst_new_box_autoadd_ln_offer() { + return _frbgen_breez_liquid_cst_new_box_autoadd_ln_offer(); + } + + late final _frbgen_breez_liquid_cst_new_box_autoadd_ln_offerPtr = + _lookup Function()>>( + 'frbgen_breez_liquid_cst_new_box_autoadd_ln_offer'); + late final _frbgen_breez_liquid_cst_new_box_autoadd_ln_offer = + _frbgen_breez_liquid_cst_new_box_autoadd_ln_offerPtr + .asFunction Function()>(); + ffi.Pointer frbgen_breez_liquid_cst_new_box_autoadd_ln_url_auth_request_data() { return _frbgen_breez_liquid_cst_new_box_autoadd_ln_url_auth_request_data(); @@ -1361,6 +1383,20 @@ class FlutterBreezLiquidBindings { _frbgen_breez_liquid_cst_new_box_autoadd_url_success_action_dataPtr .asFunction Function()>(); + ffi.Pointer frbgen_breez_liquid_cst_new_list_String( + int len, + ) { + return _frbgen_breez_liquid_cst_new_list_String( + len, + ); + } + + late final _frbgen_breez_liquid_cst_new_list_StringPtr = + _lookup Function(ffi.Int32)>>( + 'frbgen_breez_liquid_cst_new_list_String'); + late final _frbgen_breez_liquid_cst_new_list_String = _frbgen_breez_liquid_cst_new_list_StringPtr + .asFunction Function(int)>(); + ffi.Pointer frbgen_breez_liquid_cst_new_list_fiat_currency( int len, ) { @@ -3997,10 +4033,19 @@ final class wire_cst_SendDestination_Bolt11 extends ffi.Struct { external ffi.Pointer invoice; } +final class wire_cst_SendDestination_Bolt12 extends ffi.Struct { + external ffi.Pointer offer; + + @ffi.Uint64() + external int receiver_amount_sat; +} + final class SendDestinationKind extends ffi.Union { external wire_cst_SendDestination_LiquidAddress LiquidAddress; external wire_cst_SendDestination_Bolt11 Bolt11; + + external wire_cst_SendDestination_Bolt12 Bolt12; } final class wire_cst_send_destination extends ffi.Struct { @@ -4419,6 +4464,31 @@ final class wire_cst_aes_success_action_data_result extends ffi.Struct { external AesSuccessActionDataResultKind kind; } +final class wire_cst_Amount_Bitcoin extends ffi.Struct { + @ffi.Uint64() + external int amount_msat; +} + +final class wire_cst_Amount_Currency extends ffi.Struct { + external ffi.Pointer iso4217_code; + + @ffi.Uint64() + external int fractional_amount; +} + +final class AmountKind extends ffi.Union { + external wire_cst_Amount_Bitcoin Bitcoin; + + external wire_cst_Amount_Currency Currency; +} + +final class wire_cst_amount extends ffi.Struct { + @ffi.Int32() + external int tag; + + external AmountKind kind; +} + final class wire_cst_bitcoin_address_data extends ffi.Struct { external ffi.Pointer address; @@ -4432,6 +4502,29 @@ final class wire_cst_bitcoin_address_data extends ffi.Struct { external ffi.Pointer message; } +final class wire_cst_list_String extends ffi.Struct { + external ffi.Pointer> ptr; + + @ffi.Int32() + external int len; +} + +final class wire_cst_ln_offer extends ffi.Struct { + external ffi.Pointer bolt12; + + external ffi.Pointer chains; + + external ffi.Pointer amount; + + external ffi.Pointer description; + + external ffi.Pointer absolute_expiry; + + external ffi.Pointer issuer; + + external ffi.Pointer signing_pubkey; +} + final class wire_cst_ln_url_error_data extends ffi.Struct { external ffi.Pointer reason; } @@ -4617,6 +4710,10 @@ final class wire_cst_InputType_Bolt11 extends ffi.Struct { external ffi.Pointer invoice; } +final class wire_cst_InputType_Bolt12Offer extends ffi.Struct { + external ffi.Pointer offer; +} + final class wire_cst_InputType_NodeId extends ffi.Struct { external ffi.Pointer node_id; } @@ -4648,6 +4745,8 @@ final class InputTypeKind extends ffi.Union { external wire_cst_InputType_Bolt11 Bolt11; + external wire_cst_InputType_Bolt12Offer Bolt12Offer; + external wire_cst_InputType_NodeId NodeId; external wire_cst_InputType_Url Url; diff --git a/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt b/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt index 9759daed..4be02922 100644 --- a/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt +++ b/packages/react-native/android/src/main/java/com/breezsdkliquid/BreezSDKLiquidMapper.kt @@ -564,6 +564,49 @@ fun asLnInvoiceList(arr: ReadableArray): List { return list } +fun asLnOffer(lnOffer: ReadableMap): LnOffer? { + if (!validateMandatoryFields( + lnOffer, + arrayOf( + "offer", + "chains", + ), + ) + ) { + return null + } + val offer = lnOffer.getString("offer")!! + val chains = lnOffer.getArray("chains")?.let { asStringList(it) }!! + val description = if (hasNonNullKey(lnOffer, "description")) lnOffer.getString("description") else null + val signingPubkey = if (hasNonNullKey(lnOffer, "signingPubkey")) lnOffer.getString("signingPubkey") else null + val minAmount = if (hasNonNullKey(lnOffer, "minAmount")) lnOffer.getMap("minAmount")?.let { asAmount(it) } else null + val absoluteExpiry = if (hasNonNullKey(lnOffer, "absoluteExpiry")) lnOffer.getDouble("absoluteExpiry").toULong() else null + val issuer = if (hasNonNullKey(lnOffer, "issuer")) lnOffer.getString("issuer") else null + return LnOffer(offer, chains, description, signingPubkey, minAmount, absoluteExpiry, issuer) +} + +fun readableMapOf(lnOffer: LnOffer): ReadableMap = + readableMapOf( + "offer" to lnOffer.offer, + "chains" to readableArrayOf(lnOffer.chains), + "description" to lnOffer.description, + "signingPubkey" to lnOffer.signingPubkey, + "minAmount" to lnOffer.minAmount?.let { readableMapOf(it) }, + "absoluteExpiry" to lnOffer.absoluteExpiry, + "issuer" to lnOffer.issuer, + ) + +fun asLnOfferList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toList()) { + when (value) { + is ReadableMap -> list.add(asLnOffer(value)!!) + else -> throw SdkException.Generic(errUnexpectedType(value)) + } + } + return list +} + fun asLightningPaymentLimitsResponse(lightningPaymentLimitsResponse: ReadableMap): LightningPaymentLimitsResponse? { if (!validateMandatoryFields( lightningPaymentLimitsResponse, @@ -2367,6 +2410,48 @@ fun asAesSuccessActionDataResultList(arr: ReadableArray): List { + pushToMap(map, "type", "bitcoin") + pushToMap(map, "amountMsat", amount.amountMsat) + } + is Amount.Currency -> { + pushToMap(map, "type", "currency") + pushToMap(map, "iso4217Code", amount.iso4217Code) + pushToMap(map, "fractionalAmount", amount.fractionalAmount) + } + } + return map +} + +fun asAmountList(arr: ReadableArray): List { + val list = ArrayList() + for (value in arr.toList()) { + when (value) { + is ReadableMap -> list.add(asAmount(value)!!) + else -> throw SdkException.Generic(errUnexpectedType(value)) + } + } + return list +} + fun asBuyBitcoinProvider(type: String): BuyBitcoinProvider = BuyBitcoinProvider.valueOf(camelToUpperSnakeCase(type)) fun asBuyBitcoinProviderList(arr: ReadableArray): List { @@ -2427,6 +2512,10 @@ fun asInputType(inputType: ReadableMap): InputType? { val invoice = inputType.getMap("invoice")?.let { asLnInvoice(it) }!! return InputType.Bolt11(invoice) } + if (type == "bolt12Offer") { + val offer = inputType.getMap("offer")?.let { asLnOffer(it) }!! + return InputType.Bolt12Offer(offer) + } if (type == "nodeId") { val nodeId = inputType.getString("nodeId")!! return InputType.NodeId(nodeId) @@ -2469,6 +2558,10 @@ fun readableMapOf(inputType: InputType): ReadableMap? { pushToMap(map, "type", "bolt11") pushToMap(map, "invoice", readableMapOf(inputType.invoice)) } + is InputType.Bolt12Offer -> { + pushToMap(map, "type", "bolt12Offer") + pushToMap(map, "offer", readableMapOf(inputType.offer)) + } is InputType.NodeId -> { pushToMap(map, "type", "nodeId") pushToMap(map, "nodeId", inputType.nodeId) @@ -2960,6 +3053,11 @@ fun asSendDestination(sendDestination: ReadableMap): SendDestination? { val invoice = sendDestination.getMap("invoice")?.let { asLnInvoice(it) }!! return SendDestination.Bolt11(invoice) } + if (type == "bolt12") { + val offer = sendDestination.getMap("offer")?.let { asLnOffer(it) }!! + val receiverAmountSat = sendDestination.getDouble("receiverAmountSat").toULong() + return SendDestination.Bolt12(offer, receiverAmountSat) + } return null } @@ -2974,6 +3072,11 @@ fun readableMapOf(sendDestination: SendDestination): ReadableMap? { pushToMap(map, "type", "bolt11") pushToMap(map, "invoice", readableMapOf(sendDestination.invoice)) } + is SendDestination.Bolt12 -> { + pushToMap(map, "type", "bolt12") + pushToMap(map, "offer", readableMapOf(sendDestination.offer)) + pushToMap(map, "receiverAmountSat", sendDestination.receiverAmountSat) + } } return map } @@ -3124,6 +3227,7 @@ fun pushToArray( is RefundableSwap -> array.pushMap(readableMapOf(value)) is RouteHint -> array.pushMap(readableMapOf(value)) is RouteHintHop -> array.pushMap(readableMapOf(value)) + is String -> array.pushString(value) is UByte -> array.pushInt(value.toInt()) is Array<*> -> array.pushArray(readableArrayOf(value.asIterable())) is List<*> -> array.pushArray(readableArrayOf(value)) diff --git a/packages/react-native/ios/BreezSDKLiquidMapper.swift b/packages/react-native/ios/BreezSDKLiquidMapper.swift index baaad585..ec212d5f 100644 --- a/packages/react-native/ios/BreezSDKLiquidMapper.swift +++ b/packages/react-native/ios/BreezSDKLiquidMapper.swift @@ -665,6 +665,79 @@ enum BreezSDKLiquidMapper { return lnInvoiceList.map { v -> [String: Any?] in return dictionaryOf(lnInvoice: v) } } + static func asLnOffer(lnOffer: [String: Any?]) throws -> LnOffer { + guard let offer = lnOffer["offer"] as? String else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "offer", typeName: "LnOffer")) + } + guard let chains = lnOffer["chains"] as? [String] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "chains", typeName: "LnOffer")) + } + var description: String? + if hasNonNilKey(data: lnOffer, key: "description") { + guard let descriptionTmp = lnOffer["description"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "description")) + } + description = descriptionTmp + } + var signingPubkey: String? + if hasNonNilKey(data: lnOffer, key: "signingPubkey") { + guard let signingPubkeyTmp = lnOffer["signingPubkey"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "signingPubkey")) + } + signingPubkey = signingPubkeyTmp + } + var minAmount: Amount? + if let minAmountTmp = lnOffer["minAmount"] as? [String: Any?] { + minAmount = try asAmount(amount: minAmountTmp) + } + + var absoluteExpiry: UInt64? + if hasNonNilKey(data: lnOffer, key: "absoluteExpiry") { + guard let absoluteExpiryTmp = lnOffer["absoluteExpiry"] as? UInt64 else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "absoluteExpiry")) + } + absoluteExpiry = absoluteExpiryTmp + } + var issuer: String? + if hasNonNilKey(data: lnOffer, key: "issuer") { + guard let issuerTmp = lnOffer["issuer"] as? String else { + throw SdkError.Generic(message: errUnexpectedValue(fieldName: "issuer")) + } + issuer = issuerTmp + } + + return LnOffer(offer: offer, chains: chains, description: description, signingPubkey: signingPubkey, minAmount: minAmount, absoluteExpiry: absoluteExpiry, issuer: issuer) + } + + static func dictionaryOf(lnOffer: LnOffer) -> [String: Any?] { + return [ + "offer": lnOffer.offer, + "chains": lnOffer.chains, + "description": lnOffer.description == nil ? nil : lnOffer.description, + "signingPubkey": lnOffer.signingPubkey == nil ? nil : lnOffer.signingPubkey, + "minAmount": lnOffer.minAmount == nil ? nil : dictionaryOf(amount: lnOffer.minAmount!), + "absoluteExpiry": lnOffer.absoluteExpiry == nil ? nil : lnOffer.absoluteExpiry, + "issuer": lnOffer.issuer == nil ? nil : lnOffer.issuer, + ] + } + + static func asLnOfferList(arr: [Any]) throws -> [LnOffer] { + var list = [LnOffer]() + for value in arr { + if let val = value as? [String: Any?] { + var lnOffer = try asLnOffer(lnOffer: val) + list.append(lnOffer) + } else { + throw SdkError.Generic(message: errUnexpectedType(typeName: "LnOffer")) + } + } + return list + } + + static func arrayOf(lnOfferList: [LnOffer]) -> [Any] { + return lnOfferList.map { v -> [String: Any?] in return dictionaryOf(lnOffer: v) } + } + static func asLightningPaymentLimitsResponse(lightningPaymentLimitsResponse: [String: Any?]) throws -> LightningPaymentLimitsResponse { guard let sendTmp = lightningPaymentLimitsResponse["send"] as? [String: Any?] else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "send", typeName: "LightningPaymentLimitsResponse")) @@ -2727,6 +2800,65 @@ enum BreezSDKLiquidMapper { return list } + static func asAmount(amount: [String: Any?]) throws -> Amount { + let type = amount["type"] as! String + if type == "bitcoin" { + guard let _amountMsat = amount["amountMsat"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "amountMsat", typeName: "Amount")) + } + return Amount.bitcoin(amountMsat: _amountMsat) + } + if type == "currency" { + guard let _iso4217Code = amount["iso4217Code"] as? String else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "iso4217Code", typeName: "Amount")) + } + guard let _fractionalAmount = amount["fractionalAmount"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "fractionalAmount", typeName: "Amount")) + } + return Amount.currency(iso4217Code: _iso4217Code, fractionalAmount: _fractionalAmount) + } + + throw SdkError.Generic(message: "Unexpected type \(type) for enum Amount") + } + + static func dictionaryOf(amount: Amount) -> [String: Any?] { + switch amount { + case let .bitcoin( + amountMsat + ): + return [ + "type": "bitcoin", + "amountMsat": amountMsat, + ] + + case let .currency( + iso4217Code, fractionalAmount + ): + return [ + "type": "currency", + "iso4217Code": iso4217Code, + "fractionalAmount": fractionalAmount, + ] + } + } + + static func arrayOf(amountList: [Amount]) -> [Any] { + return amountList.map { v -> [String: Any?] in return dictionaryOf(amount: v) } + } + + static func asAmountList(arr: [Any]) throws -> [Amount] { + var list = [Amount]() + for value in arr { + if let val = value as? [String: Any?] { + var amount = try asAmount(amount: val) + list.append(amount) + } else { + throw SdkError.Generic(message: errUnexpectedType(typeName: "Amount")) + } + } + return list + } + static func asBuyBitcoinProvider(buyBitcoinProvider: String) throws -> BuyBitcoinProvider { switch buyBitcoinProvider { case "moonpay": @@ -2827,6 +2959,14 @@ enum BreezSDKLiquidMapper { return InputType.bolt11(invoice: _invoice) } + if type == "bolt12Offer" { + guard let offerTmp = inputType["offer"] as? [String: Any?] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "offer", typeName: "InputType")) + } + let _offer = try asLnOffer(lnOffer: offerTmp) + + return InputType.bolt12Offer(offer: _offer) + } if type == "nodeId" { guard let _nodeId = inputType["nodeId"] as? String else { throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "nodeId", typeName: "InputType")) @@ -2901,6 +3041,14 @@ enum BreezSDKLiquidMapper { "invoice": dictionaryOf(lnInvoice: invoice), ] + case let .bolt12Offer( + offer + ): + return [ + "type": "bolt12Offer", + "offer": dictionaryOf(lnOffer: offer), + ] + case let .nodeId( nodeId ): @@ -3767,6 +3915,17 @@ enum BreezSDKLiquidMapper { return SendDestination.bolt11(invoice: _invoice) } + if type == "bolt12" { + guard let offerTmp = sendDestination["offer"] as? [String: Any?] else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "offer", typeName: "SendDestination")) + } + let _offer = try asLnOffer(lnOffer: offerTmp) + + guard let _receiverAmountSat = sendDestination["receiverAmountSat"] as? UInt64 else { + throw SdkError.Generic(message: errMissingMandatoryField(fieldName: "receiverAmountSat", typeName: "SendDestination")) + } + return SendDestination.bolt12(offer: _offer, receiverAmountSat: _receiverAmountSat) + } throw SdkError.Generic(message: "Unexpected type \(type) for enum SendDestination") } @@ -3788,6 +3947,15 @@ enum BreezSDKLiquidMapper { "type": "bolt11", "invoice": dictionaryOf(lnInvoice: invoice), ] + + case let .bolt12( + offer, receiverAmountSat + ): + return [ + "type": "bolt12", + "offer": dictionaryOf(lnOffer: offer), + "receiverAmountSat": receiverAmountSat, + ] } } diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 5ec41395..94d268a0 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -116,6 +116,16 @@ export interface LnInvoice { minFinalCltvExpiryDelta: number } +export interface LnOffer { + offer: string + chains: string[] + description?: string + signingPubkey?: string + minAmount?: Amount + absoluteExpiry?: number + issuer?: string +} + export interface LightningPaymentLimitsResponse { send: Limits receive: Limits @@ -408,6 +418,20 @@ export type AesSuccessActionDataResult = { reason: string } +export enum AmountVariant { + BITCOIN = "bitcoin", + CURRENCY = "currency" +} + +export type Amount = { + type: AmountVariant.BITCOIN, + amountMsat: number +} | { + type: AmountVariant.CURRENCY, + iso4217Code: string + fractionalAmount: number +} + export enum BuyBitcoinProvider { MOONPAY = "moonpay" } @@ -425,6 +449,7 @@ export enum InputTypeVariant { BITCOIN_ADDRESS = "bitcoinAddress", LIQUID_ADDRESS = "liquidAddress", BOLT11 = "bolt11", + BOLT12_OFFER = "bolt12Offer", NODE_ID = "nodeId", URL = "url", LN_URL_PAY = "lnUrlPay", @@ -442,6 +467,9 @@ export type InputType = { } | { type: InputTypeVariant.BOLT11, invoice: LnInvoice +} | { + type: InputTypeVariant.BOLT12_OFFER, + offer: LnOffer } | { type: InputTypeVariant.NODE_ID, nodeId: string @@ -627,7 +655,8 @@ export type SdkEvent = { export enum SendDestinationVariant { LIQUID_ADDRESS = "liquidAddress", - BOLT11 = "bolt11" + BOLT11 = "bolt11", + BOLT12 = "bolt12" } export type SendDestination = { @@ -636,6 +665,10 @@ export type SendDestination = { } | { type: SendDestinationVariant.BOLT11, invoice: LnInvoice +} | { + type: SendDestinationVariant.BOLT12, + offer: LnOffer + receiverAmountSat: number } export enum SuccessActionVariant {