def test_insufficient_funds_2(generate_utxo_pool): utxo_pool = generate_utxo_pool([]) selection = select_coins(CoinSelectionParams(utxo_pool, 1, 0, 0, 0, 0, 0)) assert selection.outcome == CoinSelection.Outcome.INSUFFICIENT_FUNDS assert len(selection.outputs) == 0 assert selection.effective_value == 0 assert selection.change_value == 0
def test_makes_slightly_larger_than_dust_change(generate_utxo_pool): short_term_fee_per_byte = 100 long_term_fee_per_byte = 100 change_output_size_in_bytes = 100 change_spend_size_in_bytes = 100 utxo_pool = generate_utxo_pool([1 * CENT], short_term_fee_per_byte, long_term_fee_per_byte) not_input_size_in_bytes = 100 fixed_fee = short_term_fee_per_byte * not_input_size_in_bytes cost_of_creating_change = short_term_fee_per_byte * change_output_size_in_bytes cost_of_spending_change = long_term_fee_per_byte * change_spend_size_in_bytes cost_of_change = cost_of_creating_change + cost_of_spending_change total_effective_value = sum(output_group.effective_value for output_group in utxo_pool) params = CoinSelectionParams( utxo_pool=utxo_pool, target_value=total_effective_value - fixed_fee - cost_of_change - 1, short_term_fee_per_byte=short_term_fee_per_byte, long_term_fee_per_byte=long_term_fee_per_byte, change_spend_size_in_bytes=100, change_output_size_in_bytes=100, not_input_size_in_bytes=not_input_size_in_bytes) selection = select_coins(params) assert selection.outcome == CoinSelection.Outcome.SUCCESS assert selection.change_value == params.cost_of_change + params.fixed_fee + 1
def test_invalid_spend(generate_utxo_pool): utxo_pool = generate_utxo_pool([ 1 * CENT, 2 * CENT, 3 * CENT, 4 * CENT, ]) selection = select_coins(CoinSelectionParams(utxo_pool, 0, 0, 0, 0, 0, 0)) assert selection.outcome == CoinSelection.Outcome.INVALID_SPEND assert len(selection.outputs) == 0 assert selection.effective_value == 0 assert selection.change_value == 0
def test_insufficient_funds_after_fees(generate_utxo_pool): utxo_pool = generate_utxo_pool([10 * CENT]) selection = select_coins( CoinSelectionParams( utxo_pool=utxo_pool, target_value=10 * CENT, short_term_fee_per_byte=100, long_term_fee_per_byte=100, change_spend_size_in_bytes=100, change_output_size_in_bytes=100, not_input_size_in_bytes=100, )) assert selection.outcome == CoinSelection.Outcome.INSUFFICIENT_FUNDS_AFTER_FEES assert len(selection.outputs) == 0 assert selection.effective_value == 0 assert selection.change_value == 0
def test_success(generate_utxo_pool): utxo_pool = generate_utxo_pool([ 1 * CENT, 2 * CENT, 3 * CENT, 4 * CENT, ]) selection = select_coins( CoinSelectionParams(utxo_pool=utxo_pool, target_value=5 * CENT, short_term_fee_per_byte=100, long_term_fee_per_byte=100, change_spend_size_in_bytes=100, change_output_size_in_bytes=100, not_input_size_in_bytes=100)) assert selection.outcome == CoinSelection.Outcome.SUCCESS assert len(selection.outputs) > 0 assert selection.effective_value >= 5 * CENT assert selection.change_value > 0
def test_does_not_make_dust_change(generate_utxo_pool): short_term_fee_per_byte = 100 long_term_fee_per_byte = 100 utxo_pool = generate_utxo_pool([1 * CENT], short_term_fee_per_byte, long_term_fee_per_byte) not_input_size_in_bytes = 100 fixed_fee = short_term_fee_per_byte * not_input_size_in_bytes total_effective_value = sum(output_group.effective_value for output_group in utxo_pool) selection = select_coins( CoinSelectionParams(utxo_pool=utxo_pool, target_value=total_effective_value - fixed_fee - 1, short_term_fee_per_byte=short_term_fee_per_byte, long_term_fee_per_byte=long_term_fee_per_byte, change_spend_size_in_bytes=100, change_output_size_in_bytes=100, not_input_size_in_bytes=not_input_size_in_bytes)) assert selection.outcome == CoinSelection.Outcome.SUCCESS assert selection.change_value == 0
short_term_fee_per_byte = get_short_term_fee_rate(False) # 2.3.2 Get long term fee rate per byte # Sadly, I haven't found any public API's exposing this information # If you find one or make one yourself, let me know! Would be a cool project. # Otherwise, it's reasonable to come up with some heuristic method for your app # Lacking better options, it's okay to use short_term_fee_per_byte, # though this will cause inefficiencies when short_term_fees are anomalously low or high long_term_fee_per_byte = short_term_fee_per_byte # 3 At this point we are ready to select coins; suppose we want to spend 150000 satoshis target_value = 150000 coin_selection = select_coins( CoinSelectionParams(utxo_pool, target_value, short_term_fee_per_byte, long_term_fee_per_byte, change_output_size_in_bytes, change_spend_size_in_bytes, not_input_size_in_bytes)) # 4 Based on the CoinSelection.Outcome, you can raise different exceptions # or surface different error text to your UI or whatever if coin_selection.outcome != CoinSelection.Outcome.SUCCESS: raise Exception("Coin selection failed") # 5 Map the coin selection back to your app-specifc class, e.g. def map_coin_selection_to_utxos( coin_selection: CoinSelection, utxo_pool: List[YourUTXOClass]) -> List[YourUTXOClass]: selected_utxos = [] for selected_coin in coin_selection.outputs: selected_utxo = next(utxo for utxo in utxo_pool