use std::path::PathBuf;
use anyhow::{anyhow, Result};
use boltz_client::{
network::Chain,
swaps::boltz::{
CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse, Leaf, Side, SwapTree,
},
ToHex,
};
use boltz_client::{BtcSwapScript, BtcSwapTx, Keypair, LBtcSwapScript, LBtcSwapTx};
use lwk_signer::SwSigner;
use lwk_wollet::ElementsNetwork;
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
use rusqlite::ToSql;
use sdk_common::prelude::*;
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString};
use crate::error::{PaymentError, SdkResult};
use crate::receive_swap::{
DEFAULT_ZERO_CONF_MAX_SAT, DEFAULT_ZERO_CONF_MIN_FEE_RATE_MAINNET,
DEFAULT_ZERO_CONF_MIN_FEE_RATE_TESTNET,
};
use crate::utils;
pub const STANDARD_FEE_RATE_SAT_PER_VBYTE: f64 = 0.1;
pub const LOWBALL_FEE_RATE_SAT_PER_VBYTE: f64 = 0.01;
#[derive(Clone, Debug, Serialize)]
pub struct Config {
pub liquid_electrum_url: String,
pub bitcoin_electrum_url: String,
pub mempoolspace_url: String,
pub working_dir: String,
pub network: LiquidNetwork,
pub payment_timeout_sec: u64,
pub zero_conf_min_fee_rate_msat: u32,
pub zero_conf_max_amount_sat: Option<u64>,
}
impl Config {
pub fn mainnet() -> Self {
Config {
liquid_electrum_url: "blockstream.info:995".to_string(),
bitcoin_electrum_url: "blockstream.info:700".to_string(),
mempoolspace_url: "https://mempool.space/api".to_string(),
working_dir: ".".to_string(),
network: LiquidNetwork::Mainnet,
payment_timeout_sec: 15,
zero_conf_min_fee_rate_msat: DEFAULT_ZERO_CONF_MIN_FEE_RATE_MAINNET,
zero_conf_max_amount_sat: None,
}
}
pub fn testnet() -> Self {
Config {
liquid_electrum_url: "blockstream.info:465".to_string(),
bitcoin_electrum_url: "blockstream.info:993".to_string(),
mempoolspace_url: "https://mempool.space/testnet/api".to_string(),
working_dir: ".".to_string(),
network: LiquidNetwork::Testnet,
payment_timeout_sec: 15,
zero_conf_min_fee_rate_msat: DEFAULT_ZERO_CONF_MIN_FEE_RATE_TESTNET,
zero_conf_max_amount_sat: None,
}
}
pub(crate) fn get_wallet_working_dir(&self, signer: &SwSigner) -> anyhow::Result<String> {
Ok(PathBuf::from(self.working_dir.clone())
.join(match self.network {
LiquidNetwork::Mainnet => "mainnet",
LiquidNetwork::Testnet => "testnet",
})
.join(signer.fingerprint().to_hex())
.to_str()
.ok_or(anyhow::anyhow!(
"Could not get retrieve current wallet directory"
))?
.to_string())
}
pub fn zero_conf_max_amount_sat(&self) -> u64 {
self.zero_conf_max_amount_sat
.unwrap_or(DEFAULT_ZERO_CONF_MAX_SAT)
}
pub(crate) fn lowball_fee_rate_msat_per_vbyte(&self) -> Option<f64> {
match self.network {
LiquidNetwork::Mainnet => Some(LOWBALL_FEE_RATE_SAT_PER_VBYTE * 1000.0),
LiquidNetwork::Testnet => None,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize)]
pub enum LiquidNetwork {
Mainnet,
Testnet,
}
impl LiquidNetwork {
pub fn as_bitcoin_chain(&self) -> Chain {
match self {
LiquidNetwork::Mainnet => Chain::Bitcoin,
LiquidNetwork::Testnet => Chain::BitcoinTestnet,
}
}
}
impl From<LiquidNetwork> for ElementsNetwork {
fn from(value: LiquidNetwork) -> Self {
match value {
LiquidNetwork::Mainnet => ElementsNetwork::Liquid,
LiquidNetwork::Testnet => ElementsNetwork::LiquidTestnet,
}
}
}
impl From<LiquidNetwork> for Chain {
fn from(value: LiquidNetwork) -> Self {
match value {
LiquidNetwork::Mainnet => Chain::Liquid,
LiquidNetwork::Testnet => Chain::LiquidTestnet,
}
}
}
impl TryFrom<&str> for LiquidNetwork {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<LiquidNetwork, anyhow::Error> {
match value.to_lowercase().as_str() {
"mainnet" => Ok(LiquidNetwork::Mainnet),
"testnet" => Ok(LiquidNetwork::Testnet),
_ => Err(anyhow!("Invalid network")),
}
}
}
impl From<LiquidNetwork> for sdk_common::prelude::Network {
fn from(value: LiquidNetwork) -> Self {
match value {
LiquidNetwork::Mainnet => Self::Bitcoin,
LiquidNetwork::Testnet => Self::Testnet,
}
}
}
impl From<LiquidNetwork> for sdk_common::bitcoin::Network {
fn from(value: LiquidNetwork) -> Self {
match value {
LiquidNetwork::Mainnet => Self::Bitcoin,
LiquidNetwork::Testnet => Self::Testnet,
}
}
}
pub trait EventListener: Send + Sync {
fn on_event(&self, e: SdkEvent);
}
#[derive(Clone, Debug, PartialEq)]
pub enum SdkEvent {
PaymentFailed { details: Payment },
PaymentPending { details: Payment },
PaymentRefunded { details: Payment },
PaymentRefundPending { details: Payment },
PaymentSucceeded { details: Payment },
PaymentWaitingConfirmation { details: Payment },
Synced,
}
#[derive(Debug, Serialize)]
pub struct ConnectRequest {
pub mnemonic: String,
pub config: Config,
}
#[derive(Clone, Debug, EnumString, Serialize, Eq, PartialEq)]
pub enum PaymentMethod {
#[strum(serialize = "lightning")]
Lightning,
#[strum(serialize = "bitcoin")]
BitcoinAddress,
#[strum(serialize = "liquid")]
LiquidAddress,
}
#[derive(Debug, Serialize)]
pub struct PrepareReceiveRequest {
pub payer_amount_sat: Option<u64>,
pub payment_method: PaymentMethod,
}
#[derive(Debug, Serialize)]
pub struct PrepareReceiveResponse {
pub payment_method: PaymentMethod,
pub payer_amount_sat: Option<u64>,
pub fees_sat: u64,
}
#[derive(Debug, Serialize)]
pub struct ReceivePaymentRequest {
pub prepare_response: PrepareReceiveResponse,
pub description: Option<String>,
pub use_description_hash: Option<bool>,
}
#[derive(Debug, Serialize)]
pub struct ReceivePaymentResponse {
pub destination: String,
}
#[derive(Debug, Serialize)]
pub struct Limits {
pub min_sat: u64,
pub max_sat: u64,
pub max_zero_conf_sat: u64,
}
#[derive(Debug, Serialize)]
pub struct LightningPaymentLimitsResponse {
pub send: Limits,
pub receive: Limits,
}
#[derive(Debug, Serialize)]
pub struct OnchainPaymentLimitsResponse {
pub send: Limits,
pub receive: Limits,
}
#[derive(Debug, Serialize, Clone)]
pub struct PrepareSendRequest {
pub destination: String,
pub amount_sat: Option<u64>,
}
#[derive(Clone, Debug, Serialize)]
pub enum SendDestination {
LiquidAddress {
address_data: liquid::LiquidAddressData,
},
Bolt11 {
invoice: LNInvoice,
},
}
#[derive(Debug, Serialize, Clone)]
pub struct PrepareSendResponse {
pub destination: SendDestination,
pub fees_sat: u64,
}
#[derive(Debug, Serialize)]
pub struct SendPaymentRequest {
pub prepare_response: PrepareSendResponse,
}
#[derive(Debug, Serialize)]
pub struct SendPaymentResponse {
pub payment: Payment,
}
#[derive(Debug, Serialize, Clone)]
pub enum PayOnchainAmount {
Receiver { amount_sat: u64 },
Drain,
}
#[derive(Debug, Serialize, Clone)]
pub struct PreparePayOnchainRequest {
pub amount: PayOnchainAmount,
pub sat_per_vbyte: Option<u32>,
}
#[derive(Debug, Serialize, Clone)]
pub struct PreparePayOnchainResponse {
pub receiver_amount_sat: u64,
pub claim_fees_sat: u64,
pub total_fees_sat: u64,
}
#[derive(Debug, Serialize)]
pub struct PayOnchainRequest {
pub address: String,
pub prepare_response: PreparePayOnchainResponse,
}
#[derive(Debug, Serialize)]
pub struct PrepareRefundRequest {
pub swap_address: String,
pub refund_address: String,
pub sat_per_vbyte: u32,
}
#[derive(Debug, Serialize)]
pub struct PrepareRefundResponse {
pub tx_vsize: u32,
pub tx_fee_sat: u64,
pub refund_tx_id: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct RefundRequest {
pub swap_address: String,
pub refund_address: String,
pub sat_per_vbyte: u32,
}
#[derive(Debug, Serialize)]
pub struct RefundResponse {
pub refund_tx_id: String,
}
#[derive(Debug, Serialize)]
pub struct GetInfoResponse {
pub balance_sat: u64,
pub pending_send_sat: u64,
pub pending_receive_sat: u64,
pub pubkey: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SignMessageRequest {
pub message: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SignMessageResponse {
pub signature: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CheckMessageRequest {
pub message: String,
pub pubkey: String,
pub signature: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CheckMessageResponse {
pub is_valid: bool,
}
#[derive(Debug, Serialize)]
pub struct BackupRequest {
pub backup_path: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct RestoreRequest {
pub backup_path: Option<String>,
}
#[derive(Default)]
pub struct ListPaymentsRequest {
pub filters: Option<Vec<PaymentType>>,
pub from_timestamp: Option<i64>,
pub to_timestamp: Option<i64>,
pub offset: Option<u32>,
pub limit: Option<u32>,
}
#[derive(Clone, Debug)]
pub(crate) enum Swap {
Chain(ChainSwap),
Send(SendSwap),
Receive(ReceiveSwap),
}
impl Swap {
pub(crate) fn id(&self) -> String {
match &self {
Swap::Chain(ChainSwap { id, .. })
| Swap::Send(SendSwap { id, .. })
| Swap::Receive(ReceiveSwap { id, .. }) => id.clone(),
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum SwapScriptV2 {
Bitcoin(BtcSwapScript),
Liquid(LBtcSwapScript),
}
impl SwapScriptV2 {
pub(crate) fn as_bitcoin_script(&self) -> Result<BtcSwapScript> {
match self {
SwapScriptV2::Bitcoin(script) => Ok(script.clone()),
_ => Err(anyhow!("Invalid chain")),
}
}
pub(crate) fn as_liquid_script(&self) -> Result<LBtcSwapScript> {
match self {
SwapScriptV2::Liquid(script) => Ok(script.clone()),
_ => Err(anyhow!("Invalid chain")),
}
}
}
#[allow(clippy::large_enum_variant)]
pub(crate) enum SwapTxV2 {
Bitcoin(BtcSwapTx),
Liquid(LBtcSwapTx),
}
impl SwapTxV2 {
pub(crate) fn as_bitcoin_tx(&self) -> Result<BtcSwapTx> {
match self {
SwapTxV2::Bitcoin(tx) => Ok(tx.clone()),
_ => Err(anyhow!("Invalid chain")),
}
}
pub(crate) fn as_liquid_tx(&self) -> Result<LBtcSwapTx> {
match self {
SwapTxV2::Liquid(tx) => Ok(tx.clone()),
_ => Err(anyhow!("Invalid chain")),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize)]
pub enum Direction {
Incoming = 0,
Outgoing = 1,
}
impl ToSql for Direction {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
}
}
impl FromSql for Direction {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => match i as u8 {
0 => Ok(Direction::Incoming),
1 => Ok(Direction::Outgoing),
_ => Err(FromSqlError::OutOfRange(i)),
},
_ => Err(FromSqlError::InvalidType),
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct ChainSwap {
pub(crate) id: String,
pub(crate) direction: Direction,
pub(crate) claim_address: String,
pub(crate) lockup_address: String,
pub(crate) timeout_block_height: u32,
pub(crate) preimage: String,
pub(crate) description: Option<String>,
pub(crate) payer_amount_sat: u64,
pub(crate) receiver_amount_sat: u64,
pub(crate) claim_fees_sat: u64,
pub(crate) accept_zero_conf: bool,
pub(crate) create_response_json: String,
pub(crate) server_lockup_tx_id: Option<String>,
pub(crate) user_lockup_tx_id: Option<String>,
pub(crate) claim_tx_id: Option<String>,
pub(crate) refund_tx_id: Option<String>,
pub(crate) created_at: u32,
pub(crate) state: PaymentState,
pub(crate) claim_private_key: String,
pub(crate) refund_private_key: String,
}
impl ChainSwap {
pub(crate) fn get_claim_keypair(&self) -> SdkResult<Keypair> {
utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
}
pub(crate) fn get_refund_keypair(&self) -> SdkResult<Keypair> {
utils::decode_keypair(&self.refund_private_key).map_err(Into::into)
}
pub(crate) fn get_boltz_create_response(&self) -> Result<CreateChainResponse> {
let internal_create_response: crate::persist::chain::InternalCreateChainResponse =
serde_json::from_str(&self.create_response_json).map_err(|e| {
anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
})?;
Ok(CreateChainResponse {
id: self.id.clone(),
claim_details: internal_create_response.claim_details,
lockup_details: internal_create_response.lockup_details,
})
}
pub(crate) fn get_claim_swap_script(&self) -> SdkResult<SwapScriptV2> {
let chain_swap_details = self.get_boltz_create_response()?.claim_details;
let our_pubkey = self.get_claim_keypair()?.public_key();
let swap_script = match self.direction {
Direction::Incoming => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
Side::Claim,
chain_swap_details,
our_pubkey.into(),
)?),
Direction::Outgoing => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
Side::Claim,
chain_swap_details,
our_pubkey.into(),
)?),
};
Ok(swap_script)
}
pub(crate) fn get_lockup_swap_script(&self) -> SdkResult<SwapScriptV2> {
let chain_swap_details = self.get_boltz_create_response()?.lockup_details;
let our_pubkey = self.get_refund_keypair()?.public_key();
let swap_script = match self.direction {
Direction::Incoming => SwapScriptV2::Bitcoin(BtcSwapScript::chain_from_swap_resp(
Side::Lockup,
chain_swap_details,
our_pubkey.into(),
)?),
Direction::Outgoing => SwapScriptV2::Liquid(LBtcSwapScript::chain_from_swap_resp(
Side::Lockup,
chain_swap_details,
our_pubkey.into(),
)?),
};
Ok(swap_script)
}
pub(crate) fn from_boltz_struct_to_json(
create_response: &CreateChainResponse,
expected_swap_id: &str,
) -> Result<String, PaymentError> {
let internal_create_response =
crate::persist::chain::InternalCreateChainResponse::try_convert_from_boltz(
create_response,
expected_swap_id,
)?;
let create_response_json =
serde_json::to_string(&internal_create_response).map_err(|e| {
PaymentError::Generic {
err: format!("Failed to serialize InternalCreateChainResponse: {e:?}"),
}
})?;
Ok(create_response_json)
}
}
#[derive(Clone, Debug)]
pub(crate) struct SendSwap {
pub(crate) id: String,
pub(crate) invoice: String,
pub(crate) description: Option<String>,
pub(crate) preimage: Option<String>,
pub(crate) payer_amount_sat: u64,
pub(crate) receiver_amount_sat: u64,
pub(crate) create_response_json: String,
pub(crate) lockup_tx_id: Option<String>,
pub(crate) refund_tx_id: Option<String>,
pub(crate) created_at: u32,
pub(crate) state: PaymentState,
pub(crate) refund_private_key: String,
}
impl SendSwap {
pub(crate) fn get_refund_keypair(&self) -> Result<Keypair, PaymentError> {
utils::decode_keypair(&self.refund_private_key).map_err(Into::into)
}
pub(crate) fn get_boltz_create_response(&self) -> Result<CreateSubmarineResponse> {
let internal_create_response: crate::persist::send::InternalCreateSubmarineResponse =
serde_json::from_str(&self.create_response_json).map_err(|e| {
anyhow!("Failed to deserialize InternalCreateSubmarineResponse: {e:?}")
})?;
let res = CreateSubmarineResponse {
id: self.id.clone(),
accept_zero_conf: internal_create_response.accept_zero_conf,
address: internal_create_response.address.clone(),
bip21: internal_create_response.bip21.clone(),
claim_public_key: crate::utils::json_to_pubkey(
&internal_create_response.claim_public_key,
)?,
expected_amount: internal_create_response.expected_amount,
referral_id: internal_create_response.referral_id,
swap_tree: internal_create_response.swap_tree.clone().into(),
timeout_block_height: internal_create_response.timeout_block_height,
blinding_key: internal_create_response.blinding_key.clone(),
};
Ok(res)
}
pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
LBtcSwapScript::submarine_from_swap_resp(
&self.get_boltz_create_response()?,
self.get_refund_keypair()?.public_key().into(),
)
.map_err(|e| PaymentError::Generic {
err: format!(
"Failed to create swap script for Send Swap {}: {e:?}",
self.id
),
})
}
pub(crate) fn from_boltz_struct_to_json(
create_response: &CreateSubmarineResponse,
expected_swap_id: &str,
) -> Result<String, PaymentError> {
let internal_create_response =
crate::persist::send::InternalCreateSubmarineResponse::try_convert_from_boltz(
create_response,
expected_swap_id,
)?;
let create_response_json =
serde_json::to_string(&internal_create_response).map_err(|e| {
PaymentError::Generic {
err: format!("Failed to serialize InternalCreateSubmarineResponse: {e:?}"),
}
})?;
Ok(create_response_json)
}
}
#[derive(Clone, Debug)]
pub(crate) struct ReceiveSwap {
pub(crate) id: String,
pub(crate) preimage: String,
pub(crate) create_response_json: String,
pub(crate) claim_private_key: String,
pub(crate) invoice: String,
pub(crate) description: Option<String>,
pub(crate) payer_amount_sat: u64,
pub(crate) receiver_amount_sat: u64,
pub(crate) claim_fees_sat: u64,
pub(crate) claim_tx_id: Option<String>,
pub(crate) created_at: u32,
pub(crate) state: PaymentState,
}
impl ReceiveSwap {
pub(crate) fn get_claim_keypair(&self) -> Result<Keypair, PaymentError> {
utils::decode_keypair(&self.claim_private_key).map_err(Into::into)
}
pub(crate) fn get_boltz_create_response(&self) -> Result<CreateReverseResponse, PaymentError> {
let internal_create_response: crate::persist::receive::InternalCreateReverseResponse =
serde_json::from_str(&self.create_response_json).map_err(|e| {
PaymentError::Generic {
err: format!("Failed to deserialize InternalCreateReverseResponse: {e:?}"),
}
})?;
let res = CreateReverseResponse {
id: self.id.clone(),
invoice: self.invoice.clone(),
swap_tree: internal_create_response.swap_tree.clone().into(),
lockup_address: internal_create_response.lockup_address.clone(),
refund_public_key: crate::utils::json_to_pubkey(
&internal_create_response.refund_public_key,
)?,
timeout_block_height: internal_create_response.timeout_block_height,
onchain_amount: internal_create_response.onchain_amount,
blinding_key: internal_create_response.blinding_key.clone(),
};
Ok(res)
}
pub(crate) fn get_swap_script(&self) -> Result<LBtcSwapScript, PaymentError> {
let keypair = self.get_claim_keypair()?;
let create_response =
self.get_boltz_create_response()
.map_err(|e| PaymentError::Generic {
err: format!(
"Failed to create swap script for Receive Swap {}: {e:?}",
self.id
),
})?;
LBtcSwapScript::reverse_from_swap_resp(&create_response, keypair.public_key().into())
.map_err(|e| PaymentError::Generic {
err: format!(
"Failed to create swap script for Receive Swap {}: {e:?}",
self.id
),
})
}
pub(crate) fn from_boltz_struct_to_json(
create_response: &CreateReverseResponse,
expected_swap_id: &str,
expected_invoice: &str,
) -> Result<String, PaymentError> {
let internal_create_response =
crate::persist::receive::InternalCreateReverseResponse::try_convert_from_boltz(
create_response,
expected_swap_id,
expected_invoice,
)?;
let create_response_json =
serde_json::to_string(&internal_create_response).map_err(|e| {
PaymentError::Generic {
err: format!("Failed to serialize InternalCreateReverseResponse: {e:?}"),
}
})?;
Ok(create_response_json)
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct RefundableSwap {
pub swap_address: String,
pub timestamp: u32,
pub amount_sat: u64,
}
impl From<ChainSwap> for RefundableSwap {
fn from(swap: ChainSwap) -> Self {
Self {
swap_address: swap.lockup_address,
timestamp: swap.created_at,
amount_sat: swap.payer_amount_sat,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Hash)]
pub enum PaymentState {
Created = 0,
Pending = 1,
Complete = 2,
Failed = 3,
TimedOut = 4,
Refundable = 5,
RefundPending = 6,
}
impl ToSql for PaymentState {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
}
}
impl FromSql for PaymentState {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => match i as u8 {
0 => Ok(PaymentState::Created),
1 => Ok(PaymentState::Pending),
2 => Ok(PaymentState::Complete),
3 => Ok(PaymentState::Failed),
4 => Ok(PaymentState::TimedOut),
5 => Ok(PaymentState::Refundable),
6 => Ok(PaymentState::RefundPending),
_ => Err(FromSqlError::OutOfRange(i)),
},
_ => Err(FromSqlError::InvalidType),
}
}
}
#[derive(Debug, Copy, Clone, Eq, EnumString, Display, Hash, PartialEq, Serialize)]
pub enum PaymentType {
Receive = 0,
Send = 1,
}
impl From<Direction> for PaymentType {
fn from(value: Direction) -> Self {
match value {
Direction::Incoming => Self::Receive,
Direction::Outgoing => Self::Send,
}
}
}
impl ToSql for PaymentType {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
}
}
impl FromSql for PaymentType {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => match i as u8 {
0 => Ok(PaymentType::Receive),
1 => Ok(PaymentType::Send),
_ => Err(FromSqlError::OutOfRange(i)),
},
_ => Err(FromSqlError::InvalidType),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub enum PaymentStatus {
Pending = 0,
Complete = 1,
}
impl ToSql for PaymentStatus {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::from(*self as i8))
}
}
impl FromSql for PaymentStatus {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => match i as u8 {
0 => Ok(PaymentStatus::Pending),
1 => Ok(PaymentStatus::Complete),
_ => Err(FromSqlError::OutOfRange(i)),
},
_ => Err(FromSqlError::InvalidType),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct PaymentTxData {
pub tx_id: String,
pub timestamp: Option<u32>,
pub amount_sat: u64,
pub fees_sat: u64,
pub payment_type: PaymentType,
pub is_confirmed: bool,
}
#[derive(Debug, Clone, Serialize)]
pub enum PaymentSwapType {
Receive,
Send,
Chain,
}
#[derive(Debug, Clone, Serialize)]
pub struct PaymentSwapData {
pub swap_id: String,
pub swap_type: PaymentSwapType,
pub created_at: u32,
pub preimage: Option<String>,
pub bolt11: Option<String>,
pub description: String,
pub payer_amount_sat: u64,
pub receiver_amount_sat: u64,
pub refund_tx_id: Option<String>,
pub refund_tx_amount_sat: Option<u64>,
pub claim_address: Option<String>,
pub status: PaymentState,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum PaymentDetails {
Lightning {
swap_id: String,
description: String,
preimage: Option<String>,
bolt11: Option<String>,
refund_tx_id: Option<String>,
refund_tx_amount_sat: Option<u64>,
},
Liquid {
destination: String,
description: String,
},
Bitcoin {
swap_id: String,
description: String,
refund_tx_id: Option<String>,
refund_tx_amount_sat: Option<u64>,
},
}
impl PaymentDetails {
pub(crate) fn get_swap_id(&self) -> Option<String> {
match self {
Self::Lightning { swap_id, .. } | Self::Bitcoin { swap_id, .. } => {
Some(swap_id.clone())
}
Self::Liquid { .. } => None,
}
}
pub(crate) fn get_refund_tx_amount_sat(&self) -> Option<u64> {
match self {
Self::Lightning {
refund_tx_amount_sat,
..
}
| Self::Bitcoin {
refund_tx_amount_sat,
..
} => *refund_tx_amount_sat,
Self::Liquid { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Payment {
pub destination: Option<String>,
pub tx_id: Option<String>,
pub timestamp: u32,
pub amount_sat: u64,
pub fees_sat: u64,
pub payment_type: PaymentType,
pub status: PaymentState,
pub details: PaymentDetails,
}
impl Payment {
pub(crate) fn from_pending_swap(swap: PaymentSwapData, payment_type: PaymentType) -> Payment {
let amount_sat = match payment_type {
PaymentType::Receive => swap.receiver_amount_sat,
PaymentType::Send => swap.payer_amount_sat,
};
Payment {
destination: swap.bolt11.clone(),
tx_id: None,
timestamp: swap.created_at,
amount_sat,
fees_sat: swap.payer_amount_sat - swap.receiver_amount_sat,
payment_type,
status: swap.status,
details: PaymentDetails::Lightning {
swap_id: swap.swap_id,
preimage: swap.preimage,
bolt11: swap.bolt11,
description: swap.description,
refund_tx_id: swap.refund_tx_id,
refund_tx_amount_sat: swap.refund_tx_amount_sat,
},
}
}
pub(crate) fn from_tx_data(
tx: PaymentTxData,
swap: Option<PaymentSwapData>,
details: PaymentDetails,
) -> Payment {
Payment {
tx_id: Some(tx.tx_id),
destination: match &swap {
Some(
PaymentSwapData {
swap_type: PaymentSwapType::Receive,
bolt11,
..
}
| PaymentSwapData {
swap_type: PaymentSwapType::Send,
bolt11,
..
},
) => bolt11.clone(),
Some(PaymentSwapData {
swap_type: PaymentSwapType::Chain,
claim_address,
..
}) => claim_address.clone(),
_ => match &details {
PaymentDetails::Liquid { destination, .. } => Some(destination.clone()),
_ => None,
},
},
timestamp: match swap {
Some(ref swap) => swap.created_at,
None => tx.timestamp.unwrap_or(utils::now()),
},
amount_sat: tx.amount_sat,
fees_sat: match swap.as_ref() {
Some(s) => s.payer_amount_sat - s.receiver_amount_sat,
None => match tx.payment_type {
PaymentType::Receive => 0,
PaymentType::Send => tx.fees_sat,
},
},
payment_type: tx.payment_type,
status: match &swap {
Some(swap) => swap.status,
None => match tx.is_confirmed {
true => PaymentState::Complete,
false => PaymentState::Pending,
},
},
details,
}
}
pub(crate) fn get_refund_tx_id(&self) -> Option<String> {
match self.details.clone() {
PaymentDetails::Lightning { refund_tx_id, .. } => Some(refund_tx_id),
PaymentDetails::Bitcoin { refund_tx_id, .. } => Some(refund_tx_id),
PaymentDetails::Liquid { .. } => None,
}
.flatten()
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RecommendedFees {
pub fastest_fee: u64,
pub half_hour_fee: u64,
pub hour_fee: u64,
pub economy_fee: u64,
pub minimum_fee: u64,
}
#[derive(Debug, Clone, Copy, EnumString, PartialEq, Serialize)]
pub enum BuyBitcoinProvider {
#[strum(serialize = "moonpay")]
Moonpay,
}
#[derive(Debug, Serialize)]
pub struct PrepareBuyBitcoinRequest {
pub provider: BuyBitcoinProvider,
pub amount_sat: u64,
}
#[derive(Clone, Debug, Serialize)]
pub struct PrepareBuyBitcoinResponse {
pub provider: BuyBitcoinProvider,
pub amount_sat: u64,
pub fees_sat: u64,
}
#[derive(Clone, Debug, Serialize)]
pub struct BuyBitcoinRequest {
pub prepare_response: PrepareBuyBitcoinResponse,
pub redirect_url: Option<String>,
}
#[derive(Clone, Debug)]
pub struct LogEntry {
pub line: String,
pub level: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct InternalLeaf {
pub output: String,
pub version: u8,
}
impl From<InternalLeaf> for Leaf {
fn from(value: InternalLeaf) -> Self {
Leaf {
output: value.output,
version: value.version,
}
}
}
impl From<Leaf> for InternalLeaf {
fn from(value: Leaf) -> Self {
InternalLeaf {
output: value.output,
version: value.version,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(super) struct InternalSwapTree {
claim_leaf: InternalLeaf,
refund_leaf: InternalLeaf,
}
impl From<InternalSwapTree> for SwapTree {
fn from(value: InternalSwapTree) -> Self {
SwapTree {
claim_leaf: value.claim_leaf.into(),
refund_leaf: value.refund_leaf.into(),
}
}
}
impl From<SwapTree> for InternalSwapTree {
fn from(value: SwapTree) -> Self {
InternalSwapTree {
claim_leaf: value.claim_leaf.into(),
refund_leaf: value.refund_leaf.into(),
}
}
}
#[derive(Serialize)]
#[allow(clippy::large_enum_variant)]
pub enum LnUrlPayResult {
EndpointSuccess { data: LnUrlPaySuccessData },
EndpointError { data: LnUrlErrorData },
PayError { data: LnUrlPayErrorData },
}
#[derive(Serialize)]
pub struct LnUrlPaySuccessData {
pub payment: Payment,
pub success_action: Option<SuccessActionProcessed>,
}
#[macro_export]
macro_rules! get_invoice_amount {
($invoice:expr) => {
$invoice
.parse::<Bolt11Invoice>()
.expect("Expecting valid invoice")
.amount_milli_satoshis()
.expect("Expecting valid amount")
/ 1000
};
}
#[macro_export]
macro_rules! get_invoice_description {
($invoice:expr) => {
match $invoice
.trim()
.parse::<Bolt11Invoice>()
.expect("Expecting valid invoice")
.description()
{
Bolt11InvoiceDescription::Direct(msg) => Some(msg.to_string()),
Bolt11InvoiceDescription::Hash(_) => None,
}
};
}