|
| 1 | +use std::time::UNIX_EPOCH; |
| 2 | + |
| 3 | +use breez_sdk_common::input; |
| 4 | +use spark_wallet::{ |
| 5 | + CoopExitFeeQuote, CoopExitSpeedFeeQuote, ExitSpeed, LightningSendPayment, LightningSendStatus, |
| 6 | + Network as SparkNetwork, SspUserRequest, TokenTransactionStatus, TransferDirection, |
| 7 | + TransferStatus, TransferType, WalletTransfer, |
| 8 | +}; |
| 9 | + |
| 10 | +use crate::{ |
| 11 | + Fee, Network, OnchainConfirmationSpeed, Payment, PaymentDetails, PaymentMethod, PaymentStatus, |
| 12 | + PaymentType, SdkError, SendOnchainFeeQuote, SendOnchainSpeedFeeQuote, TokenBalance, |
| 13 | + TokenMetadata, |
| 14 | +}; |
| 15 | + |
| 16 | +impl From<TransferType> for PaymentMethod { |
| 17 | + fn from(value: TransferType) -> Self { |
| 18 | + match value { |
| 19 | + TransferType::PreimageSwap => PaymentMethod::Lightning, |
| 20 | + TransferType::CooperativeExit => PaymentMethod::Withdraw, |
| 21 | + TransferType::Transfer => PaymentMethod::Spark, |
| 22 | + TransferType::UtxoSwap => PaymentMethod::Deposit, |
| 23 | + _ => PaymentMethod::Unknown, |
| 24 | + } |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +impl TryFrom<SspUserRequest> for PaymentDetails { |
| 29 | + type Error = SdkError; |
| 30 | + fn try_from(user_request: SspUserRequest) -> Result<Self, Self::Error> { |
| 31 | + let details = match user_request { |
| 32 | + SspUserRequest::CoopExitRequest(request) => PaymentDetails::Withdraw { |
| 33 | + tx_id: request.coop_exit_txid, |
| 34 | + }, |
| 35 | + SspUserRequest::LeavesSwapRequest(_) => PaymentDetails::Spark, |
| 36 | + SspUserRequest::LightningReceiveRequest(request) => { |
| 37 | + let invoice_details = input::parse_invoice(&request.invoice.encoded_invoice) |
| 38 | + .ok_or(SdkError::Generic( |
| 39 | + "Invalid invoice in SspUserRequest::LightningReceiveRequest".to_string(), |
| 40 | + ))?; |
| 41 | + PaymentDetails::Lightning { |
| 42 | + description: invoice_details.description, |
| 43 | + preimage: request.lightning_receive_payment_preimage, |
| 44 | + invoice: request.invoice.encoded_invoice, |
| 45 | + payment_hash: request.invoice.payment_hash, |
| 46 | + destination_pubkey: invoice_details.payee_pubkey, |
| 47 | + lnurl_pay_info: None, |
| 48 | + } |
| 49 | + } |
| 50 | + SspUserRequest::LightningSendRequest(request) => { |
| 51 | + let invoice_details = |
| 52 | + input::parse_invoice(&request.encoded_invoice).ok_or(SdkError::Generic( |
| 53 | + "Invalid invoice in SspUserRequest::LightningSendRequest".to_string(), |
| 54 | + ))?; |
| 55 | + PaymentDetails::Lightning { |
| 56 | + description: invoice_details.description, |
| 57 | + preimage: request.lightning_send_payment_preimage, |
| 58 | + invoice: request.encoded_invoice, |
| 59 | + payment_hash: invoice_details.payment_hash, |
| 60 | + destination_pubkey: invoice_details.payee_pubkey, |
| 61 | + lnurl_pay_info: None, |
| 62 | + } |
| 63 | + } |
| 64 | + SspUserRequest::ClaimStaticDeposit(request) => PaymentDetails::Deposit { |
| 65 | + tx_id: request.transaction_id, |
| 66 | + }, |
| 67 | + }; |
| 68 | + Ok(details) |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +impl TryFrom<WalletTransfer> for Payment { |
| 73 | + type Error = SdkError; |
| 74 | + fn try_from(transfer: WalletTransfer) -> Result<Self, Self::Error> { |
| 75 | + let payment_type = match transfer.direction { |
| 76 | + TransferDirection::Incoming => PaymentType::Receive, |
| 77 | + TransferDirection::Outgoing => PaymentType::Send, |
| 78 | + }; |
| 79 | + let mut status = match transfer.status { |
| 80 | + TransferStatus::Completed => PaymentStatus::Completed, |
| 81 | + TransferStatus::SenderKeyTweaked |
| 82 | + if transfer.direction == TransferDirection::Outgoing => |
| 83 | + { |
| 84 | + PaymentStatus::Completed |
| 85 | + } |
| 86 | + TransferStatus::Expired | TransferStatus::Returned => PaymentStatus::Failed, |
| 87 | + _ => PaymentStatus::Pending, |
| 88 | + }; |
| 89 | + let (fees_sat, mut amount_sat): (u64, u64) = match transfer.clone().user_request { |
| 90 | + Some(user_request) => match user_request { |
| 91 | + SspUserRequest::LightningSendRequest(r) => { |
| 92 | + // TODO: if we have the preimage it is not pending. This is a workaround |
| 93 | + // until spark will implement incremental syncing based on updated time. |
| 94 | + if r.lightning_send_payment_preimage.is_some() { |
| 95 | + status = PaymentStatus::Completed; |
| 96 | + } |
| 97 | + let fee_sat = r.fee.as_sats().unwrap_or(0); |
| 98 | + (fee_sat, transfer.total_value_sat.saturating_sub(fee_sat)) |
| 99 | + } |
| 100 | + SspUserRequest::CoopExitRequest(r) => { |
| 101 | + let fee_sat = r |
| 102 | + .fee |
| 103 | + .as_sats() |
| 104 | + .unwrap_or(0) |
| 105 | + .saturating_add(r.l1_broadcast_fee.as_sats().unwrap_or(0)); |
| 106 | + (fee_sat, transfer.total_value_sat.saturating_sub(fee_sat)) |
| 107 | + } |
| 108 | + SspUserRequest::ClaimStaticDeposit(r) => { |
| 109 | + let fee_sat = r.max_fee.as_sats().unwrap_or(0); |
| 110 | + (fee_sat, transfer.total_value_sat) |
| 111 | + } |
| 112 | + _ => (0, transfer.total_value_sat), |
| 113 | + }, |
| 114 | + None => (0, transfer.total_value_sat), |
| 115 | + }; |
| 116 | + |
| 117 | + let details: Option<PaymentDetails> = if let Some(user_request) = transfer.user_request { |
| 118 | + Some(user_request.try_into()?) |
| 119 | + } else { |
| 120 | + // in case we have a completed status without user object we want |
| 121 | + // to keep syncing this payment |
| 122 | + if status == PaymentStatus::Completed |
| 123 | + && [ |
| 124 | + TransferType::CooperativeExit, |
| 125 | + TransferType::PreimageSwap, |
| 126 | + TransferType::UtxoSwap, |
| 127 | + ] |
| 128 | + .contains(&transfer.transfer_type) |
| 129 | + { |
| 130 | + status = PaymentStatus::Pending; |
| 131 | + } |
| 132 | + amount_sat = transfer.total_value_sat; |
| 133 | + None |
| 134 | + }; |
| 135 | + |
| 136 | + Ok(Payment { |
| 137 | + id: transfer.id.to_string(), |
| 138 | + payment_type, |
| 139 | + status, |
| 140 | + amount: amount_sat, |
| 141 | + fees: fees_sat, |
| 142 | + timestamp: match transfer.created_at.map(|t| t.duration_since(UNIX_EPOCH)) { |
| 143 | + Some(Ok(duration)) => duration.as_secs(), |
| 144 | + _ => 0, |
| 145 | + }, |
| 146 | + method: transfer.transfer_type.into(), |
| 147 | + details, |
| 148 | + }) |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +impl Payment { |
| 153 | + pub fn from_lightning( |
| 154 | + payment: LightningSendPayment, |
| 155 | + amount_sat: u64, |
| 156 | + transfer_id: String, |
| 157 | + ) -> Result<Self, SdkError> { |
| 158 | + let mut status = match payment.status { |
| 159 | + LightningSendStatus::LightningPaymentSucceeded => PaymentStatus::Completed, |
| 160 | + LightningSendStatus::LightningPaymentFailed |
| 161 | + | LightningSendStatus::TransferFailed |
| 162 | + | LightningSendStatus::PreimageProvidingFailed |
| 163 | + | LightningSendStatus::UserSwapReturnFailed |
| 164 | + | LightningSendStatus::UserSwapReturned => PaymentStatus::Failed, |
| 165 | + _ => PaymentStatus::Pending, |
| 166 | + }; |
| 167 | + if payment.payment_preimage.is_some() { |
| 168 | + status = PaymentStatus::Completed; |
| 169 | + } |
| 170 | + |
| 171 | + let invoice_details = input::parse_invoice(&payment.encoded_invoice).ok_or( |
| 172 | + SdkError::Generic("Invalid invoice in LightnintSendPayment".to_string()), |
| 173 | + )?; |
| 174 | + let details = PaymentDetails::Lightning { |
| 175 | + description: invoice_details.description, |
| 176 | + preimage: payment.payment_preimage, |
| 177 | + invoice: payment.encoded_invoice, |
| 178 | + payment_hash: invoice_details.payment_hash, |
| 179 | + destination_pubkey: invoice_details.payee_pubkey, |
| 180 | + lnurl_pay_info: None, |
| 181 | + }; |
| 182 | + |
| 183 | + Ok(Payment { |
| 184 | + id: transfer_id, |
| 185 | + payment_type: PaymentType::Send, |
| 186 | + status, |
| 187 | + amount: amount_sat, |
| 188 | + fees: payment.fee_sat, |
| 189 | + timestamp: payment.created_at.cast_unsigned(), |
| 190 | + method: PaymentMethod::Lightning, |
| 191 | + details: Some(details), |
| 192 | + }) |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | +impl From<Network> for SparkNetwork { |
| 197 | + fn from(network: Network) -> Self { |
| 198 | + match network { |
| 199 | + Network::Mainnet => SparkNetwork::Mainnet, |
| 200 | + Network::Regtest => SparkNetwork::Regtest, |
| 201 | + } |
| 202 | + } |
| 203 | +} |
| 204 | + |
| 205 | +impl From<Fee> for spark_wallet::Fee { |
| 206 | + fn from(fee: Fee) -> Self { |
| 207 | + match fee { |
| 208 | + Fee::Fixed { amount } => spark_wallet::Fee::Fixed { amount }, |
| 209 | + Fee::Rate { sat_per_vbyte } => spark_wallet::Fee::Rate { sat_per_vbyte }, |
| 210 | + } |
| 211 | + } |
| 212 | +} |
| 213 | + |
| 214 | +impl From<spark_wallet::TokenBalance> for TokenBalance { |
| 215 | + fn from(value: spark_wallet::TokenBalance) -> Self { |
| 216 | + Self { |
| 217 | + balance: value.balance.try_into().unwrap_or_default(), // balance will be changed to u128 or similar |
| 218 | + token_metadata: value.token_metadata.into(), |
| 219 | + } |
| 220 | + } |
| 221 | +} |
| 222 | + |
| 223 | +impl From<spark_wallet::TokenMetadata> for TokenMetadata { |
| 224 | + fn from(value: spark_wallet::TokenMetadata) -> Self { |
| 225 | + Self { |
| 226 | + identifier: value.identifier, |
| 227 | + issuer_public_key: hex::encode(value.issuer_public_key.serialize()), |
| 228 | + name: value.name, |
| 229 | + ticker: value.ticker, |
| 230 | + decimals: value.decimals, |
| 231 | + max_supply: value.max_supply.try_into().unwrap_or_default(), // max_supply will be changed to u128 or similar |
| 232 | + is_freezable: value.is_freezable, |
| 233 | + } |
| 234 | + } |
| 235 | +} |
| 236 | + |
| 237 | +impl From<CoopExitFeeQuote> for SendOnchainFeeQuote { |
| 238 | + fn from(value: CoopExitFeeQuote) -> Self { |
| 239 | + Self { |
| 240 | + id: value.id, |
| 241 | + expires_at: value.expires_at, |
| 242 | + speed_fast: value.speed_fast.into(), |
| 243 | + speed_medium: value.speed_medium.into(), |
| 244 | + speed_slow: value.speed_slow.into(), |
| 245 | + } |
| 246 | + } |
| 247 | +} |
| 248 | + |
| 249 | +impl From<SendOnchainFeeQuote> for CoopExitFeeQuote { |
| 250 | + fn from(value: SendOnchainFeeQuote) -> Self { |
| 251 | + Self { |
| 252 | + id: value.id, |
| 253 | + expires_at: value.expires_at, |
| 254 | + speed_fast: value.speed_fast.into(), |
| 255 | + speed_medium: value.speed_medium.into(), |
| 256 | + speed_slow: value.speed_slow.into(), |
| 257 | + } |
| 258 | + } |
| 259 | +} |
| 260 | + |
| 261 | +impl From<CoopExitSpeedFeeQuote> for SendOnchainSpeedFeeQuote { |
| 262 | + fn from(value: CoopExitSpeedFeeQuote) -> Self { |
| 263 | + Self { |
| 264 | + user_fee_sat: value.user_fee_sat, |
| 265 | + l1_broadcast_fee_sat: value.l1_broadcast_fee_sat, |
| 266 | + } |
| 267 | + } |
| 268 | +} |
| 269 | + |
| 270 | +impl From<SendOnchainSpeedFeeQuote> for CoopExitSpeedFeeQuote { |
| 271 | + fn from(value: SendOnchainSpeedFeeQuote) -> Self { |
| 272 | + Self { |
| 273 | + user_fee_sat: value.user_fee_sat, |
| 274 | + l1_broadcast_fee_sat: value.l1_broadcast_fee_sat, |
| 275 | + } |
| 276 | + } |
| 277 | +} |
| 278 | + |
| 279 | +impl From<OnchainConfirmationSpeed> for ExitSpeed { |
| 280 | + fn from(speed: OnchainConfirmationSpeed) -> Self { |
| 281 | + match speed { |
| 282 | + OnchainConfirmationSpeed::Fast => ExitSpeed::Fast, |
| 283 | + OnchainConfirmationSpeed::Medium => ExitSpeed::Medium, |
| 284 | + OnchainConfirmationSpeed::Slow => ExitSpeed::Slow, |
| 285 | + } |
| 286 | + } |
| 287 | +} |
| 288 | + |
| 289 | +impl From<ExitSpeed> for OnchainConfirmationSpeed { |
| 290 | + fn from(speed: ExitSpeed) -> Self { |
| 291 | + match speed { |
| 292 | + ExitSpeed::Fast => OnchainConfirmationSpeed::Fast, |
| 293 | + ExitSpeed::Medium => OnchainConfirmationSpeed::Medium, |
| 294 | + ExitSpeed::Slow => OnchainConfirmationSpeed::Slow, |
| 295 | + } |
| 296 | + } |
| 297 | +} |
| 298 | + |
| 299 | +impl PaymentStatus { |
| 300 | + pub(crate) fn from_token_transaction_status( |
| 301 | + status: TokenTransactionStatus, |
| 302 | + is_transfer_transaction: bool, |
| 303 | + ) -> Self { |
| 304 | + match status { |
| 305 | + TokenTransactionStatus::Started |
| 306 | + | TokenTransactionStatus::Revealed |
| 307 | + | TokenTransactionStatus::Unknown => PaymentStatus::Pending, |
| 308 | + TokenTransactionStatus::Signed if is_transfer_transaction => PaymentStatus::Pending, |
| 309 | + TokenTransactionStatus::Finalized | TokenTransactionStatus::Signed => { |
| 310 | + PaymentStatus::Completed |
| 311 | + } |
| 312 | + TokenTransactionStatus::StartedCancelled | TokenTransactionStatus::SignedCancelled => { |
| 313 | + PaymentStatus::Failed |
| 314 | + } |
| 315 | + } |
| 316 | + } |
| 317 | +} |
0 commit comments