Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TxBuilder APIs #611

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ interface CreateTxError {
UnknownUtxo(string outpoint);
MissingNonWitnessUtxo(string outpoint);
MiniscriptPsbt(string error_message);
PushBytesError();
LockTimeConversionError();
};

[Error]
Expand Down Expand Up @@ -674,50 +676,195 @@ dictionary Condition {
LockTime? timelock;
};

/// A `TxBuilder` is created by calling `build_tx` on a wallet. After assigning it, you set options on it until finally
/// calling `finish` to consume the builder and generate the transaction.
interface TxBuilder {
constructor();

/// Fill-in the `PSBT_GLOBAL_XPUB` field with the extended keys contained in both the external and internal
/// descriptors.
///
/// This is useful for offline signers that take part to a multisig. Some hardware wallets like BitBox and ColdCard
/// are known to require this.
TxBuilder add_global_xpubs();

/// Add a recipient to the internal list of recipients.
TxBuilder add_recipient([ByRef] Script script, Amount amount);

/// Replace the recipients already added with a new list of recipients.
TxBuilder set_recipients(sequence<ScriptAmount> recipients);

/// Add a utxo to the internal list of unspendable utxos.
///
/// It’s important to note that the "must-be-spent" utxos added with `TxBuilder::add_utxo` have priority over this.
TxBuilder add_unspendable(OutPoint unspendable);

/// Replace the internal list of unspendable utxos with a new list.
///
/// It’s important to note that the "must-be-spent" utxos added with `TxBuilder::add_utxo` have priority over these.
TxBuilder unspendable(sequence<OutPoint> unspendable);

/// Add a utxo to the internal list of utxos that must be spent.
///
/// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in the "utxos" and the
/// "unspendable" list, it will be spent.
TxBuilder add_utxo(OutPoint outpoint);

/// The TxBuilder::policy_path is a complex API. See the Rust docs for complete information: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.TxBuilder.html#method.policy_path
TxBuilder policy_path(record<string, sequence<u64>> policy_path, KeychainKind keychain);

/// Set a specific `ChangeSpendPolicy`. See `TxBuilder::do_not_spend_change` and `TxBuilder::only_spend_change` for
/// some shortcuts. This method assumes the presence of an internal keychain, otherwise it has no effect.
TxBuilder change_policy(ChangeSpendPolicy change_policy);

/// Do not spend change outputs.
///
/// This effectively adds all the change outputs to the "unspendable" list. See `TxBuilder::unspendable`. This method
/// assumes the presence of an internal keychain, otherwise it has no effect.
TxBuilder do_not_spend_change();

/// Only spend change outputs.
///
/// This effectively adds all the non-change outputs to the "unspendable" list. See `TxBuilder::unspendable`. This
/// method assumes the presence of an internal keychain, otherwise it has no effect.
TxBuilder only_spend_change();

/// Only spend utxos added by `TxBuilder::add_utxo`.
///
/// The wallet will not add additional utxos to the transaction even if they are needed to make the transaction valid.
TxBuilder manually_selected_only();

/// Set a custom fee rate.
///
/// This method sets the mining fee paid by the transaction as a rate on its size. This means that the total fee paid
/// is equal to fee_rate times the size of the transaction. Default is 1 sat/vB in accordance with Bitcoin Core’s
/// default relay policy.
///
/// Note that this is really a minimum feerate – it’s possible to overshoot it slightly since adding a change output
/// to drain the remaining excess might not be viable.
TxBuilder fee_rate([ByRef] FeeRate fee_rate);

/// Set an absolute fee The `fee_absolute` method refers to the absolute transaction fee in `Amount`. If anyone sets
/// both the `fee_absolute` method and the `fee_rate` method, the `FeePolicy` enum will be set by whichever method was
/// called last, as the `FeeRate` and `FeeAmount` are mutually exclusive.
///
/// Note that this is really a minimum absolute fee – it’s possible to overshoot it slightly since adding a change output to drain the remaining excess might not be viable.
TxBuilder fee_absolute(Amount fee);

/// Spend all the available inputs. This respects filters like `TxBuilder::unspendable` and the change policy.
TxBuilder drain_wallet();

/// Sets the address to drain excess coins to.
///
/// Usually, when there are excess coins they are sent to a change address generated by the wallet. This option
/// replaces the usual change address with an arbitrary script_pubkey of your choosing. Just as with a change output,
/// if the drain output is not needed (the excess coins are too small) it will not be included in the resulting
/// transaction. The only difference is that it is valid to use `drain_to` without setting any ordinary recipients
/// with `add_recipient` (but it is perfectly fine to add recipients as well).
///
/// If you choose not to set any recipients, you should provide the utxos that the transaction should spend via
/// `add_utxos`. `drain_to` is very useful for draining all the coins in a wallet with `drain_wallet` to a single
/// address.
TxBuilder drain_to([ByRef] Script script);

/// Set an exact `nSequence` value.
///
/// This can cause conflicts if the wallet’s descriptors contain an "older" (`OP_CSV`) operator and the given
/// `nsequence` is lower than the CSV value.
TxBuilder set_exact_sequence(u32 nsequence);

/// Add data as an output using `OP_RETURN`.
TxBuilder add_data(sequence<u8> data);

/// Set the current blockchain height.
///
/// This will be used to:
///
/// 1. Set the `nLockTime` for preventing fee sniping. Note: This will be ignored if you manually specify a
/// `nlocktime` using `TxBuilder::nlocktime`.
///
/// 2. Decide whether coinbase outputs are mature or not. If the coinbase outputs are not mature at `current_height`,
/// we ignore them in the coin selection. If you want to create a transaction that spends immature coinbase inputs,
/// manually add them using `TxBuilder::add_utxos`.
/// In both cases, if you don’t provide a current height, we use the last sync height.
TxBuilder current_height(u32 height);

/// Use a specific nLockTime while creating the transaction.
///
/// This can cause conflicts if the wallet’s descriptors contain an "after" (`OP_CLTV`) operator.
TxBuilder nlocktime(LockTime locktime);

/// Set whether or not the dust limit is checked.
///
/// Note: by avoiding a dust limit check you may end up with a transaction that is non-standard.
TxBuilder allow_dust(boolean allow_dust);

/// Build a transaction with a specific version.
///
/// The version should always be greater than 0 and greater than 1 if the wallet’s descriptors contain an "older"
/// (`OP_CSV`) operator.
TxBuilder version(i32 version);

/// Finish building the transaction.
///
/// Uses the thread-local random number generator (rng).
///
/// Returns a new `Psbt` per BIP174.
///
/// WARNING: To avoid change address reuse you must persist the changes resulting from one or more calls to this
/// method before closing the wallet. See `Wallet::reveal_next_address`.
[Throws=CreateTxError]
Psbt finish([ByRef] Wallet wallet);
};

