diff --git a/Cargo.lock b/Cargo.lock index d1965c65..3d4cdf55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -568,52 +568,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "breez-sdk-core" -version = "0.4.1" -source = "git+https://github.com/breez/breez-sdk?tag=0.4.1#4eb555cf822e52a40e04eb2eb7fc606d42db5c0d" -dependencies = [ - "aes", - "anyhow", - "base64 0.13.1", - "bip21", - "cbc", - "chrono", - "const_format", - "ecies", - "env_logger 0.10.2", - "flutter_rust_bridge", - "futures", - "gl-client", - "hex", - "lazy_static", - "log", - "miniz_oxide", - "once_cell", - "openssl", - "prost", - "querystring", - "rand", - "regex", - "reqwest", - "ripemd", - "rusqlite", - "rusqlite_migration", - "serde", - "serde_json", - "serde_with", - "strum", - "strum_macros", - "tempfile", - "thiserror", - "tiny-bip39", - "tokio", - "tokio-stream", - "tonic", - "tonic-build", - "zbase32", -] - [[package]] name = "breez-sdk-core" version = "0.4.1" @@ -662,11 +616,11 @@ dependencies = [ [[package]] name = "breez-sdk-mock" -version = "0.4.1" +version = "0.4.2-rc1" dependencies = [ "anyhow", "bitcoin 0.30.2", - "breez-sdk-core 0.4.1 (git+https://github.com/breez/breez-sdk?tag=0.4.1)", + "breez-sdk-core", "chrono", "hex", "lazy_static", @@ -793,7 +747,7 @@ dependencies = [ [[package]] name = "chameleon" version = "0.1.0" -source = "git+https://github.com/getlipa/wild?tag=v1.23.0#4543426e91ae5470893a6525fed7650b351d35da" +source = "git+https://github.com/getlipa/wild?tag=v1.24.0#7fce8e6bb9cb4d672392ed6a2193b64a0d203777" dependencies = [ "graphql", "honeybadger", @@ -997,7 +951,7 @@ dependencies = [ [[package]] name = "crow" version = "0.1.0" -source = "git+https://github.com/getlipa/wild?tag=v1.23.0#4543426e91ae5470893a6525fed7650b351d35da" +source = "git+https://github.com/getlipa/wild?tag=v1.24.0#7fce8e6bb9cb4d672392ed6a2193b64a0d203777" dependencies = [ "graphql", "honeybadger", @@ -1648,7 +1602,7 @@ dependencies = [ [[package]] name = "graphql" version = "0.1.0" -source = "git+https://github.com/getlipa/wild?tag=v1.23.0#4543426e91ae5470893a6525fed7650b351d35da" +source = "git+https://github.com/getlipa/wild?tag=v1.24.0#7fce8e6bb9cb4d672392ed6a2193b64a0d203777" dependencies = [ "chrono", "graphql_client", @@ -1846,7 +1800,7 @@ dependencies = [ [[package]] name = "honeybadger" version = "1.0.1" -source = "git+https://github.com/getlipa/wild?tag=v1.23.0#4543426e91ae5470893a6525fed7650b351d35da" +source = "git+https://github.com/getlipa/wild?tag=v1.24.0#7fce8e6bb9cb4d672392ed6a2193b64a0d203777" dependencies = [ "base64 0.22.0", "bdk", @@ -2122,9 +2076,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" @@ -2256,7 +2210,7 @@ dependencies = [ "anyhow", "bip39", "bitcoin 0.30.2", - "breez-sdk-core 0.4.1 (git+https://github.com/breez/breez-sdk?tag=0.4.2-rc1)", + "breez-sdk-core", "breez-sdk-mock", "camino", "chameleon", @@ -2321,9 +2275,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru-cache" @@ -2704,7 +2658,7 @@ dependencies = [ [[package]] name = "parrot" version = "0.1.0" -source = "git+https://github.com/getlipa/wild?tag=v1.23.0#4543426e91ae5470893a6525fed7650b351d35da" +source = "git+https://github.com/getlipa/wild?tag=v1.24.0#7fce8e6bb9cb4d672392ed6a2193b64a0d203777" dependencies = [ "graphql", "honeybadger", @@ -2806,7 +2760,7 @@ dependencies = [ [[package]] name = "pigeon" version = "0.1.0" -source = "git+https://github.com/getlipa/wild?tag=v1.23.0#4543426e91ae5470893a6525fed7650b351d35da" +source = "git+https://github.com/getlipa/wild?tag=v1.24.0#7fce8e6bb9cb4d672392ed6a2193b64a0d203777" dependencies = [ "graphql", "honeybadger", @@ -3610,9 +3564,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -3826,7 +3780,7 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "squirrel" version = "0.1.0" -source = "git+https://github.com/getlipa/wild?tag=v1.23.0#4543426e91ae5470893a6525fed7650b351d35da" +source = "git+https://github.com/getlipa/wild?tag=v1.24.0#7fce8e6bb9cb4d672392ed6a2193b64a0d203777" dependencies = [ "bdk", "graphql", @@ -4553,9 +4507,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" dependencies = [ "serde", "sha1_smol", diff --git a/Cargo.toml b/Cargo.toml index 5f6c6b8b..94d3b311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,19 +19,19 @@ parser = { path = "parser" } pocketclient = { path = "pocketclient", optional = true } pocketclient-mock = { path = "mock/pocketclient", optional = true } -chameleon = { git = "https://github.com/getlipa/wild", tag = "v1.23.0", optional = true } +chameleon = { git = "https://github.com/getlipa/wild", tag = "v1.24.0", optional = true } chameleon-mock = { path = "mock/wild/chameleon", optional = true } -crow = { git = "https://github.com/getlipa/wild", tag = "v1.23.0", optional = true } +crow = { git = "https://github.com/getlipa/wild", tag = "v1.24.0", optional = true } crow-mock = { path = "mock/wild/crow", optional = true } -graphql = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } -honeybadger = { git = "https://github.com/getlipa/wild", tag = "v1.23.0", optional = true } +graphql = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } +honeybadger = { git = "https://github.com/getlipa/wild", tag = "v1.24.0", optional = true } honeybadger-mock = { path = "mock/wild/honeybadger", optional = true } -parrot = { git = "https://github.com/getlipa/wild", tag = "v1.23.0", optional = true } +parrot = { git = "https://github.com/getlipa/wild", tag = "v1.24.0", optional = true } parrot-mock = { path = "mock/wild/parrot", optional = true } perro = { git = "https://github.com/getlipa/perro", tag = "v1.2.0" } -pigeon = { git = "https://github.com/getlipa/wild", tag = "v1.23.0", optional = true } +pigeon = { git = "https://github.com/getlipa/wild", tag = "v1.24.0", optional = true } pigeon-mock = { path = "mock/wild/pigeon", optional = true } -squirrel = { git = "https://github.com/getlipa/wild", tag = "v1.23.0", optional = true } +squirrel = { git = "https://github.com/getlipa/wild", tag = "v1.24.0", optional = true } squirrel-mock = { path = "mock/wild/squirrel", optional = true } breez-sdk-core = { git = "https://github.com/breez/breez-sdk", tag = "0.4.2-rc1", optional = true } @@ -46,7 +46,7 @@ email_address = "0.2.4" file-rotate = "0.7.6" hex = "0.4.3" iban_validate = "4.0.1" -log = "0.4.21" +log = "0.4.22" num_enum = "0.7.2" phonenumber = "0.3.5" rand = "0.8.5" @@ -60,7 +60,7 @@ simplelog = { version = "0.12.2" } thiserror = "1.0.61" tokio = { version = "1.38.0", features = ["rt-multi-thread", "time", "sync"] } uniffi = "0.28.0" -uuid = { version = "1.8.0", features = ["v5"] } +uuid = { version = "1.9.1", features = ["v5"] } [features] default = ["dep:breez-sdk-core", "dep:chameleon", "dep:crow", "dep:honeybadger", "dep:parrot", "dep:pigeon", "dep:squirrel", "dep:pocketclient"] @@ -95,7 +95,7 @@ rustyline = { version = "14.0.0", features = ["derive"] } serial_test = { version = "3.1.1", features = ["file_locks"] } strip-ansi-escapes = "0.2.0" thousands = "0.2.0" -lazy_static = "1.4.0" +lazy_static = "1.5.0" [build-dependencies] camino = "1.1.7" diff --git a/examples/node/cli.rs b/examples/node/cli.rs index ea6032a6..1c594799 100644 --- a/examples/node/cli.rs +++ b/examples/node/cli.rs @@ -1238,6 +1238,14 @@ fn print_activity(activity: Activity) -> Result<()> { println!(" Swap: {swap_info:?}"); Ok(()) } + Activity::ReverseSwap { + outgoing_payment_info, + reverse_swap_info, + } => { + print_outgoing_payment(outgoing_payment_info)?; + println!(" Reverse Swap: {reverse_swap_info:?}"); + Ok(()) + } Activity::ChannelClose { channel_close_info } => print_channel_close(channel_close_info), } } diff --git a/examples/node/overview.rs b/examples/node/overview.rs index 4cc485eb..14dd1a16 100644 --- a/examples/node/overview.rs +++ b/examples/node/overview.rs @@ -106,6 +106,10 @@ fn print_activity(activity: Activity) -> Result<()> { // TODO: implement print of pending swap Ok(()) } + Activity::ReverseSwap { + outgoing_payment_info, + .. + } => print_outgoing_payment(outgoing_payment_info), Activity::ChannelClose { channel_close_info } => print_channel_close(channel_close_info), } } diff --git a/mock/breez-sdk/Cargo.toml b/mock/breez-sdk/Cargo.toml index 8c5f63f8..227fec7a 100644 --- a/mock/breez-sdk/Cargo.toml +++ b/mock/breez-sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "breez-sdk-mock" -version = "0.4.1" +version = "0.4.2-rc1" edition = "2021" [lib] @@ -10,7 +10,7 @@ name = "breez_sdk_core" tokio = { version = "1.37.0", features = ["rt-multi-thread", "time", "sync"] } anyhow = { version = "1.0.81", features = [] } bitcoin = { version = "0.30.2", default-features = false } -breez-sdk-core = { git = "https://github.com/breez/breez-sdk", tag = "0.4.1" } +breez-sdk-core = { git = "https://github.com/breez/breez-sdk", tag = "0.4.2-rc1" } chrono = { version = "0.4", features = [] } hex = "0.4.3" rand = { version = "0.8.5", features = [] } diff --git a/mock/breez-sdk/src/lib.rs b/mock/breez-sdk/src/lib.rs index 6f854f15..a3f528b1 100644 --- a/mock/breez-sdk/src/lib.rs +++ b/mock/breez-sdk/src/lib.rs @@ -58,15 +58,15 @@ pub use breez_sdk_core::{ PaymentTypeFilter, PrepareOnchainPaymentRequest, PrepareOnchainPaymentResponse, PrepareRedeemOnchainFundsRequest, PrepareRefundRequest, ReceiveOnchainRequest, ReceivePaymentRequest, ReceivePaymentResponse, RedeemOnchainFundsRequest, RefundRequest, - ReportIssueRequest, ReportPaymentFailureDetails, ReverseSwapFeesRequest, SendOnchainRequest, - SendPaymentRequest, SignMessageRequest, SwapAmountType, SwapStatus, UnspentTransactionOutput, + ReportIssueRequest, ReportPaymentFailureDetails, ReverseSwapFeesRequest, ReverseSwapStatus, + SendOnchainRequest, SendPaymentRequest, SignMessageRequest, SwapAmountType, SwapStatus, + UnspentTransactionOutput, }; use breez_sdk_core::{ ChannelState, Config, LspInformation, NodeState, OpenChannelFeeResponse, PayOnchainResponse, PrepareRedeemOnchainFundsResponse, PrepareRefundResponse, RecommendedFees, RedeemOnchainFundsResponse, RefundResponse, ReverseSwapInfo, ReverseSwapPairInfo, - ReverseSwapStatus, SendPaymentResponse, ServiceHealthCheckResponse, SignMessageResponse, - SwapInfo, + SendPaymentResponse, ServiceHealthCheckResponse, SignMessageResponse, SwapInfo, }; use chrono::Utc; use hex::FromHex; diff --git a/mock/wild/chameleon/Cargo.toml b/mock/wild/chameleon/Cargo.toml index e660ab96..f769ea7a 100644 --- a/mock/wild/chameleon/Cargo.toml +++ b/mock/wild/chameleon/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" name = "chameleon" [dependencies] -chameleon = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } -graphql = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } +chameleon = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } +graphql = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } honeybadger-mock = { path = "../honeybadger" } lazy_static = "1.4.0" rand = "0.8.5" diff --git a/mock/wild/crow/Cargo.toml b/mock/wild/crow/Cargo.toml index 5b6a0dfd..6cf0c6c6 100644 --- a/mock/wild/crow/Cargo.toml +++ b/mock/wild/crow/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" name = "crow" [dependencies] -crow = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } -graphql = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } +crow = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } +graphql = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } honeybadger-mock = { path = "../honeybadger" } isocountry = { version = "0.3.2" } isolanguage-1 = { version = "0.2.2" } diff --git a/mock/wild/honeybadger/Cargo.toml b/mock/wild/honeybadger/Cargo.toml index 77197f8c..0881a1a1 100644 --- a/mock/wild/honeybadger/Cargo.toml +++ b/mock/wild/honeybadger/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" name = "honeybadger" [dependencies] -graphql = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } -honeybadger = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } +graphql = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } +honeybadger = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } rand = "0.8.5" secp256k1 = { version = "0.27.0", features = ["global-context"] } tokio = "1.37.0" diff --git a/mock/wild/parrot/Cargo.toml b/mock/wild/parrot/Cargo.toml index ebb1fd52..5c1596fa 100644 --- a/mock/wild/parrot/Cargo.toml +++ b/mock/wild/parrot/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" name = "parrot" [dependencies] -graphql = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } +graphql = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } honeybadger-mock = { path = "../honeybadger" } -parrot = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } +parrot = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } diff --git a/mock/wild/pigeon/Cargo.toml b/mock/wild/pigeon/Cargo.toml index bc2bc24a..64d18e56 100644 --- a/mock/wild/pigeon/Cargo.toml +++ b/mock/wild/pigeon/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" name = "pigeon" [dependencies] -graphql = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } +graphql = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } honeybadger-mock = { path = "../honeybadger" } lazy_static = "1.4.0" diff --git a/mock/wild/pigeon/src/lib.rs b/mock/wild/pigeon/src/lib.rs index ff4c7ba1..9c75f691 100644 --- a/mock/wild/pigeon/src/lib.rs +++ b/mock/wild/pigeon/src/lib.rs @@ -12,7 +12,7 @@ pub async fn submit_lnurl_pay_invoice( _backend_url: &str, _auth: &Auth, _id: String, - _invoice: String, + _invoice: Option, ) -> graphql::Result<()> { Ok(()) } diff --git a/mock/wild/squirrel/Cargo.toml b/mock/wild/squirrel/Cargo.toml index 70f60142..d0037157 100644 --- a/mock/wild/squirrel/Cargo.toml +++ b/mock/wild/squirrel/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" name = "squirrel" [dependencies] -graphql = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } +graphql = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } honeybadger-mock = { path = "../honeybadger" } -squirrel = { git = "https://github.com/getlipa/wild", tag = "v1.23.0" } +squirrel = { git = "https://github.com/getlipa/wild", tag = "v1.24.0" } diff --git a/pocketclient/Cargo.toml b/pocketclient/Cargo.toml index 5bcc2617..4cd317ec 100644 --- a/pocketclient/Cargo.toml +++ b/pocketclient/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] chrono = { version = "0.4.38", default-features = false, features = ["serde"] } -log = "0.4.21" +log = "0.4.22" perro = { git = "https://github.com/getlipa/perro", tag = "v1.2.0" } reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } serde = { version = "1.0", features = ["derive"] } diff --git a/src/activity.rs b/src/activity.rs index 3b435fe4..5f01e61b 100644 --- a/src/activity.rs +++ b/src/activity.rs @@ -1,6 +1,8 @@ use crate::payment::{IncomingPaymentInfo, OutgoingPaymentInfo, PaymentInfo}; use crate::{Amount, OfferKind, SwapInfo, TzTime}; +use crate::reverse_swap::ReverseSwapInfo; +use breez_sdk_core::ReverseSwapStatus; use std::time::SystemTime; /// Information about **all** pending and **only** requested completed activities. @@ -31,6 +33,10 @@ pub enum Activity { incoming_payment_info: Option, swap_info: SwapInfo, }, + ReverseSwap { + outgoing_payment_info: OutgoingPaymentInfo, + reverse_swap_info: ReverseSwapInfo, + }, ChannelClose { channel_close_info: ChannelCloseInfo, }, @@ -53,6 +59,10 @@ impl Activity { incoming_payment_info, .. } => incoming_payment_info.as_ref().map(|i| &i.payment_info), + Activity::ReverseSwap { + outgoing_payment_info, + .. + } => Some(&outgoing_payment_info.payment_info), Activity::ChannelClose { .. } => None, } } @@ -80,6 +90,14 @@ impl Activity { } pub(crate) fn is_pending(&self) -> bool { + if let Activity::ReverseSwap { + reverse_swap_info, .. + } = self + { + return reverse_swap_info.status == ReverseSwapStatus::Initial + || reverse_swap_info.status == ReverseSwapStatus::InProgress + || reverse_swap_info.status == ReverseSwapStatus::CompletedSeen; + } if let Some(payment_info) = self.get_payment_info() { return payment_info.payment_state.is_pending(); } diff --git a/src/amount.rs b/src/amount.rs index 0042f226..0212fcf7 100644 --- a/src/amount.rs +++ b/src/amount.rs @@ -20,13 +20,14 @@ pub(crate) struct Msats { } impl Msats { - #[allow(dead_code)] - pub(crate) fn sats_round_up(&self) -> u64 { - round(self.msats, Rounding::Up) + pub(crate) fn sats_round_up(&self) -> Sats { + let sats = round(self.msats, Rounding::Up); + Sats::new(sats) } - pub(crate) fn sats_round_down(&self) -> u64 { - round(self.msats, Rounding::Down) + pub(crate) fn sats_round_down(&self) -> Sats { + let sats = round(self.msats, Rounding::Down); + Sats::new(sats) } } @@ -54,6 +55,19 @@ impl AsSats for u32 { } } +pub(crate) struct Permyriad(pub u16); + +impl Permyriad { + pub fn of(&self, sats: &Sats) -> Msats { + let msats = sats.sats * (self.0 as u64) / 10; + Msats { msats } + } + + pub fn to_percentage(&self) -> f64 { + (self.0 as f64) / 100_f64 + } +} + /// A fiat value accompanied by the exchange rate that was used to get it. #[derive(Debug, PartialEq, Clone, Eq)] pub struct FiatValue { @@ -191,7 +205,17 @@ mod tests { fn rounding_msats_to_sats() { let msats = 12349123u64.as_msats(); - assert_eq!(msats.sats_round_down(), 12349); - assert_eq!(msats.sats_round_up(), 12350); + assert_eq!(msats.sats_round_down().sats, 12349); + assert_eq!(msats.sats_round_up().sats, 12350); } + + #[test] + #[rustfmt::skip] + fn permyriad() { + assert_eq!(Permyriad(10000).of(&Sats::new(1234)).msats, 1234000); + assert_eq!(Permyriad( 1000).of(&Sats::new(1234)).msats, 123400); + assert_eq!(Permyriad( 100).of(&Sats::new(1234)).msats, 12340); + assert_eq!(Permyriad( 10).of(&Sats::new(1234)).msats, 1234); + assert_eq!(Permyriad( 1).of(&Sats::new(1234)).msats, 123); + } } diff --git a/src/lib.rs b/src/lib.rs index cdcc1de0..a20068ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ mod payment; mod phone_number; mod random; mod recovery; +mod reverse_swap; mod sanitize_input; mod secret; mod swap; @@ -44,7 +45,7 @@ mod util; pub use crate::activity::{Activity, ChannelCloseInfo, ChannelCloseState, ListActivitiesResponse}; pub use crate::amount::{Amount, FiatValue}; -use crate::amount::{AsSats, Msats, Sats, ToAmount}; +use crate::amount::{AsSats, Msats, Permyriad, Sats, ToAmount}; use crate::analytics::{derive_analytics_keys, AnalyticsConfig, AnalyticsInterceptor}; pub use crate::analytics::{InvoiceCreationMetadata, PaymentMetadata}; use crate::async_runtime::AsyncRuntime; @@ -82,6 +83,7 @@ pub use crate::payment::{ pub use crate::phone_number::PhoneNumber; use crate::phone_number::PhoneNumberPrefixParser; pub use crate::recovery::recover_lightning_node; +pub use crate::reverse_swap::ReverseSwapInfo; pub use crate::secret::{generate_secret, mnemonic_to_secret, words_by_prefix, Secret}; pub use crate::swap::{ FailedSwapInfo, ResolveFailedSwapInfo, SwapAddressInfo, SwapInfo, SwapToLightningFees, @@ -97,6 +99,7 @@ use pocketclient::PocketClient; pub use breez_sdk_core::error::ReceiveOnchainError as SwapError; use breez_sdk_core::error::{ReceiveOnchainError, SendPaymentError}; pub use breez_sdk_core::HealthCheckStatus as BreezHealthCheckStatus; +pub use breez_sdk_core::ReverseSwapStatus; use breez_sdk_core::{ parse, parse_invoice, BitcoinAddressData, BreezServices, ClosedChannelPaymentDetails, ConnectRequest, EventListener, GreenlightCredentials, GreenlightNodeConfig, InputType, @@ -263,7 +266,7 @@ pub struct ClearWalletInfo { prepare_response: PrepareOnchainPaymentResponse, } -const MAX_FEE_PERMYRIAD: u16 = 150; +const MAX_FEE_PERMYRIAD: Permyriad = Permyriad(150); const EXEMPT_FEE: Sats = Sats::new(21); /// The main class/struct of this library. Constructing an instance will initiate the Lightning node and @@ -757,11 +760,10 @@ impl LightningNode { let max_fee_msats = match routing_fee_mode { MaxRoutingFeeMode::Relative { max_fee_permyriad } => { - (amount_sat * (max_fee_permyriad as u64) / 10000).as_sats() + Permyriad(max_fee_permyriad).of(&amount).msats } - MaxRoutingFeeMode::Absolute { max_fee_amount } => max_fee_amount.sats.as_sats(), - } - .msats; + MaxRoutingFeeMode::Absolute { max_fee_amount } => max_fee_amount.sats.as_sats().msats, + }; let node_state = self.sdk.node_info().map_to_runtime_error( RuntimeErrorCode::NodeUnavailable, @@ -1148,6 +1150,7 @@ impl LightningNode { incoming_payment_info: None, .. } => invalid_input!("Pending swap was found"), + Activity::ReverseSwap { .. } => invalid_input!("ReverseSwap was found"), Activity::ChannelClose { .. } => invalid_input!("ChannelClose was found"), }; } @@ -1183,6 +1186,10 @@ impl LightningNode { } => Ok(outgoing_payment_info), Activity::OfferClaim { .. } => invalid_input!("OfferClaim was found"), Activity::Swap { .. } => invalid_input!("Swap was found"), + Activity::ReverseSwap { + outgoing_payment_info, + .. + } => Ok(outgoing_payment_info), Activity::ChannelClose { .. } => invalid_input!("ChannelClose was found"), } } @@ -1308,6 +1315,23 @@ impl LightningNode { incoming_payment_info: Some(incoming_payment_info), swap_info, }) + } else if let Some(ref s) = payment_details.reverse_swap_info { + let reverse_swap_info = ReverseSwapInfo { + paid_onchain_amount: s.onchain_amount_sat.as_sats().to_amount_up(&exchange_rate), + claim_txid: s.claim_txid.clone(), + status: s.status, + }; + let outgoing_payment_info = OutgoingPaymentInfo::new( + breez_payment, + &exchange_rate, + tz_config, + personal_note, + &self.environment.lipa_lightning_domain, + )?; + Ok(Activity::ReverseSwap { + outgoing_payment_info, + reverse_swap_info, + }) } else if breez_payment.payment_type == breez_sdk_core::PaymentType::Received { let incoming_payment_info = IncomingPaymentInfo::new( breez_payment, @@ -2378,11 +2402,13 @@ impl LightningNode { )? .channels_balance_msat .as_msats() - .sats_round_down(); + .sats_round_down() + .sats; let exchange_rate = self.get_exchange_rate(); // Accomodating lightning network routing fees. - let min = limits.min + limits.min * (MAX_FEE_PERMYRIAD as u64) / 10000; + let routing_fee = MAX_FEE_PERMYRIAD.of(&limits.min.as_sats()).sats_round_up(); + let min = limits.min + routing_fee.sats; let range_hit = match balance_sat { balance_sat if balance_sat < min => RangeHit::Below { min: min.as_sats().to_amount_up(&exchange_rate), @@ -2656,7 +2682,7 @@ pub(crate) async fn start_sdk( .working_dir .clone_from(&config.local_persistence_path); breez_config.exemptfee_msat = EXEMPT_FEE.msats; - breez_config.maxfee_percent = MAX_FEE_PERMYRIAD as f64 / 100_f64; + breez_config.maxfee_percent = MAX_FEE_PERMYRIAD.to_percentage(); let connect_request = ConnectRequest { config: breez_config, seed: config.seed.clone(), @@ -2739,13 +2765,14 @@ fn get_payment_max_routing_fee_mode( amount_sat: u64, exchange_rate: &Option, ) -> MaxRoutingFeeMode { - if amount_sat * (MAX_FEE_PERMYRIAD as u64) / 10 < EXEMPT_FEE.msats { + let relative_fee = MAX_FEE_PERMYRIAD.of(&amount_sat.as_sats()); + if relative_fee.msats < EXEMPT_FEE.msats { MaxRoutingFeeMode::Absolute { max_fee_amount: EXEMPT_FEE.to_amount_up(exchange_rate), } } else { MaxRoutingFeeMode::Relative { - max_fee_permyriad: MAX_FEE_PERMYRIAD, + max_fee_permyriad: MAX_FEE_PERMYRIAD.0, } } } @@ -2899,7 +2926,7 @@ mod tests { #[test] fn test_get_payment_max_routing_fee_mode_absolute() { let max_routing_mode = get_payment_max_routing_fee_mode( - EXEMPT_FEE.msats / ((MAX_FEE_PERMYRIAD as u64) / 10) - 1, + EXEMPT_FEE.msats / ((MAX_FEE_PERMYRIAD.0 as u64) / 10) - 1, &None, ); @@ -2916,13 +2943,13 @@ mod tests { #[test] fn test_get_payment_max_routing_fee_mode_relative() { let max_routing_mode = get_payment_max_routing_fee_mode( - EXEMPT_FEE.msats / ((MAX_FEE_PERMYRIAD as u64) / 10), + EXEMPT_FEE.msats / ((MAX_FEE_PERMYRIAD.0 as u64) / 10), &None, ); match max_routing_mode { MaxRoutingFeeMode::Relative { max_fee_permyriad } => { - assert_eq!(max_fee_permyriad, MAX_FEE_PERMYRIAD); + assert_eq!(max_fee_permyriad, MAX_FEE_PERMYRIAD.0); } _ => { panic!("Unexpected variant"); diff --git a/src/lipalightninglib.udl b/src/lipalightninglib.udl index 30c33c0e..e11203e4 100644 --- a/src/lipalightninglib.udl +++ b/src/lipalightninglib.udl @@ -350,6 +350,7 @@ interface Activity { OutgoingPayment(OutgoingPaymentInfo outgoing_payment_info); OfferClaim(IncomingPaymentInfo incoming_payment_info, OfferKind offer_kind); Swap(IncomingPaymentInfo? incoming_payment_info, SwapInfo swap_info); + ReverseSwap(OutgoingPaymentInfo outgoing_payment_info, ReverseSwapInfo reverse_swap_info); ChannelClose(ChannelCloseInfo channel_close_info); }; @@ -549,6 +550,20 @@ dictionary SwapInfo { Amount paid_amount; }; +dictionary ReverseSwapInfo { + Amount paid_onchain_amount; + string? claim_txid; + ReverseSwapStatus status; +}; + +enum ReverseSwapStatus { + "Initial", + "InProgress", + "Cancelled", + "CompletedSeen", + "CompletedConfirmed", +}; + enum PaymentSource { "Camera", "Clipboard", diff --git a/src/notification_handling.rs b/src/notification_handling.rs index e5a24e4c..f4cbae14 100644 --- a/src/notification_handling.rs +++ b/src/notification_handling.rs @@ -307,8 +307,13 @@ fn handle_lnurl_pay_request_notification( NotificationHandlingErrorCode::NodeUnavailable, "Failed to query open channel fees", )?; + + let strong_typed_seed = get_strong_typed_seed(&config)?; + let environment = get_environment(&config)?; + if let Some(fee_msat) = open_channel_fee_response.fee_msat { if fee_msat > 0 { + report_insuficcient_inbound_liquidity(rt, &environment, &strong_typed_seed, &data.id)?; runtime_error!( NotificationHandlingErrorCode::InsufficientInboundLiquidity, "Rejecting an inbound LNURL-pay payment because of insufficient inbound liquidity" @@ -316,8 +321,6 @@ fn handle_lnurl_pay_request_notification( } } - let strong_typed_seed = get_strong_typed_seed(&config)?; - let environment = get_environment(&config)?; let auth = build_auth(&strong_typed_seed, &environment.backend_url).map_to_runtime_error( NotificationHandlingErrorCode::LipaServiceUnavailable, "Failed to authenticate against backend", @@ -344,13 +347,13 @@ fn handle_lnurl_pay_request_notification( NotificationHandlingErrorCode::NodeUnavailable, "Failed to create invoice", )?; - ensure!( - receive_payment_result.opening_fee_msat.is_none(), - runtime_error( + if receive_payment_result.opening_fee_msat.is_some() { + report_insuficcient_inbound_liquidity(rt, &environment, &strong_typed_seed, &data.id)?; + runtime_error!( NotificationHandlingErrorCode::InsufficientInboundLiquidity, "Rejecting an inbound LNURL-pay payment because of insufficient inbound liquidity" ) - ); + } // Invoice is not persisted in invoices table because we are not interested in unpaid invoices // resulting from incoming LNURL payments @@ -394,15 +397,36 @@ fn handle_lnurl_pay_request_notification( &environment.backend_url, &async_auth, data.id, - receive_payment_result.ln_invoice.bolt11, + Some(receive_payment_result.ln_invoice.bolt11), )) .map_runtime_error_to(NotificationHandlingErrorCode::LipaServiceUnavailable)?; Ok(Notification::LnurlInvoiceCreated { - amount_sat: data.amount_msat.as_msats().sats_round_down(), + amount_sat: data.amount_msat.as_msats().sats_round_down().sats, }) } +fn report_insuficcient_inbound_liquidity( + rt: AsyncRuntime, + environment: &Environment, + strong_typed_seed: &[u8; 64], + id: &str, +) -> NotificationHandlingResult<()> { + let async_auth = build_async_auth(strong_typed_seed, &environment.backend_url) + .map_to_runtime_error( + NotificationHandlingErrorCode::LipaServiceUnavailable, + "Failed to authenticate against backend", + )?; + rt.handle() + .block_on(submit_lnurl_pay_invoice( + &environment.backend_url, + &async_auth, + id.to_string(), + None, + )) + .map_runtime_error_to(NotificationHandlingErrorCode::LipaServiceUnavailable) +} + fn get_confirmed_payment( rt: &AsyncRuntime, sdk: &Arc, diff --git a/src/reverse_swap.rs b/src/reverse_swap.rs new file mode 100644 index 00000000..af4debb5 --- /dev/null +++ b/src/reverse_swap.rs @@ -0,0 +1,14 @@ +use crate::Amount; +use breez_sdk_core::ReverseSwapStatus; + +/// Information about a successful reverse swap. +#[derive(PartialEq, Debug)] +pub struct ReverseSwapInfo { + pub paid_onchain_amount: Amount, + /// The tx id of the claim tx, which is the final tx in the reverse swap flow, which send funds + /// to the targeted on-chain address. + /// + /// It will only be present once the claim tx is broacasted. + pub claim_txid: Option, + pub status: ReverseSwapStatus, +}