use chia_bls::PublicKey;
use chia_protocol::{Bytes32, Coin, CoinSpend};
use chia_puzzle_types::standard::StandardArgs;
use chia_sdk_driver::{Cat, CatSpend, SpendContext, SpendWithConditions, StandardLayer};
use chia_sdk_types::Conditions;
use crate::error::WalletError;
fn smallest_cap<T, F>(mut items: Vec<T>, cap: usize, key: F) -> Result<Vec<T>, WalletError>
where
F: Fn(&T) -> (u64, Bytes32),
{
if items.len() < 2 {
return Err(WalletError::Parse(
"consolidation requires at least 2 coins".to_string(),
));
}
items.sort_by(|a, b| {
let (aa, ai) = key(a);
let (ba, bi) = key(b);
aa.cmp(&ba).then_with(|| ai.cmp(&bi))
});
let take = cap.min(items.len());
if take < 2 {
return Err(WalletError::Parse(
"cap must allow merging at least 2 coins".to_string(),
));
}
items.truncate(take);
Ok(items)
}
pub fn build_coin_consolidation(
spender_synthetic_key: PublicKey,
coins: Vec<Coin>,
cap: usize,
fee: u64,
) -> Result<Vec<CoinSpend>, WalletError> {
let merge = smallest_cap(coins, cap, |c| (c.amount, c.coin_id()))?;
let total = merge
.iter()
.map(|c| c.amount)
.fold(0u64, u64::saturating_add);
if total <= fee {
return Err(WalletError::Parse(
"fee must be less than the consolidated amount".to_string(),
));
}
let owner_puzzle_hash: Bytes32 = StandardArgs::curry_tree_hash(spender_synthetic_key).into();
let mut ctx = SpendContext::new();
let p2 = StandardLayer::new(spender_synthetic_key);
let lead = merge[0];
let lead_id = lead.coin_id();
for coin in merge.iter().skip(1) {
p2.spend(
&mut ctx,
*coin,
Conditions::new().assert_concurrent_spend(lead_id),
)?;
}
let hint = ctx.hint(owner_puzzle_hash)?;
let mut lead_conditions = Conditions::new().create_coin(owner_puzzle_hash, total - fee, hint);
if fee > 0 {
lead_conditions = lead_conditions.reserve_fee(fee);
}
p2.spend(&mut ctx, lead, lead_conditions)?;
Ok(ctx.take())
}
pub fn build_cat_consolidation(
spender_synthetic_key: PublicKey,
cats: Vec<Cat>,
cap: usize,
) -> Result<Vec<CoinSpend>, WalletError> {
if let Some(first) = cats.first() {
let asset_id = first.info.asset_id;
if cats.iter().any(|c| c.info.asset_id != asset_id) {
return Err(WalletError::Parse(
"selected CATs mix more than one asset id".to_string(),
));
}
}
let merge = smallest_cap(cats, cap, |c| (c.coin.amount, c.coin.coin_id()))?;
let total = merge
.iter()
.map(|c| c.coin.amount)
.fold(0u64, u64::saturating_add);
let owner_puzzle_hash: Bytes32 = StandardArgs::curry_tree_hash(spender_synthetic_key).into();
let mut ctx = SpendContext::new();
let p2 = StandardLayer::new(spender_synthetic_key);
let lead_id = merge[0].coin.coin_id();
let mut cat_spends = Vec::with_capacity(merge.len());
for (i, cat) in merge.iter().enumerate() {
let conditions = if i == 0 {
let hint = ctx.hint(owner_puzzle_hash)?;
Conditions::new().create_coin(owner_puzzle_hash, total, hint)
} else {
Conditions::new().assert_concurrent_spend(lead_id)
};
let inner = p2.spend_with_conditions(&mut ctx, conditions)?;
cat_spends.push(CatSpend::new(*cat, inner));
}
Cat::spend_all(&mut ctx, &cat_spends)?;
Ok(ctx.take())
}