Skip to content

Commit

Permalink
feat: added support for pending payments
Browse files Browse the repository at this point in the history
  • Loading branch information
yse committed Mar 22, 2024
1 parent 4e3536f commit a88a3ca
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 99 deletions.
7 changes: 4 additions & 3 deletions cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rustyline::history::DefaultHistory;
use rustyline::Editor;
use rustyline::{hint::HistoryHinter, Completer, Helper, Hinter, Validator};

use breez_sdk_liquid::{ReceivePaymentRequest, Wallet};
use breez_sdk_liquid::{ReceivePaymentRequest, SendPaymentResponse, Wallet};

#[derive(Parser, Debug, Clone, PartialEq)]
pub(crate) enum Command {
Expand Down Expand Up @@ -61,13 +61,14 @@ pub(crate) fn handle_command(
))
}
Command::SendPayment { bolt11 } => {
let response = wallet.send_payment(&bolt11)?;
let prepare_response = dbg!(wallet.prepare_payment(&bolt11)?);
let SendPaymentResponse { txid } = wallet.send_payment(&prepare_response)?;

Ok(format!(
r#"
Successfully paid the invoice!
You can view the onchain transaction at https://blockstream.info/liquidtestnet/tx/{}"#,
response.txid
txid
))
}
Command::GetInfo => {
Expand Down
3 changes: 2 additions & 1 deletion lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ mod tests {
println!("Please paste the invoice to be paid: ");
io::stdin().read_line(&mut invoice)?;

breez_wallet.send_payment(&invoice)?;
let prepare_response = breez_wallet.prepare_payment(&invoice)?;
breez_wallet.send_payment(&prepare_response)?;

Ok(())
}
Expand Down
114 changes: 82 additions & 32 deletions lib/src/model.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,57 @@
use boltz_client::util::error::S5Error;
use lwk_signer::SwSigner;
use lwk_wollet::{ElectrumUrl, ElementsNetwork};
use rusqlite::types::FromSql;

#[derive(Debug)]
#[allow(dead_code)]
pub(crate) enum SwapTypeFilter {
Sent,
Received,
}

#[derive(Debug, Eq, PartialEq, Hash)]
pub(crate) enum SwapType {
ReverseSubmarine,
Submarine,
}

impl ToString for SwapType {
fn to_string(&self) -> String {
match self {
SwapType::Submarine => "Sent",
SwapType::ReverseSubmarine => "Received",
}
.to_string()
}
}

impl FromSql for SwapType {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
String::column_result(value).and_then(|str| match str.as_str() {
"Sent" => Ok(SwapType::Submarine),
"Received" => Ok(SwapType::ReverseSubmarine),
_ => Err(rusqlite::types::FromSqlError::InvalidType),
})
}
}

#[derive(Debug)]
pub(crate) struct OngoingSwap {
pub id: String,
pub swap_type: SwapType,

/// For [SwapType::Submarine]
pub preimage: Option<String>,
pub redeem_script: Option<String>,
pub blinding_key: Option<String>,
pub invoice_amount_sat: Option<u64>,
pub onchain_amount_sat: Option<u64>,

/// For [SwapType::ReverseSubmarine]
pub funding_address: Option<String>,
pub funding_amount_sat: Option<u64>,
}

