4 个版本 (2 个破坏性更新)

0.3.0 2024 年 3 月 31 日
0.2.0 2024 年 1 月 15 日
0.1.1 2023 年 12 月 5 日
0.1.0 2023 年 11 月 30 日

#749魔豆

Download history 712/week @ 2024-03-13 1857/week @ 2024-03-20 1292/week @ 2024-03-27 1622/week @ 2024-04-03 1187/week @ 2024-04-10 696/week @ 2024-04-17 1155/week @ 2024-04-24 550/week @ 2024-05-01 584/week @ 2024-05-08 763/week @ 2024-05-15 890/week @ 2024-05-22 1253/week @ 2024-05-29 1044/week @ 2024-06-05 385/week @ 2024-06-12 394/week @ 2024-06-19 388/week @ 2024-06-26

每月下载量 2,574
liana 中使用

MIT/Apache

75KB
1K SLoC

BDK Coin Selection

bdk_coin_select 是一个零依赖的工具,可以帮助您选择输入以制作比特币 (代码:BTC) 交易。

⚠ 此工作仅供那些预期(可能灾难性的)错误并有时间调查它们并向此存储库做出贡献的人使用。

概述

use std::str::FromStr;
use bdk_coin_select::{ CoinSelector, Candidate, TR_KEYSPEND_TXIN_WEIGHT, Drain, FeeRate, Target, ChangePolicy, TargetOutputs, TargetFee, DrainWeights};
use bitcoin::{ Address, Network, Transaction, TxIn, TxOut };

let recipient_addr = 
    Address::from_str("tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46").unwrap();

let outputs = vec![TxOut {
    value: 3_500_000,
    script_pubkey: recipient_addr.payload.script_pubkey(),
}];

let target = Target {
    outputs: TargetOutputs::fund_outputs(outputs.iter().map(|output| (output.weight() as u32, output.value))),
    fee: TargetFee::from_feerate(FeeRate::from_sat_per_vb(42.0))
};

let candidates = vec![
    Candidate {
        // How many inputs does this candidate represents. Needed so we can 
        // figure out the weight of the varint that encodes the number of inputs
        input_count: 1,
        // the value of the input
        value: 1_000_000,
        // the total weight of the input(s) including their witness/scriptSig
        // you may need to use miniscript to figure out the correct value here.
        weight: TR_KEYSPEND_TXIN_WEIGHT,
        // wether it's a segwit input. Needed so we know whether to include the
        // segwit header in total weight calculations.
        is_segwit: true
    },
    Candidate {
        // A candidate can represent multiple inputs in the case where you 
        // always want some inputs to be spent together.
        input_count: 2,
        weight: 2*TR_KEYSPEND_TXIN_WEIGHT,
        value: 3_000_000,
        is_segwit: true
    }
];

// You can now select coins!
let mut coin_selector = CoinSelector::new(&candidates);
coin_selector.select(0);

assert!(!coin_selector.is_target_met(target), "we didn't select enough");
println!("we didn't select enough yet we're missing: {}", coin_selector.missing(target));
coin_selector.select(1);
assert!(coin_selector.is_target_met(target), "we should have enough now");

// Now we need to know if we need a change output to drain the excess if we overshot too much
//
// We don't need to know exactly which change output we're going to use yet but we assume it's a taproot output
// that we'll use a keyspend to spend from.
let drain_weights = DrainWeights::TR_KEYSPEND; 
// Our policy is to only add a change output if the value is over 1_000 sats
let change_policy = ChangePolicy::min_value(drain_weights, 1_000);
let change = coin_selector.drain(target, change_policy);
if change.is_some() {
    println!("We need to add our change output to the transaction with {} value", change.value);
} else {
    println!("Yay we don't need to add a change output");
}

使用分支和界限进行自动选择

您可以使用如 CoinSelector::select 的方法手动选择代币,或者使用如 CoinSelector::select_until_target_met 的方法进行基本的自动选择。您可能想使用 CoinSelector::run_bnb 来智能地完成此操作。

内置指标提供在 metrics 子模块中。目前,只有 LowestFee 指标被认为是稳定的。注意,您 可以 尝试自己编写指标,通过实现 BnbMetric,但我们不建议这样做。

use std::str::FromStr;
use bdk_coin_select::{ Candidate, CoinSelector, FeeRate, Target, TargetFee, TargetOutputs, ChangePolicy, TR_KEYSPEND_TXIN_WEIGHT, TR_DUST_RELAY_MIN_VALUE};
use bdk_coin_select::metrics::LowestFee;
use bitcoin::{ Address, Network, Transaction, TxIn, TxOut };

let recipient_addr =
    Address::from_str("tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46").unwrap();

let outputs = vec![TxOut {
    value: 210_000,
    script_pubkey: recipient_addr.payload.script_pubkey(),
}];

let candidates = [
    Candidate {
        input_count: 1,
        value: 400_000,
        weight: TR_KEYSPEND_TXIN_WEIGHT,
        is_segwit: true
    },
    Candidate {
        input_count: 1,
        value: 200_000,
        weight: TR_KEYSPEND_TXIN_WEIGHT,
        is_segwit: true
    },
    Candidate {
        input_count: 1,
        value: 11_000,
        weight: TR_KEYSPEND_TXIN_WEIGHT,
        is_segwit: true
    }
];
let drain_weights = bdk_coin_select::DrainWeights::default();
// You could determine this by looking at the user's transaction history and taking an average of the feerate.
let long_term_feerate = FeeRate::from_sat_per_vb(10.0);

let mut coin_selector = CoinSelector::new(&candidates);

let target = Target {
    fee: TargetFee::from_feerate(FeeRate::from_sat_per_vb(15.0)),
    outputs: TargetOutputs::fund_outputs(outputs.iter().map(|output| (output.weight() as u32, output.value))),
};

// The change output must be at least this size to be relayed.
// To choose it you need to know the kind of script pubkey on your change txout.
// Here we assume it's a taproot output
let dust_limit = TR_DUST_RELAY_MIN_VALUE;

// We use a change policy that introduces a change output if doing so reduces
// the "waste" (i.e. adding change doesn't increase the fees we'd pay if we factor in the cost to spend the output later on).
let change_policy = ChangePolicy::min_value_and_waste(
    drain_weights,
    dust_limit,
    target.fee.rate,
    long_term_feerate,
);

// The LowestFee metric tries make selections that minimize your total fees paid over time.
let metric = LowestFee {
    target,
    long_term_feerate, // used to calculate the cost of spending th change output if the future
    change_policy
};

// We run the branch and bound algorithm with a max round limit of 100,000.
match coin_selector.run_bnb(metric, 100_000) {
    Err(err) => {
        println!("failed to find a solution: {}", err);
        // fall back to naive selection
        coin_selector.select_until_target_met(target).expect("a selection was impossible!");
    }
    Ok(score) => {
        println!("we found a solution with score {}", score);

        let selection = coin_selector
            .apply_selection(&candidates)
            .collect::<Vec<_>>();
        let change = coin_selector.drain(target, change_policy);

        println!("we selected {} inputs", selection.len());
        println!("We are including a change output of {} value (0 means not change)", change.value);
    }
};

最低支持的Rust版本(MSRV)

此库可以在rust v1.54及以上版本编译

无运行时依赖