use crate::error::ErrorContext;
use crate::swap_storage::SwapStorage;
use crate::wallet::OnchainWallet;
use crate::Blockchain;
use crate::Client;
use crate::Error;
use ark_core::contract::SpendPathKind;
use ark_core::unilateral_exit;
use ark_core::ExplorerUtxo;
use bitcoin::Amount;
use bitcoin::TxOut;
use jiff::Timestamp;
use std::collections::HashSet;
use std::time::Duration;
pub async fn coin_select_for_onchain<B, W, S>(
client: &Client<B, W, S>,
target_amount: Amount,
) -> Result<
(
Vec<unilateral_exit::OnChainInput>,
Vec<unilateral_exit::VtxoInput>,
),
Error,
>
where
B: Blockchain,
W: OnchainWallet,
S: SwapStorage + 'static,
{
let boarding_outputs = client.boarding_outputs()?;
let now = Timestamp::now();
let mut selected_boarding_outputs = HashSet::new();
let mut selected_amount = Amount::ZERO;
for boarding_output in boarding_outputs.iter() {
if target_amount <= selected_amount {
return Ok((selected_boarding_outputs.into_iter().collect(), Vec::new()));
}
let outpoints = client
.blockchain()
.find_outpoints(boarding_output.address())
.await?;
for o in outpoints.iter() {
if let ExplorerUtxo {
outpoint,
amount,
confirmation_blocktime: Some(confirmation_blocktime),
confirmations,
is_spent: false,
} = o
{
if boarding_output.can_be_claimed_unilaterally_by_owner(
now.as_duration().try_into().context("invalid now")?,
Duration::from_secs(*confirmation_blocktime),
*confirmations,
) {
tracing::debug!(?outpoint, %amount, ?boarding_output, "Selected boarding output");
let script_pubkey = boarding_output.script_pubkey();
let spend_selection = boarding_output.spend_selection(SpendPathKind::Exit)?;
if selected_boarding_outputs.insert(
unilateral_exit::OnChainInput::new_with_spend_selection(
boarding_output.exit_delay(),
script_pubkey,
spend_selection,
*amount,
*outpoint,
),
) {
selected_amount += *amount;
}
}
}
}
}
let mut selected_vtxo_outputs = HashSet::new();
for contract in client.active_offchain_contracts()? {
let vtxo = &contract.vtxo;
if target_amount <= selected_amount {
return Ok((
selected_boarding_outputs.into_iter().collect(),
selected_vtxo_outputs.into_iter().collect(),
));
}
let outpoints = client.blockchain().find_outpoints(vtxo.address()).await?;
for o in outpoints.iter() {
if let ExplorerUtxo {
outpoint,
amount,
confirmation_blocktime: Some(confirmation_blocktime),
confirmations,
is_spent: false,
} = o
{
if vtxo.can_be_claimed_unilaterally_by_owner(
now.as_duration().try_into().map_err(Error::ad_hoc)?,
Duration::from_secs(*confirmation_blocktime),
*confirmations,
) {
tracing::debug!(?outpoint, %amount, ?vtxo, "Selected VTXO");
let script_pubkey = vtxo.script_pubkey();
let spend_selection = contract.spend_selection(SpendPathKind::Exit)?;
selected_vtxo_outputs.insert(
unilateral_exit::VtxoInput::new_with_spend_selection(
*outpoint,
vtxo.exit_delay(),
TxOut {
value: *amount,
script_pubkey,
},
spend_selection,
),
);
selected_amount += *amount;
}
}
}
}
if selected_amount < target_amount {
return Err(Error::coin_select(format!(
"insufficient funds: selected = {selected_amount}, needed = {target_amount}"
)));
}
Ok((
selected_boarding_outputs.into_iter().collect(),
selected_vtxo_outputs.into_iter().collect(),
))
}