pub enum Network {
Liquid,
Expand Down Expand Up @@ -28,29 +79,37 @@ pub struct WalletOptions {
pub electrum_url: Option<ElectrumUrl>,
}

pub struct ReceivePaymentRequest {
pub invoice_amount_sat: Option<u64>,
pub onchain_amount_sat: Option<u64>,
}

#[derive(Debug)]
pub struct SwapLbtcResponse {
pub struct ReceivePaymentResponse {
pub id: String,
pub invoice: String,
}

pub enum SwapStatus {
Created,
Mempool,
Completed,
#[derive(Debug)]
pub struct PreparePaymentRequest {
pub invoice: String,
}

pub struct ReceivePaymentRequest {
pub invoice_amount_sat: Option<u64>,
pub onchain_amount_sat: Option<u64>,
#[derive(Debug)]
pub struct PreparePaymentResponse {
pub id: String,
pub funding_address: String,
pub funding_amount_sat: u64,
pub fees_sat: u64,
}

#[derive(Debug)]
pub struct SendPaymentResponse {
pub txid: String,
}

#[derive(thiserror::Error, Debug)]
pub enum SwapError {
pub enum PaymentError {
#[error("Could not contact Boltz servers: {err}")]
ServersUnreachable { err: String },

Expand Down Expand Up @@ -79,15 +138,15 @@ pub enum SwapError {
BoltzGeneric { err: String },
}

impl From<S5Error> for SwapError {
impl From<S5Error> for PaymentError {
fn from(err: S5Error) -> Self {
match err.kind {
boltz_client::util::error::ErrorKind::Network
| boltz_client::util::error::ErrorKind::BoltzApi => {
SwapError::ServersUnreachable { err: err.message }
PaymentError::ServersUnreachable { err: err.message }
}
boltz_client::util::error::ErrorKind::Input => SwapError::BadResponse,
_ => SwapError::BoltzGeneric { err: err.message },
boltz_client::util::error::ErrorKind::Input => PaymentError::BadResponse,
_ => PaymentError::BoltzGeneric { err: err.message },
}
}
}
Expand All @@ -99,30 +158,21 @@ pub struct WalletInfo {
pub active_address: String,
}

#[derive(Debug)]
pub struct OngoingReceiveSwap {
pub id: String,
pub preimage: String,
pub redeem_script: String,
pub blinding_key: String,
pub invoice_amount_sat: u64,
pub onchain_amount_sat: u64,
}

pub struct OngoingSendSwap {
pub id: String,
// pub preimage: String,
// pub redeem_script: String,
// pub blinding_key: String,
// pub invoice_amount_sat: Option<u64>,
// pub onchain_amount_sat: Option<u64>,
}

#[derive(Debug)]
pub enum PaymentType {
Sent,
Received,
PendingReceive,
PendingSend,
}

impl From<SwapType> for PaymentType {
fn from(swap_type: SwapType) -> Self {
match swap_type {
SwapType::ReverseSubmarine => Self::PendingReceive,
SwapType::Submarine => Self::PendingSend,
}
}
}

#[derive(Debug)]
Expand Down
13 changes: 8 additions & 5 deletions lib/src/persist/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ pub(crate) fn current_migrations() -> Vec<&'static str> {
vec![
"CREATE TABLE IF NOT EXISTS ongoing_swaps (
id TEXT NOT NULL PRIMARY KEY,
preimage TEXT NOT NULL,
redeem_script TEXT NOT NULL,
blinding_key TEXT NOT NULL,
invoice_amount_sat INTEGER NOT NULL,
onchain_amount_sat INTEGER NOT NULL
swap_type TEXT NOT NULL check(swap_type in('Sent', 'Received')),
preimage TEXT,
redeem_script TEXT,
blinding_key TEXT,
invoice_amount_sat INTEGER,
onchain_amount_sat INTEGER,
funding_address TEXT,
funding_amount_sat INTEGER
) STRICT;",
]
}
101 changes: 82 additions & 19 deletions lib/src/persist/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
mod migrations;

use std::collections::HashSet;

use anyhow::Result;
use rusqlite::{params, Connection, Row};
use rusqlite_migration::{Migrations, M};

use crate::OngoingReceiveSwap;

use migrations::current_migrations;

use crate::{OngoingSwap, SwapType, SwapTypeFilter};

pub(crate) struct Persister {
main_db_file: String,
}
Expand Down Expand Up @@ -35,31 +37,37 @@ impl Persister {
Ok(())
}

pub fn insert_ongoing_swaps(&self, swaps: &[OngoingReceiveSwap]) -> Result<()> {
pub fn insert_ongoing_swaps(&self, swaps: &[OngoingSwap]) -> Result<()> {
let con = self.get_connection()?;

let mut stmt = con.prepare(
"
INSERT INTO ongoing_swaps (
id,
swap_type,
preimage,
redeem_script,
blinding_key,
invoice_amount_sat,
onchain_amount_sat
onchain_amount_sat,
funding_address,
funding_amount_sat
)
VALUES (?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
",
)?;

for swap in swaps {
_ = stmt.execute((
&swap.id,
&swap.swap_type.to_string(),
&swap.preimage,
&swap.redeem_script,
&swap.blinding_key,
&swap.invoice_amount_sat,
&swap.onchain_amount_sat,
&swap.funding_address,
&swap.funding_amount_sat,
))?
}

Expand All @@ -75,27 +83,82 @@ impl Persister {
Ok(())
}

pub fn list_ongoing_swaps(&self) -> Result<Vec<OngoingReceiveSwap>> {
fn build_where_clause(&self, type_filters: &Option<Vec<SwapTypeFilter>>) -> String {
let mut where_clause: Vec<String> = vec![];

if let Some(filters) = type_filters {
if !filters.is_empty() {
let mut type_filter_clause: HashSet<SwapType> = HashSet::new();
for type_filter in filters {
type_filter_clause.insert(match type_filter {
SwapTypeFilter::Sent => SwapType::Submarine,
SwapTypeFilter::Received => SwapType::ReverseSubmarine,
});
}

where_clause.push(format!(
"swap_type in ({})",
type_filter_clause
.iter()
.map(|t| format!("'{}'", t.to_string()))
.collect::<Vec<_>>()
.join(", ")
));
}
}

let mut where_clause_str = String::new();
if !where_clause.is_empty() {
where_clause_str = String::from("where ");
where_clause_str.push_str(where_clause.join(" and ").as_str());
}
where_clause_str
}

fn sql_row_to_swap(&self, row: &Row) -> Result<OngoingSwap, rusqlite::Error> {
Ok(OngoingSwap {
id: row.get(0)?,
swap_type: row.get(1)?,
preimage: row.get(2)?,
redeem_script: row.get(3)?,
blinding_key: row.get(4)?,
invoice_amount_sat: row.get(5)?,
onchain_amount_sat: row.get(6)?,
funding_address: row.get(7)?,
funding_amount_sat: row.get(8)?,
})
}

pub fn list_ongoing_swaps(
&self,
type_filters: Option<Vec<SwapTypeFilter>>,
) -> Result<Vec<OngoingSwap>> {
let con = self.get_connection()?;

let mut stmt = con.prepare("SELECT * FROM ongoing_swaps")?;
let where_clause = self.build_where_clause(&type_filters);

let swaps: Vec<OngoingReceiveSwap> = stmt
let mut stmt = con.prepare(&format!(
"
SELECT
id,
swap_type,
preimage,
redeem_script,
blinding_key,
invoice_amount_sat,
onchain_amount_sat,
funding_address,
funding_amount_sat
FROM ongoing_swaps
{where_clause}
"
))?;

let swaps: Vec<OngoingSwap> = stmt
.query_map(params![], |row| self.sql_row_to_swap(row))?
.map(|i| i.unwrap())
.collect();

Ok(swaps)
}

fn sql_row_to_swap(&self, row: &Row) -> Result<OngoingReceiveSwap, rusqlite::Error> {
Ok(OngoingReceiveSwap {
id: row.get(0)?,
preimage: row.get(1)?,
redeem_script: row.get(2)?,
blinding_key: row.get(3)?,
invoice_amount_sat: row.get(4)?,
onchain_amount_sat: row.get(5)?,
})
}
}
Loading

0 comments on commit a88a3ca

Please sign in to comment.