mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2026-01-27 09:54:32 +01:00
feat: persist invoice rather than amount (#66)
Co-authored-by: ok300 <106775972+ok300@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
use std::borrow::Cow::{self, Owned};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{arg, Parser};
|
||||
@@ -15,7 +17,13 @@ use serde_json::to_string_pretty;
|
||||
#[derive(Parser, Debug, Clone, PartialEq)]
|
||||
pub(crate) enum Command {
|
||||
/// Send lbtc and receive btc through a swap
|
||||
SendPayment { bolt11: String },
|
||||
SendPayment {
|
||||
bolt11: String,
|
||||
|
||||
/// Delay for the send, in seconds
|
||||
#[arg(short, long)]
|
||||
delay: Option<u64>,
|
||||
},
|
||||
/// Receive lbtc and send btc through a swap
|
||||
ReceivePayment {
|
||||
#[arg(short, long)]
|
||||
@@ -76,10 +84,22 @@ pub(crate) fn handle_command(
|
||||
qr2term::print_qr(response.invoice.clone())?;
|
||||
command_result!(response)
|
||||
}
|
||||
Command::SendPayment { bolt11 } => {
|
||||
Command::SendPayment { bolt11, delay } => {
|
||||
let prepare_response = wallet.prepare_payment(&bolt11)?;
|
||||
let response = wallet.send_payment(&prepare_response)?;
|
||||
command_result!(response)
|
||||
|
||||
if let Some(delay) = delay {
|
||||
let wallet_cloned = wallet.clone();
|
||||
let prepare_cloned = prepare_response.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
thread::sleep(Duration::from_secs(delay));
|
||||
wallet_cloned.send_payment(&prepare_cloned).unwrap();
|
||||
});
|
||||
command_result!(prepare_response)
|
||||
} else {
|
||||
let response = wallet.send_payment(&prepare_response)?;
|
||||
command_result!(response)
|
||||
}
|
||||
}
|
||||
Command::GetInfo => {
|
||||
command_result!(wallet.get_info(true)?)
|
||||
|
||||
@@ -8,6 +8,7 @@ pub use wallet::*;
|
||||
// To avoid sendrawtransaction error "min relay fee not met"
|
||||
const CLAIM_ABSOLUTE_FEES: u64 = 134;
|
||||
const DEFAULT_DATA_DIR: &str = ".data";
|
||||
const MAIN_DB_FILE: &str = "storage.sql";
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ensure_sdk {
|
||||
|
||||
@@ -69,7 +69,7 @@ pub struct ReceivePaymentResponse {
|
||||
pub invoice: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct PreparePaymentResponse {
|
||||
pub id: String,
|
||||
pub funding_amount: u64,
|
||||
@@ -101,14 +101,26 @@ pub enum PaymentError {
|
||||
#[error("The generated preimage is not valid")]
|
||||
InvalidPreimage,
|
||||
|
||||
#[error("The specified funds have already been claimed")]
|
||||
AlreadyClaimed,
|
||||
|
||||
#[error("Generic boltz error: {err}")]
|
||||
BoltzGeneric { err: String },
|
||||
}
|
||||
|
||||
impl From<Error> for PaymentError {
|
||||
fn from(err: Error) -> Self {
|
||||
PaymentError::BoltzGeneric {
|
||||
err: format!("{err:?}"),
|
||||
match err {
|
||||
Error::Protocol(msg) => {
|
||||
if msg == "Could not find utxos for script" {
|
||||
return PaymentError::AlreadyClaimed;
|
||||
}
|
||||
|
||||
PaymentError::BoltzGeneric { err: msg }
|
||||
}
|
||||
_ => PaymentError::BoltzGeneric {
|
||||
err: format!("{err:?}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,13 +138,14 @@ pub(crate) enum OngoingSwap {
|
||||
id: String,
|
||||
amount_sat: u64,
|
||||
funding_address: String,
|
||||
invoice: String,
|
||||
},
|
||||
Receive {
|
||||
id: String,
|
||||
preimage: String,
|
||||
redeem_script: String,
|
||||
blinding_key: String,
|
||||
invoice_amount_sat: u64,
|
||||
invoice: String,
|
||||
onchain_amount_sat: u64,
|
||||
},
|
||||
}
|
||||
@@ -152,24 +165,35 @@ pub struct Payment {
|
||||
pub amount_sat: u64,
|
||||
#[serde(rename(serialize = "type"))]
|
||||
pub payment_type: PaymentType,
|
||||
|
||||
/// Only for [PaymentType::PendingReceive]
|
||||
pub invoice: Option<String>,
|
||||
}
|
||||
|
||||
impl From<OngoingSwap> for Payment {
|
||||
fn from(swap: OngoingSwap) -> Self {
|
||||
match swap {
|
||||
OngoingSwap::Send { amount_sat, .. } => Payment {
|
||||
OngoingSwap::Send {
|
||||
amount_sat,
|
||||
invoice,
|
||||
..
|
||||
} => Payment {
|
||||
id: None,
|
||||
timestamp: None,
|
||||
payment_type: PaymentType::PendingSend,
|
||||
amount_sat,
|
||||
invoice: Some(invoice),
|
||||
},
|
||||
OngoingSwap::Receive {
|
||||
onchain_amount_sat, ..
|
||||
onchain_amount_sat,
|
||||
invoice,
|
||||
..
|
||||
} => Payment {
|
||||
id: None,
|
||||
timestamp: None,
|
||||
payment_type: PaymentType::PendingReceive,
|
||||
amount_sat: onchain_amount_sat,
|
||||
invoice: Some(invoice),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ pub(crate) fn current_migrations() -> Vec<&'static str> {
|
||||
preimage TEXT NOT NULL,
|
||||
redeem_script TEXT NOT NULL,
|
||||
blinding_key TEXT NOT NULL,
|
||||
invoice_amount_sat INTEGER NOT NULL,
|
||||
invoice TEXT NOT NULL,
|
||||
onchain_amount_sat INTEGER NOT NULL,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
) STRICT;",
|
||||
@@ -13,6 +13,7 @@ pub(crate) fn current_migrations() -> Vec<&'static str> {
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
amount_sat INTEGER NOT NULL,
|
||||
funding_address TEXT NOT NULL,
|
||||
invoice TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
) STRICT;",
|
||||
]
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
mod migrations;
|
||||
|
||||
use std::{fs::create_dir_all, path::PathBuf, str::FromStr};
|
||||
|
||||
use anyhow::Result;
|
||||
use rusqlite::{params, Connection};
|
||||
use rusqlite_migration::{Migrations, M};
|
||||
|
||||
use crate::OngoingSwap;
|
||||
use crate::{OngoingSwap, MAIN_DB_FILE};
|
||||
|
||||
use migrations::current_migrations;
|
||||
|
||||
pub(crate) struct Persister {
|
||||
main_db_file: String,
|
||||
pub(crate) main_db_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Persister {
|
||||
pub fn new(working_dir: &str) -> Self {
|
||||
let main_db_file = format!("{}/storage.sql", working_dir);
|
||||
Persister { main_db_file }
|
||||
pub fn new(working_dir: &str) -> Result<Self> {
|
||||
let main_db_dir = PathBuf::from_str(working_dir)?;
|
||||
if !main_db_dir.exists() {
|
||||
create_dir_all(&main_db_dir)?;
|
||||
}
|
||||
Ok(Persister { main_db_dir })
|
||||
}
|
||||
|
||||
pub(crate) fn get_connection(&self) -> Result<Connection> {
|
||||
let con = Connection::open(self.main_db_file.clone())?;
|
||||
Ok(con)
|
||||
Ok(Connection::open(self.main_db_dir.join(MAIN_DB_FILE))?)
|
||||
}
|
||||
|
||||
pub fn init(&self) -> Result<()> {
|
||||
@@ -44,26 +48,28 @@ impl Persister {
|
||||
id,
|
||||
funding_address,
|
||||
amount_sat,
|
||||
invoice,
|
||||
} => {
|
||||
let mut stmt = con.prepare(
|
||||
"
|
||||
INSERT INTO ongoing_send_swaps (
|
||||
id,
|
||||
amount_sat,
|
||||
funding_address
|
||||
funding_address,
|
||||
invoice
|
||||
)
|
||||
VALUES (?, ?, ?)
|
||||
VALUES (?, ?, ?, ?)
|
||||
",
|
||||
)?;
|
||||
|
||||
_ = stmt.execute((&id, &amount_sat, &funding_address))?
|
||||
_ = stmt.execute((&id, &amount_sat, &funding_address, invoice))?
|
||||
}
|
||||
OngoingSwap::Receive {
|
||||
id,
|
||||
preimage,
|
||||
redeem_script,
|
||||
blinding_key,
|
||||
invoice_amount_sat,
|
||||
invoice,
|
||||
onchain_amount_sat,
|
||||
} => {
|
||||
let mut stmt = con.prepare(
|
||||
@@ -73,7 +79,7 @@ impl Persister {
|
||||
preimage,
|
||||
redeem_script,
|
||||
blinding_key,
|
||||
invoice_amount_sat,
|
||||
invoice,
|
||||
onchain_amount_sat
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
@@ -85,7 +91,7 @@ impl Persister {
|
||||
&preimage,
|
||||
&redeem_script,
|
||||
&blinding_key,
|
||||
&invoice_amount_sat,
|
||||
&invoice,
|
||||
&onchain_amount_sat,
|
||||
))?
|
||||
}
|
||||
@@ -123,6 +129,7 @@ impl Persister {
|
||||
id,
|
||||
amount_sat,
|
||||
funding_address,
|
||||
invoice,
|
||||
created_at
|
||||
FROM ongoing_send_swaps
|
||||
ORDER BY created_at
|
||||
@@ -135,6 +142,7 @@ impl Persister {
|
||||
id: row.get(0)?,
|
||||
amount_sat: row.get(1)?,
|
||||
funding_address: row.get(2)?,
|
||||
invoice: row.get(3)?,
|
||||
})
|
||||
})?
|
||||
.map(|i| i.unwrap())
|
||||
@@ -151,7 +159,7 @@ impl Persister {
|
||||
preimage,
|
||||
redeem_script,
|
||||
blinding_key,
|
||||
invoice_amount_sat,
|
||||
invoice,
|
||||
onchain_amount_sat,
|
||||
created_at
|
||||
FROM ongoing_receive_swaps
|
||||
@@ -166,7 +174,7 @@ impl Persister {
|
||||
preimage: row.get(1)?,
|
||||
redeem_script: row.get(2)?,
|
||||
blinding_key: row.get(3)?,
|
||||
invoice_amount_sat: row.get(4)?,
|
||||
invoice: row.get(4)?,
|
||||
onchain_amount_sat: row.get(5)?,
|
||||
})
|
||||
})?
|
||||
|
||||
@@ -10,7 +10,10 @@ use anyhow::{anyhow, Result};
|
||||
use boltz_client::{
|
||||
network::electrum::ElectrumConfig,
|
||||
swaps::{
|
||||
boltz::{BoltzApiClient, CreateSwapRequest, BOLTZ_MAINNET_URL, BOLTZ_TESTNET_URL},
|
||||
boltz::{
|
||||
BoltzApiClient, CreateSwapRequest, SubSwapStates, SwapStatusRequest, BOLTZ_MAINNET_URL,
|
||||
BOLTZ_TESTNET_URL,
|
||||
},
|
||||
liquid::{LBtcSwapScript, LBtcSwapTx},
|
||||
},
|
||||
util::secrets::{LBtcReverseRecovery, LiquidSwapKey, Preimage, SwapKey},
|
||||
@@ -46,7 +49,7 @@ pub struct Wallet {
|
||||
network: Network,
|
||||
wallet: Arc<Mutex<LwkWollet>>,
|
||||
active_address: Option<u32>,
|
||||
swap_persister: Persister,
|
||||
persister: Persister,
|
||||
data_dir_path: String,
|
||||
}
|
||||
|
||||
@@ -80,8 +83,8 @@ impl Wallet {
|
||||
|
||||
fs::create_dir_all(&data_dir_path)?;
|
||||
|
||||
let swap_persister = Persister::new(&data_dir_path);
|
||||
swap_persister.init()?;
|
||||
let persister = Persister::new(&data_dir_path)?;
|
||||
persister.init()?;
|
||||
|
||||
let wallet = Arc::new(Wallet {
|
||||
wallet,
|
||||
@@ -89,11 +92,11 @@ impl Wallet {
|
||||
electrum_url,
|
||||
signer: opts.signer,
|
||||
active_address: None,
|
||||
swap_persister,
|
||||
persister,
|
||||
data_dir_path,
|
||||
});
|
||||
|
||||
Wallet::track_claims(&wallet)?;
|
||||
Wallet::track_pending_swaps(&wallet)?;
|
||||
|
||||
Ok(wallet)
|
||||
}
|
||||
@@ -110,25 +113,52 @@ impl Wallet {
|
||||
Ok(descriptor_str.parse()?)
|
||||
}
|
||||
|
||||
fn track_claims(self: &Arc<Wallet>) -> Result<()> {
|
||||
fn track_pending_swaps(self: &Arc<Wallet>) -> Result<()> {
|
||||
let cloned = self.clone();
|
||||
let client = self.boltz_client();
|
||||
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
let ongoing_swaps = cloned.swap_persister.list_ongoing_swaps().unwrap();
|
||||
let ongoing_swaps = cloned.persister.list_ongoing_swaps().unwrap();
|
||||
|
||||
for swap in ongoing_swaps {
|
||||
if let OngoingSwap::Receive {
|
||||
id,
|
||||
preimage,
|
||||
redeem_script,
|
||||
blinding_key,
|
||||
..
|
||||
} = swap
|
||||
{
|
||||
match cloned.try_claim(&preimage, &redeem_script, &blinding_key, None) {
|
||||
Ok(_) => cloned.swap_persister.resolve_ongoing_swap(&id).unwrap(),
|
||||
Err(e) => warn!("Could not claim yet. Err: {e}"),
|
||||
match swap {
|
||||
OngoingSwap::Receive {
|
||||
id,
|
||||
preimage,
|
||||
redeem_script,
|
||||
blinding_key,
|
||||
..
|
||||
} => match cloned.try_claim(&preimage, &redeem_script, &blinding_key, None) {
|
||||
Ok(_) => cloned
|
||||
.persister
|
||||
.resolve_ongoing_swap(&id)
|
||||
.unwrap_or_else(|err| {
|
||||
warn!("Could not write to database. Err: {err:?}")
|
||||
}),
|
||||
Err(err) => {
|
||||
if let PaymentError::AlreadyClaimed = err {
|
||||
warn!("Funds already claimed");
|
||||
cloned.persister.resolve_ongoing_swap(&id).unwrap()
|
||||
}
|
||||
warn!("Could not claim yet. Err: {err}");
|
||||
}
|
||||
},
|
||||
OngoingSwap::Send { id, .. } => {
|
||||
let Ok(status_response) =
|
||||
client.swap_status(SwapStatusRequest { id: id.clone() })
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if status_response.status == SubSwapStates::TransactionClaimed.to_string() {
|
||||
cloned
|
||||
.persister
|
||||
.resolve_ongoing_swap(&id)
|
||||
.unwrap_or_else(|err| {
|
||||
warn!("Could not write to database. Err: {err:?}")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,11 +272,12 @@ impl Wallet {
|
||||
let funding_amount = swap_response.get_funding_amount()?;
|
||||
let funding_address = swap_response.get_funding_address()?;
|
||||
|
||||
self.swap_persister
|
||||
self.persister
|
||||
.insert_ongoing_swap(&[OngoingSwap::Send {
|
||||
id: id.clone(),
|
||||
amount_sat,
|
||||
funding_address: funding_address.clone(),
|
||||
invoice: invoice.to_string(),
|
||||
}])
|
||||
.map_err(|_| PaymentError::PersistError)?;
|
||||
|
||||
@@ -269,10 +300,6 @@ impl Wallet {
|
||||
err: err.to_string(),
|
||||
})?;
|
||||
|
||||
self.swap_persister
|
||||
.resolve_ongoing_swap(&res.id)
|
||||
.map_err(|_| PaymentError::PersistError)?;
|
||||
|
||||
Ok(SendPaymentResponse { txid })
|
||||
}
|
||||
|
||||
@@ -396,18 +423,13 @@ impl Wallet {
|
||||
return Err(PaymentError::InvalidInvoice);
|
||||
};
|
||||
|
||||
let invoice_amount_sat = invoice
|
||||
.amount_milli_satoshis()
|
||||
.ok_or(PaymentError::InvalidInvoice)?
|
||||
/ 1000;
|
||||
|
||||
self.swap_persister
|
||||
self.persister
|
||||
.insert_ongoing_swap(dbg!(&[OngoingSwap::Receive {
|
||||
id: swap_id.clone(),
|
||||
preimage: preimage_str,
|
||||
blinding_key: blinding_str,
|
||||
redeem_script,
|
||||
invoice_amount_sat,
|
||||
invoice: invoice.to_string(),
|
||||
onchain_amount_sat,
|
||||
}]))
|
||||
.map_err(|_| PaymentError::PersistError)?;
|
||||
@@ -438,12 +460,13 @@ impl Wallet {
|
||||
true => PaymentType::Received,
|
||||
false => PaymentType::Sent,
|
||||
},
|
||||
invoice: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if include_pending {
|
||||
for swap in self.swap_persister.list_ongoing_swaps()? {
|
||||
for swap in self.persister.list_ongoing_swaps()? {
|
||||
payments.insert(0, swap.into());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user