diff --git a/lib/bindings/src/breez_liquid_sdk.udl b/lib/bindings/src/breez_liquid_sdk.udl index 745f2f8..fd3693b 100644 --- a/lib/bindings/src/breez_liquid_sdk.udl +++ b/lib/bindings/src/breez_liquid_sdk.udl @@ -132,6 +132,7 @@ enum PaymentState { "Pending", "Complete", "Failed", + "TimedOut", }; [Enum] diff --git a/lib/core/src/frb/bridge.rs b/lib/core/src/frb/bridge.rs index e7d56bc..935b2af 100644 --- a/lib/core/src/frb/bridge.rs +++ b/lib/core/src/frb/bridge.rs @@ -431,6 +431,7 @@ impl CstDecode for i32 { 1 => crate::model::PaymentState::Pending, 2 => crate::model::PaymentState::Complete, 3 => crate::model::PaymentState::Failed, + 4 => crate::model::PaymentState::TimedOut, _ => unreachable!("Invalid variant for PaymentState: {}", self), } } @@ -858,6 +859,7 @@ impl SseDecode for crate::model::PaymentState { 1 => crate::model::PaymentState::Pending, 2 => crate::model::PaymentState::Complete, 3 => crate::model::PaymentState::Failed, + 4 => crate::model::PaymentState::TimedOut, _ => unreachable!("Invalid variant for PaymentState: {}", inner), }; } @@ -1292,6 +1294,7 @@ impl flutter_rust_bridge::IntoDart for crate::model::PaymentState { Self::Pending => 1.into_dart(), Self::Complete => 2.into_dart(), Self::Failed => 3.into_dart(), + Self::TimedOut => 4.into_dart(), } } } @@ -1789,6 +1792,7 @@ impl SseEncode for crate::model::PaymentState { crate::model::PaymentState::Pending => 1, crate::model::PaymentState::Complete => 2, crate::model::PaymentState::Failed => 3, + crate::model::PaymentState::TimedOut => 4, _ => { unimplemented!(""); } diff --git a/lib/core/src/model.rs b/lib/core/src/model.rs index c22eb07..47eada2 100644 --- a/lib/core/src/model.rs +++ b/lib/core/src/model.rs @@ -378,6 +378,12 @@ pub enum PaymentState { /// /// This is the status when a swap refund was initiated and the refund tx is confirmed. Failed = 3, + + /// ## Send Swaps + /// + /// This covers the case when the swap state is still Created and the swap fails to reach the + /// Pending state in time. The TimedOut state indicates the lockup tx should never be broadcast. + TimedOut = 4, } impl ToSql for PaymentState { fn to_sql(&self) -> rusqlite::Result> { @@ -392,6 +398,7 @@ impl FromSql for PaymentState { 1 => Ok(PaymentState::Pending), 2 => Ok(PaymentState::Complete), 3 => Ok(PaymentState::Failed), + 4 => Ok(PaymentState::TimedOut), _ => Err(FromSqlError::OutOfRange(i)), }, _ => Err(FromSqlError::InvalidType), diff --git a/lib/core/src/persist/send.rs b/lib/core/src/persist/send.rs index aaf5796..955b689 100644 --- a/lib/core/src/persist/send.rs +++ b/lib/core/src/persist/send.rs @@ -46,6 +46,29 @@ impl Persister { Ok(()) } + pub(crate) fn update_send_swaps_by_state( + &self, + from_state: PaymentState, + to_state: PaymentState, + ) -> Result<()> { + let con = self.get_connection()?; + con.execute( + "UPDATE send_swaps + SET + state = :to_state + WHERE + state = :from_state + ", + named_params! { + ":from_state": from_state, + ":to_state": to_state, + }, + ) + .map_err(|_| PaymentError::PersistError)?; + + Ok(()) + } + fn list_send_swaps_query(where_clauses: Vec) -> String { let mut where_clause_str = String::new(); if !where_clauses.is_empty() { diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 96c4036..59e2fc2 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -133,6 +133,9 @@ impl LiquidSdk { async fn start(self: &Arc) -> LiquidSdkResult<()> { let mut is_started = self.is_started.write().await; let start_ts = Instant::now(); + + self.persister + .update_send_swaps_by_state(Created, TimedOut)?; self.start_background_tasks().await?; *is_started = true; @@ -265,15 +268,20 @@ impl LiquidSdk { }), (Created | Pending, Pending) => Ok(()), - (Complete | Failed, Pending) => Err(PaymentError::Generic { + (Complete | Failed | TimedOut, Pending) => Err(PaymentError::Generic { err: format!("Cannot transition from {from_state:?} to Pending state"), }), (Created | Pending, Complete) => Ok(()), - (Complete | Failed, Complete) => Err(PaymentError::Generic { + (Complete | Failed | TimedOut, Complete) => Err(PaymentError::Generic { err: format!("Cannot transition from {from_state:?} to Complete state"), }), + (Created, TimedOut) => Ok(()), + (_, TimedOut) => Err(PaymentError::Generic { + err: format!("Cannot transition from {from_state:?} to TimedOut state"), + }), + (_, Failed) => Ok(()), } } @@ -646,6 +654,7 @@ impl LiquidSdk { None => pending_send_sat += p.amount_sat, }, Created => pending_send_sat += p.amount_sat, + TimedOut => {} }, PaymentType::Receive => match p.status { Complete => confirmed_received_sat += p.amount_sat, @@ -1058,7 +1067,11 @@ impl LiquidSdk { tokio::select! { _ = &mut timeout_fut => match maybe_payment { Some(payment) => return Ok(payment), - None => return Err(PaymentError::PaymentTimeout), + None => { + debug!("Timeout occured without payment, set swap to timed out"); + self.try_handle_send_swap_update(&swap_id, TimedOut, None, None, None).await?; + return Err(PaymentError::PaymentTimeout) + }, }, event = events_stream.recv() => match event { Ok(LiquidSdkEvent::PaymentPending { details }) => match details.swap_id.clone() { diff --git a/packages/dart/lib/src/model.dart b/packages/dart/lib/src/model.dart index f45af88..fc00b76 100644 --- a/packages/dart/lib/src/model.dart +++ b/packages/dart/lib/src/model.dart @@ -331,6 +331,12 @@ enum PaymentState { /// /// This is the status when a swap refund was initiated and the refund tx is confirmed. failed, + + /// ## Send Swaps + /// + /// This covers the case when the swap state is still Created and the swap fails to reach the + /// Pending state in time. The TimedOut state indicates the lockup tx should never be broadcast. + timedOut, ; } diff --git a/packages/react-native/ios/BreezLiquidSDKMapper.swift b/packages/react-native/ios/BreezLiquidSDKMapper.swift index cc71b79..11b3f36 100644 --- a/packages/react-native/ios/BreezLiquidSDKMapper.swift +++ b/packages/react-native/ios/BreezLiquidSDKMapper.swift @@ -907,6 +907,9 @@ enum BreezLiquidSDKMapper { case "failed": return PaymentState.failed + case "timedOut": + return PaymentState.timedOut + default: throw LiquidSdkError.Generic(message: "Invalid variant \(paymentState) for enum PaymentState") } } @@ -924,6 +927,9 @@ enum BreezLiquidSDKMapper { case .failed: return "failed" + + case .timedOut: + return "timedOut" } } diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 017f846..df9acb2 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -154,7 +154,8 @@ export enum PaymentState { CREATED = "created", PENDING = "pending", COMPLETE = "complete", - FAILED = "failed" + FAILED = "failed", + TIMED_OUT = "timedOut" } export enum PaymentType {