breez_sdk_liquid/payjoin/utxo_select/
asset.rs

1use std::collections::BTreeMap;
2
3use anyhow::{anyhow, ensure, Result};
4use lwk_wollet::elements::AssetId;
5
6use crate::payjoin::utxo_select::{utxo_select_best, InOut};
7
8pub(crate) struct AssetSelectRequest {
9    pub fee_asset: AssetId,
10    pub wallet_utxos: Vec<InOut>,
11    pub user_outputs: Vec<InOut>,
12}
13
14pub(crate) struct AssetSelectResult {
15    pub asset_inputs: Vec<InOut>,
16    pub user_outputs: Vec<InOut>,
17    pub change_outputs: Vec<InOut>,
18    pub user_output_amounts: BTreeMap<AssetId, u64>,
19}
20
21pub(crate) fn asset_select(
22    AssetSelectRequest {
23        fee_asset,
24        wallet_utxos,
25        user_outputs,
26    }: AssetSelectRequest,
27) -> Result<AssetSelectResult> {
28    let mut user_output_amounts = BTreeMap::<AssetId, u64>::new();
29
30    for input in wallet_utxos.iter() {
31        ensure!(input.value > 0, anyhow!("Invalid amount {:?}", input));
32    }
33
34    for user_output in user_outputs.iter() {
35        ensure!(
36            user_output.value > 0,
37            anyhow!("Invalid amount {:?}", user_output),
38        );
39        *user_output_amounts.entry(user_output.asset_id).or_default() += user_output.value;
40    }
41
42    let mut asset_inputs = Vec::<InOut>::new();
43    let mut change_outputs = Vec::<InOut>::new();
44
45    for (&asset_id, &target_value) in user_output_amounts.iter() {
46        if asset_id != fee_asset {
47            let wallet_utxo = wallet_utxos
48                .iter()
49                .filter(|utxo| utxo.asset_id == asset_id)
50                .map(|utxo| utxo.value)
51                .collect::<Vec<_>>();
52            let available = wallet_utxo.iter().sum::<u64>();
53
54            ensure!(
55                available >= target_value,
56                anyhow!(
57                    "Not enough UTXOs for asset {}, required: {}, available: {}",
58                    asset_id,
59                    target_value,
60                    available
61                )
62            );
63
64            let selected =
65                utxo_select_best(target_value, &wallet_utxo).ok_or(anyhow!("No utxos selected"))?;
66
67            let mut total_value = 0;
68            for value in selected {
69                asset_inputs.push(InOut { asset_id, value });
70                total_value += value;
71            }
72
73            ensure!(
74                total_value >= target_value,
75                "Total value is less than target: {} < {}",
76                total_value,
77                target_value
78            );
79            let change_amount = total_value - target_value;
80            if change_amount > 0 {
81                change_outputs.push(InOut {
82                    asset_id,
83                    value: change_amount,
84                });
85            }
86        }
87    }
88
89    Ok(AssetSelectResult {
90        asset_inputs,
91        user_outputs,
92        change_outputs,
93        user_output_amounts,
94    })
95}