mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2025-12-23 17:04:25 +01:00
list-refundables: show refundable amount, not swap amount (#516)
* list-refundables: show refundable amount, not swap amount * Rename chainswap fn for clarity get_lockup_swap_script_pubkey only applies to Receive Chain Swaps, so it was renamed to get_receive_lockup_swap_script_pubkey. * list_refundables: batch calls to fetch balance from chain service * Simplify conversion from Chain Swap to RefundableSwap * Fix MockBitcoinChainService * Re-generate flutter bindings * Add utility for creating SdkError::Generic with &str or String * Chain Swap getter for swap script pk: throw SdkError instead of anyhow::Error * Update RefundableSwap comment Co-authored-by: Ross Savage <551697+dangeross@users.noreply.github.com> * Re-generate dart files --------- Co-authored-by: Ross Savage <551697+dangeross@users.noreply.github.com>
This commit is contained in:
@@ -44,10 +44,7 @@ impl log::Log for UniffiBindingLogger {
|
||||
|
||||
/// If used, this must be called before `connect`
|
||||
pub fn set_logger(logger: Box<dyn Logger>) -> Result<(), SdkError> {
|
||||
UniffiBindingLogger::init(logger).map_err(|_| SdkError::Generic {
|
||||
err: "Logger already created".into(),
|
||||
})?;
|
||||
Ok(())
|
||||
UniffiBindingLogger::init(logger).map_err(|_| SdkError::generic("Logger already created"))
|
||||
}
|
||||
|
||||
pub fn connect(req: ConnectRequest) -> Result<Arc<BindingLiquidSdk>, SdkError> {
|
||||
|
||||
@@ -54,9 +54,7 @@ pub async fn connect(req: ConnectRequest) -> Result<BindingLiquidSdk, SdkError>
|
||||
|
||||
/// If used, this must be called before `connect`. It can only be called once.
|
||||
pub fn breez_log_stream(s: StreamSink<LogEntry>) -> Result<()> {
|
||||
DartBindingLogger::init(s).map_err(|_| SdkError::Generic {
|
||||
err: "Log stream already created".into(),
|
||||
})?;
|
||||
DartBindingLogger::init(s).map_err(|_| SdkError::generic("Log stream already created"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,9 @@ pub trait BitcoinChainService: Send + Sync {
|
||||
/// Return the confirmed and unconfirmed balances of a script hash
|
||||
fn script_get_balance(&self, script: &Script) -> Result<GetBalanceRes>;
|
||||
|
||||
/// Return the confirmed and unconfirmed balances of a list of script hashes
|
||||
fn scripts_get_balance(&self, scripts: &[&Script]) -> Result<Vec<GetBalanceRes>>;
|
||||
|
||||
/// Verify that a transaction appears in the address script history
|
||||
async fn verify_tx(
|
||||
&self,
|
||||
@@ -207,6 +210,10 @@ impl BitcoinChainService for HybridBitcoinChainService {
|
||||
Ok(self.client.script_get_balance(script)?)
|
||||
}
|
||||
|
||||
fn scripts_get_balance(&self, scripts: &[&Script]) -> Result<Vec<GetBalanceRes>> {
|
||||
Ok(self.client.batch_script_get_balance(scripts)?)
|
||||
}
|
||||
|
||||
async fn verify_tx(
|
||||
&self,
|
||||
address: &Address,
|
||||
|
||||
@@ -142,11 +142,7 @@ impl ChainSwapHandler {
|
||||
let is_monitoring_expired = current_height > monitoring_block_height;
|
||||
|
||||
if (is_swap_expired && !is_monitoring_expired) || swap.state == RefundPending {
|
||||
let swap_script = swap.get_lockup_swap_script()?.as_bitcoin_script()?;
|
||||
let script_pubkey = swap_script
|
||||
.to_address(self.config.network.as_bitcoin_chain())
|
||||
.map_err(|e| anyhow!("Error getting script address: {e:?}"))?
|
||||
.script_pubkey();
|
||||
let script_pubkey = swap.get_receive_lockup_swap_script_pubkey(self.config.network)?;
|
||||
let script_balance = self
|
||||
.bitcoin_chain_service
|
||||
.lock()
|
||||
@@ -743,9 +739,9 @@ impl ChainSwapHandler {
|
||||
let swap = self
|
||||
.persister
|
||||
.fetch_chain_swap_by_lockup_address(lockup_address)?
|
||||
.ok_or(SdkError::Generic {
|
||||
err: format!("Swap {} not found", lockup_address),
|
||||
})?;
|
||||
.ok_or(SdkError::generic(format!(
|
||||
"Chain Swap with lockup address {lockup_address} not found"
|
||||
)))?;
|
||||
|
||||
let refund_tx_id = swap.refund_tx_id.clone();
|
||||
if let Some(refund_tx_id) = &refund_tx_id {
|
||||
|
||||
@@ -28,31 +28,34 @@ pub enum SdkError {
|
||||
#[error("Service connectivity: {err}")]
|
||||
ServiceConnectivity { err: String },
|
||||
}
|
||||
impl SdkError {
|
||||
pub fn generic<T: AsRef<str>>(err: T) -> Self {
|
||||
Self::Generic {
|
||||
err: err.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for SdkError {
|
||||
fn from(e: Error) -> Self {
|
||||
SdkError::Generic { err: e.to_string() }
|
||||
SdkError::generic(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<boltz_client::error::Error> for SdkError {
|
||||
fn from(err: boltz_client::error::Error) -> Self {
|
||||
match err {
|
||||
boltz_client::error::Error::HTTP(e) => SdkError::Generic {
|
||||
err: format!("Could not contact servers: {e:?}"),
|
||||
},
|
||||
_ => SdkError::Generic {
|
||||
err: format!("{err:?}"),
|
||||
},
|
||||
boltz_client::error::Error::HTTP(e) => {
|
||||
SdkError::generic(format!("Could not contact servers: {e:?}"))
|
||||
}
|
||||
_ => SdkError::generic(format!("{err:?}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<secp256k1::Error> for SdkError {
|
||||
fn from(err: secp256k1::Error) -> Self {
|
||||
SdkError::Generic {
|
||||
err: format!("{err:?}"),
|
||||
}
|
||||
SdkError::generic(format!("{err:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::path::PathBuf;
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
use boltz_client::{
|
||||
bitcoin::ScriptBuf,
|
||||
network::Chain,
|
||||
swaps::boltz::{
|
||||
CreateChainResponse, CreateReverseResponse, CreateSubmarineResponse, Leaf, Side, SwapTree,
|
||||
@@ -608,6 +609,27 @@ impl ChainSwap {
|
||||
Ok(swap_script)
|
||||
}
|
||||
|
||||
/// Returns the lockup script pubkey for Receive Chain Swaps
|
||||
pub(crate) fn get_receive_lockup_swap_script_pubkey(
|
||||
&self,
|
||||
network: LiquidNetwork,
|
||||
) -> SdkResult<ScriptBuf> {
|
||||
let swap_script = self.get_lockup_swap_script()?.as_bitcoin_script()?;
|
||||
let script_pubkey = swap_script
|
||||
.to_address(network.as_bitcoin_chain())
|
||||
.map_err(|e| SdkError::generic(format!("Error getting script address: {e:?}")))?
|
||||
.script_pubkey();
|
||||
Ok(script_pubkey)
|
||||
}
|
||||
|
||||
pub(crate) fn to_refundable(&self, refundable_amount_sat: u64) -> RefundableSwap {
|
||||
RefundableSwap {
|
||||
swap_address: self.lockup_address.clone(),
|
||||
timestamp: self.created_at,
|
||||
amount_sat: refundable_amount_sat,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_boltz_struct_to_json(
|
||||
create_response: &CreateChainResponse,
|
||||
expected_swap_id: &str,
|
||||
@@ -681,11 +703,11 @@ impl SendSwap {
|
||||
&self.get_boltz_create_response()?,
|
||||
self.get_refund_keypair()?.public_key().into(),
|
||||
)
|
||||
.map_err(|e| SdkError::Generic {
|
||||
err: format!(
|
||||
.map_err(|e| {
|
||||
SdkError::generic(format!(
|
||||
"Failed to create swap script for Send Swap {}: {e:?}",
|
||||
self.id
|
||||
),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -806,17 +828,9 @@ impl ReceiveSwap {
|
||||
pub struct RefundableSwap {
|
||||
pub swap_address: String,
|
||||
pub timestamp: u32,
|
||||
/// Amount that is refundable, from all UTXOs
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The payment state of an individual payment.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Hash)]
|
||||
|
||||
@@ -246,9 +246,7 @@ impl LiquidSdk {
|
||||
let mut is_started = self.is_started.write().await;
|
||||
self.shutdown_sender
|
||||
.send(())
|
||||
.map_err(|e| SdkError::Generic {
|
||||
err: format!("Shutdown failed: {e}"),
|
||||
})?;
|
||||
.map_err(|e| SdkError::generic(format!("Shutdown failed: {e}")))?;
|
||||
*is_started = false;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1740,12 +1738,34 @@ impl LiquidSdk {
|
||||
/// List all failed chain swaps that need to be refunded.
|
||||
/// They can be refunded by calling [LiquidSdk::prepare_refund] then [LiquidSdk::refund].
|
||||
pub async fn list_refundables(&self) -> SdkResult<Vec<RefundableSwap>> {
|
||||
Ok(self
|
||||
.persister
|
||||
.list_refundable_chain_swaps()?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect())
|
||||
let chain_swaps = self.persister.list_refundable_chain_swaps()?;
|
||||
|
||||
let mut lockup_script_pubkeys = vec![];
|
||||
for swap in &chain_swaps {
|
||||
let script_pubkey = swap.get_receive_lockup_swap_script_pubkey(self.config.network)?;
|
||||
lockup_script_pubkeys.push(script_pubkey);
|
||||
}
|
||||
let lockup_scripts: Vec<&boltz_client::bitcoin::Script> = lockup_script_pubkeys
|
||||
.iter()
|
||||
.map(|s| s.as_script())
|
||||
.collect();
|
||||
let scripts_balance = self
|
||||
.bitcoin_chain_service
|
||||
.lock()
|
||||
.await
|
||||
.scripts_get_balance(&lockup_scripts)?;
|
||||
|
||||
let mut refundables = vec![];
|
||||
for (chain_swap, script_balance) in chain_swaps.into_iter().zip(scripts_balance) {
|
||||
let swap_id = &chain_swap.id;
|
||||
let refundable_confirmed_sat = script_balance.confirmed;
|
||||
info!("Incoming Chain Swap {swap_id} is refundable with {refundable_confirmed_sat} confirmed sats");
|
||||
|
||||
let refundable: RefundableSwap = chain_swap.to_refundable(refundable_confirmed_sat);
|
||||
refundables.push(refundable);
|
||||
}
|
||||
|
||||
Ok(refundables)
|
||||
}
|
||||
|
||||
/// Prepares to refund a failed chain swap by calculating the refund transaction size and absolute fee.
|
||||
@@ -2069,10 +2089,7 @@ impl LiquidSdk {
|
||||
.unwrap_or(self.persister.get_default_backup_path());
|
||||
ensure_sdk!(
|
||||
backup_path.exists(),
|
||||
SdkError::Generic {
|
||||
err: "Backup file does not exist".to_string()
|
||||
}
|
||||
.into()
|
||||
SdkError::generic("Backup file does not exist").into()
|
||||
);
|
||||
self.persister.restore_from_backup(backup_path)
|
||||
}
|
||||
@@ -2121,12 +2138,10 @@ impl LiquidSdk {
|
||||
});
|
||||
};
|
||||
|
||||
let preimage_str = preimage
|
||||
.clone()
|
||||
.ok_or(SdkError::Generic {
|
||||
let preimage_str =
|
||||
preimage.clone().ok_or(LnUrlPayError::Generic {
|
||||
err: "Payment successful but no preimage found".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
})?;
|
||||
let preimage =
|
||||
sha256::Hash::from_str(&preimage_str).map_err(|_| {
|
||||
LnUrlPayError::Generic {
|
||||
|
||||
@@ -34,21 +34,17 @@ impl BoltzSwapper {
|
||||
)
|
||||
}
|
||||
Direction::Outgoing => {
|
||||
return Err(SdkError::Generic {
|
||||
err: format!(
|
||||
return Err(SdkError::generic(format!(
|
||||
"Cannot create Bitcoin refund wrapper for outgoing Chain swap {}",
|
||||
swap.id
|
||||
),
|
||||
});
|
||||
)));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(SdkError::Generic {
|
||||
err: format!(
|
||||
return Err(SdkError::generic(format!(
|
||||
"Cannot create Bitcoin refund wrapper for swap {}",
|
||||
swap.id()
|
||||
),
|
||||
});
|
||||
)));
|
||||
}
|
||||
}?;
|
||||
Ok(refund_wrapper)
|
||||
@@ -64,28 +60,21 @@ impl BoltzSwapper {
|
||||
) -> Result<Transaction, SdkError> {
|
||||
ensure_sdk!(
|
||||
swap.direction == Direction::Incoming,
|
||||
SdkError::Generic {
|
||||
err: "Cannot create BTC refund tx for outgoing Chain swaps.".to_string()
|
||||
}
|
||||
SdkError::generic("Cannot create BTC refund tx for outgoing Chain swaps.")
|
||||
);
|
||||
|
||||
let address = Address::from_str(refund_address).map_err(|err| SdkError::Generic {
|
||||
err: format!("Could not parse address: {err:?}"),
|
||||
})?;
|
||||
let address = Address::from_str(refund_address)
|
||||
.map_err(|err| SdkError::generic(format!("Could not parse address: {err:?}")))?;
|
||||
|
||||
ensure_sdk!(
|
||||
address.is_valid_for_network(self.config.network.into()),
|
||||
SdkError::Generic {
|
||||
err: "Address network validation failed".to_string()
|
||||
}
|
||||
SdkError::generic("Address network validation failed")
|
||||
);
|
||||
|
||||
let utxo = utxos
|
||||
.first()
|
||||
.and_then(|utxo| utxo.as_bitcoin().cloned())
|
||||
.ok_or(SdkError::Generic {
|
||||
err: "No UTXO found".to_string(),
|
||||
})?;
|
||||
.ok_or(SdkError::generic("No UTXO found"))?;
|
||||
|
||||
let swap_script = swap.get_lockup_swap_script()?.as_bitcoin_script()?;
|
||||
let refund_tx = BtcSwapTx {
|
||||
|
||||
@@ -111,12 +111,10 @@ impl BoltzSwapper {
|
||||
let refund_wrapper = match swap {
|
||||
Swap::Chain(swap) => match swap.direction {
|
||||
Direction::Incoming => {
|
||||
return Err(SdkError::Generic {
|
||||
err: format!(
|
||||
return Err(SdkError::generic(format!(
|
||||
"Cannot create Liquid refund wrapper for incoming Chain swap {}",
|
||||
swap.id
|
||||
),
|
||||
});
|
||||
)));
|
||||
}
|
||||
Direction::Outgoing => {
|
||||
let swap_script = swap.get_lockup_swap_script()?;
|
||||
@@ -140,12 +138,10 @@ impl BoltzSwapper {
|
||||
)
|
||||
}
|
||||
Swap::Receive(swap) => {
|
||||
return Err(SdkError::Generic {
|
||||
err: format!(
|
||||
return Err(SdkError::generic(format!(
|
||||
"Cannot create Liquid refund wrapper for Receive swap {}",
|
||||
swap.id
|
||||
),
|
||||
});
|
||||
)));
|
||||
}
|
||||
}?;
|
||||
Ok(refund_wrapper)
|
||||
@@ -162,9 +158,7 @@ impl BoltzSwapper {
|
||||
Swap::Chain(swap) => {
|
||||
ensure_sdk!(
|
||||
swap.direction == Direction::Outgoing,
|
||||
SdkError::Generic {
|
||||
err: "Cannot create LBTC refund tx for incoming Chain swaps".to_string()
|
||||
}
|
||||
SdkError::generic("Cannot create LBTC refund tx for incoming Chain swaps")
|
||||
);
|
||||
|
||||
(
|
||||
@@ -179,25 +173,23 @@ impl BoltzSwapper {
|
||||
Preimage::new(),
|
||||
),
|
||||
Swap::Receive(_) => {
|
||||
return Err(SdkError::Generic {
|
||||
err: "Cannot create LBTC refund tx for Receive swaps.".to_string(),
|
||||
});
|
||||
return Err(SdkError::generic(
|
||||
"Cannot create LBTC refund tx for Receive swaps.",
|
||||
));
|
||||
}
|
||||
};
|
||||
let swap_id = swap.id();
|
||||
|
||||
let address = Address::from_str(refund_address).map_err(|err| SdkError::Generic {
|
||||
err: format!("Could not parse address: {err:?}"),
|
||||
})?;
|
||||
let address = Address::from_str(refund_address)
|
||||
.map_err(|err| SdkError::generic(format!("Could not parse address: {err:?}")))?;
|
||||
|
||||
let genesis_hash = liquid_genesis_hash(&self.liquid_electrum_config)?;
|
||||
|
||||
let (funding_outpoint, funding_tx_out) = *utxos
|
||||
let (funding_outpoint, funding_tx_out) =
|
||||
*utxos
|
||||
.first()
|
||||
.and_then(|utxo| utxo.as_liquid())
|
||||
.ok_or(SdkError::Generic {
|
||||
err: "No refundable UTXOs found".to_string(),
|
||||
})?;
|
||||
.ok_or(SdkError::generic("No refundable UTXOs found"))?;
|
||||
|
||||
let refund_tx = LBtcSwapTx {
|
||||
kind: SwapTxKind::Refund,
|
||||
|
||||
@@ -297,12 +297,10 @@ impl Swapper for BoltzSwapper {
|
||||
),
|
||||
Swap::Send(swap) => (swap.get_refund_keypair()?, Preimage::new()),
|
||||
Swap::Receive(swap) => {
|
||||
return Err(SdkError::Generic {
|
||||
err: format!(
|
||||
return Err(SdkError::generic(format!(
|
||||
"Failed to retrieve refund keypair and preimage for Receive swap {}: invalid swap type",
|
||||
swap.id
|
||||
),
|
||||
});
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use boltz_client::{
|
||||
Amount,
|
||||
};
|
||||
use electrum_client::bitcoin::{consensus::deserialize, OutPoint, Script, TxOut};
|
||||
use electrum_client::GetBalanceRes;
|
||||
use lwk_wollet::{
|
||||
elements::{BlockHash, Txid as ElementsTxid},
|
||||
History,
|
||||
@@ -188,6 +189,10 @@ impl BitcoinChainService for MockBitcoinChainService {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn scripts_get_balance(&self, _scripts: &[&Script]) -> Result<Vec<GetBalanceRes>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn verify_tx(
|
||||
&self,
|
||||
_address: &boltz_client::Address,
|
||||
|
||||
@@ -1047,6 +1047,8 @@ class RefundResponse {
|
||||
class RefundableSwap {
|
||||
final String swapAddress;
|
||||
final int timestamp;
|
||||
|
||||
/// Amount that is refundable, from all UTXOs
|
||||
final BigInt amountSat;
|
||||
|
||||
const RefundableSwap({
|
||||
|
||||
Reference in New Issue
Block a user