uniffi_lipalightninglib/actions_required.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
use crate::amount::{AsSats, ToAmount};
use crate::errors::Result;
use crate::fiat_topup::FiatTopup;
use crate::locker::Locker;
use crate::onchain::Onchain;
use crate::support::Support;
use crate::{ActionRequiredItem, FailedSwapInfo, RuntimeErrorCode, CLN_DUST_LIMIT_SAT};
use breez_sdk_core::{BitcoinAddressData, Network};
use perro::ResultTrait;
use std::ops::Not;
use std::sync::Arc;
pub struct ActionsRequired {
support: Arc<Support>,
fiat_topup: Arc<FiatTopup>,
onchain: Arc<Onchain>,
}
impl ActionsRequired {
pub(crate) fn new(
support: Arc<Support>,
fiat_topup: Arc<FiatTopup>,
onchain: Arc<Onchain>,
) -> Self {
Self {
support,
fiat_topup,
onchain,
}
}
/// List action required items.
///
/// Returns a list of actionable items. They can be:
/// * Uncompleted offers (either available for collection or failed).
/// * Unresolved failed swaps.
/// * Available funds resulting from channel closes.
///
/// Requires network: **yes**
pub fn list(&self) -> Result<Vec<ActionRequiredItem>> {
let uncompleted_offers = self.fiat_topup.query_uncompleted_offers()?;
let hidden_failed_swap_addresses = self
.support
.data_store
.lock_unwrap()
.retrieve_hidden_unresolved_failed_swaps()?;
let failed_swaps: Vec<_> = self
.onchain
.swap()
.list_failed_unresolved()?
.into_iter()
.filter(|s| {
hidden_failed_swap_addresses.contains(&s.address).not()
|| self
.onchain
.swap()
.prepare_sweep(
s.clone(),
BitcoinAddressData {
address: "1BitcoinEaterAddressDontSendf59kuE".to_string(),
network: Network::Bitcoin,
amount_sat: None,
label: None,
message: None,
},
)
.is_ok()
})
.collect();
let available_channel_closes_funds = self.support.get_node_info()?.onchain_balance;
let mut action_required_items: Vec<ActionRequiredItem> = uncompleted_offers
.into_iter()
.map(Into::into)
.chain(failed_swaps.into_iter().map(Into::into))
.collect();
// CLN currently forces a min-emergency onchain balance of 546 (the dust limit)
// TODO: Replace CLN_DUST_LIMIT_SAT with 0 if/when
// https://github.com/ElementsProject/lightning/issues/7131 is addressed
if available_channel_closes_funds.sats > CLN_DUST_LIMIT_SAT {
let utxos = self.support.get_node_utxos()?;
// If we already have a 546 sat UTXO, then we hide from the total amount available
let available_funds_sats = if utxos
.iter()
.any(|u| u.amount_millisatoshi == CLN_DUST_LIMIT_SAT * 1_000)
{
available_channel_closes_funds.sats
} else {
available_channel_closes_funds.sats - CLN_DUST_LIMIT_SAT
};
let optional_hidden_amount_sat = self
.support
.data_store
.lock_unwrap()
.retrieve_hidden_channel_close_onchain_funds_amount_sat()?;
let include_item_in_list = match optional_hidden_amount_sat {
Some(amount) if amount == available_channel_closes_funds.sats => self
.onchain
.channel_close()
.determine_resolving_fees()?
.is_some(),
_ => true,
};
if include_item_in_list {
action_required_items.push(ActionRequiredItem::ChannelClosesFundsAvailable {
available_funds: available_funds_sats
.as_sats()
.to_amount_down(&self.support.get_exchange_rate()),
});
}
}
// TODO: improve ordering of items in the returned vec
Ok(action_required_items)
}
/// Hides the topup with the given id. Can be called on expired topups so that they stop being returned
/// by [`ActionsRequired::list`].
///
/// Topup id can be obtained from [`OfferKind::Pocket`](crate::OfferKind::Pocket).
///
/// Requires network: **yes**
pub fn dismiss_topup(&self, id: String) -> Result<()> {
self.support
.offer_manager
.hide_topup(id)
.map_runtime_error_to(RuntimeErrorCode::OfferServiceUnavailable)
}
/// Hides the channel close action required item in case the amount cannot be recovered due
/// to it being too small. The item will reappear once the amount of funds changes or
/// onchain-fees go down enough to make the amount recoverable.
///
/// Requires network: **no**
pub fn hide_unrecoverable_channel_close_funds_item(&self) -> Result<()> {
let onchain_balance_sat = self.support.get_node_info()?.onchain_balance.sats;
self.support
.data_store
.lock_unwrap()
.store_hidden_channel_close_onchain_funds_amount_sat(onchain_balance_sat)?;
Ok(())
}
/// Hides the unresolved failed swap action required item in case the amount cannot be
/// recovered due to it being too small. The item will reappear once the onchain-fees go
/// down enough to make the amount recoverable.
///
/// Requires network: **no**
pub fn hide_unrecoverable_failed_swap_item(
&self,
failed_swap_info: FailedSwapInfo,
) -> Result<()> {
self.support
.data_store
.lock_unwrap()
.store_hidden_unresolved_failed_swap(&failed_swap_info.address)?;
Ok(())
}
}