breez_sdk_liquid/payjoin/utxo_select/
asset.rs1use 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}