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(())
    }
}