/// A `BumpFeeTxBuilder` is created by calling `build_fee_bump` on a wallet. After assigning it, you set options on it
/// until finally calling `finish` to consume the builder and generate the transaction.
interface BumpFeeTxBuilder {
constructor(string txid, FeeRate fee_rate);

/// Set an exact `nSequence` value.
///
/// This can cause conflicts if the wallet’s descriptors contain an "older" (`OP_CSV`) operator and the given
/// `nsequence` is lower than the CSV value.
BumpFeeTxBuilder set_exact_sequence(u32 nsequence);

/// Set the current blockchain height.
///
/// This will be used to:
///
/// 1. Set the `nLockTime` for preventing fee sniping. Note: This will be ignored if you manually specify a
/// `nlocktime` using `TxBuilder::nlocktime`.
///
/// 2. Decide whether coinbase outputs are mature or not. If the coinbase outputs are not mature at `current_height`,
/// we ignore them in the coin selection. If you want to create a transaction that spends immature coinbase inputs,
/// manually add them using `TxBuilder::add_utxos`.
/// In both cases, if you don’t provide a current height, we use the last sync height.
BumpFeeTxBuilder current_height(u32 height);

/// Use a specific nLockTime while creating the transaction.
///
/// This can cause conflicts if the wallet’s descriptors contain an "after" (`OP_CLTV`) operator.
BumpFeeTxBuilder nlocktime(LockTime locktime);

/// Set whether or not the dust limit is checked.
///
/// Note: by avoiding a dust limit check you may end up with a transaction that is non-standard.
BumpFeeTxBuilder allow_dust(boolean allow_dust);

/// Build a transaction with a specific version.
///
/// The version should always be greater than 0 and greater than 1 if the wallet’s descriptors contain an "older"
/// (`OP_CSV`) operator.
BumpFeeTxBuilder version(i32 version);

/// Finish building the transaction.
///
/// Uses the thread-local random number generator (rng).
///
/// Returns a new `Psbt` per BIP174.
///
/// WARNING: To avoid change address reuse you must persist the changes resulting from one or more calls to this
/// method before closing the wallet. See `Wallet::reveal_next_address`.
[Throws=CreateTxError]
Psbt finish([ByRef] Wallet wallet);
};
Expand Down
13 changes: 13 additions & 0 deletions bdk-ffi/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bitcoin_ffi::OutPoint;

use bdk_core::bitcoin::script::PushBytesError;
use bdk_electrum::electrum_client::Error as BdkElectrumError;
use bdk_esplora::esplora_client::{Error as BdkEsploraError, Error};
use bdk_wallet::bitcoin::address::ParseError as BdkParseError;
Expand Down Expand Up @@ -195,6 +196,12 @@ pub enum CreateTxError {

#[error("miniscript psbt error: {error_message}")]
MiniscriptPsbt { error_message: String },

#[error("attempt to prepare too many bytes to be pushed into script")]
PushBytesError,

#[error("invalid lock time value")]
LockTimeConversionError,
}

#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -951,6 +958,12 @@ impl From<BdkCreateTxError> for CreateTxError {
}
}

impl From<PushBytesError> for CreateTxError {
fn from(_: PushBytesError) -> Self {
CreateTxError::PushBytesError
}
}

impl From<BdkCreateWithPersistError<chain::rusqlite::Error>> for CreateWithPersistError {
fn from(error: BdkCreateWithPersistError<chain::rusqlite::Error>) -> Self {
match error {
Expand Down
Loading
Loading