use crate::error::ErrorContext;
use crate::swap_storage::SwapStorage;
use crate::wallet::BoardingWallet;
use crate::wallet::OnchainWallet;
use crate::Blockchain;
use crate::Client;
use crate::Error;
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, K>(
client: &Client<B, W, S, K>,
target_amount: Amount,
) -> Result<
(
Vec<unilateral_exit::OnChainInput>,
Vec<unilateral_exit::VtxoInput>,
),
Error,
>
where
B: Blockchain,
W: BoardingWallet + OnchainWallet,
S: SwapStorage + 'static,
K: crate::KeyProvider,
{
let boarding_outputs = client.inner.wallet.get_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");
if selected_boarding_outputs.insert(unilateral_exit::OnChainInput::new(
boarding_output.clone(),
*amount,
*outpoint,
)) {
selected_amount += *amount;
}
}
}
}
}
let mut selected_vtxo_outputs = HashSet::new();
for (_, vtxo) in client.get_offchain_addresses()? {
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");
selected_vtxo_outputs.insert(unilateral_exit::VtxoInput::new(
*outpoint,
vtxo.exit_delay(),
TxOut {
value: *amount,
script_pubkey: vtxo.script_pubkey(),
},
vtxo.exit_spend_info()?,
));
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(),
))
}