/// Adds a recipient to the transaction.
// Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704.
// Address and amount parsing is done at run time in handler function.
+ #[arg(env = "ADDRESS:SAT", long = "to", required = true, value_parser = parse_recipient)]
+ recipients: Vec<(ScriptBuf, u64)>,
+ /// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0.
+ #[arg(long = "send_all", short = 'a')]
+ send_all: bool,
+ /// Enables Replace-By-Fee (BIP125).
+ #[arg(long = "enable_rbf", short = 'r', default_value_t = true)]
+ enable_rbf: bool,
+ /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output.
+ #[arg(long = "offline_signer")]
+ offline_signer: bool,
+ /// Selects which utxos *must* be spent.
+ #[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
+ utxos: Option<Vec<OutPoint>>,
+ /// Marks a utxo as unspendable.
+ #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
+ unspendable: Option<Vec<OutPoint>>,
+ /// Fee rate to use in sat/vbyte.
+ #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")]
+ fee_rate: Option<f32>,
+ /// Selects which policy should be used to satisfy the external descriptor.
+ #[arg(env = "EXT_POLICY", long = "external_policy")]
+ external_policy: Option<String>,
+ /// Selects which policy should be used to satisfy the internal descriptor.
+ #[arg(env = "INT_POLICY", long = "internal_policy")]
+ internal_policy: Option<String>,
+ /// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes)
+ #[arg(
+ env = "ADD_STRING",
+ long = "add_string",
+ short = 's',
+ conflicts_with = "add_data"
+ )]
+ add_string: Option<String>,
+ /// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes)
+ #[arg(
+ env = "ADD_DATA",
+ long = "add_data",
+ short = 'o',
+ conflicts_with = "add_string"
+ )]
+ add_data: Option<String>, //base 64 econding
+ },
+ #[cfg(feature = "dns_payment")]
+ /// Creates a new unsigned transaction from DNS payment instructions.
+ CreateDnsTx {
+ /// Adds a recipient to the transaction.
#[arg(env = "ADDRESS:SAT", long = "to", value_parser = parse_recipient)]
recipients: Vec<(ScriptBuf, u64)>,
- #[cfg(feature = "dns_payment")]
- /// Adds DNS recipients to the transaction
#[arg(long = "to_dns", value_parser = parse_dns_recipient)]
dns_recipients: Vec<(String, u64)>,
- #[cfg(feature = "dns_payment")]
- /// Custom resolver DNS IP to be used for resolution.
#[arg(long = "dns_resolver", default_value = "8.8.8.8")]
dns_resolver: String,
/// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0.
}
}
CreateTx {
- #[cfg(feature = "dns_payment")]
- mut recipients,
- #[cfg(not(feature = "dns_payment"))]
recipients,
- #[cfg(feature = "dns_payment")]
+ send_all,
+ enable_rbf,
+ offline_signer,
+ utxos,
+ unspendable,
+ fee_rate,
+ external_policy,
+ internal_policy,
+ add_data,
+ add_string,
+ } => {
+ let mut tx_builder = wallet.build_tx();
+
+ if send_all {
+ if recipients.len() == 1 {
+ tx_builder.drain_wallet().drain_to(recipients[0].0.clone());
+ } else {
+ return Err(Error::Generic(
+ "Wallet can only be drained to a single output".to_string(),
+ ));
+ }
+ } else {
+ let recipients: Vec<_> = recipients
+ .into_iter()
+ .map(|(script, amount)| (script, Amount::from_sat(amount)))
+ .collect();
+ tx_builder.set_recipients(recipients);
+ }
+
+ if !enable_rbf {
+ tx_builder.set_exact_sequence(Sequence::MAX);
+ }
+
+ if offline_signer {
+ tx_builder.include_output_redeem_witness_script();
+ }
+
+ if let Some(fee_rate) = fee_rate
+ && let Some(fee_rate) = FeeRate::from_sat_per_vb(fee_rate as u64)
+ {
+ tx_builder.fee_rate(fee_rate);
+ }
+
+ if let Some(utxos) = utxos {
+ tx_builder
+ .add_utxos(&utxos[..])
+ .map_err(|_| bdk_wallet::error::CreateTxError::UnknownUtxo)?;
+ }
+
+ if let Some(unspendable) = unspendable {
+ tx_builder.unspendable(unspendable);
+ }
+
+ if let Some(base64_data) = add_data {
+ let op_return_data = BASE64_STANDARD
+ .decode(base64_data)
+ .map_err(|e| Error::Generic(e.to_string()))?;
+ tx_builder.add_data(
+ &PushBytesBuf::try_from(op_return_data)
+ .map_err(|e| Error::Generic(e.to_string()))?,
+ );
+ } else if let Some(string_data) = add_string {
+ let data = PushBytesBuf::try_from(string_data.as_bytes().to_vec())
+ .map_err(|e| Error::Generic(e.to_string()))?;
+ tx_builder.add_data(&data);
+ }
+
+ let policies = vec![
+ external_policy.map(|p| (p, KeychainKind::External)),
+ internal_policy.map(|p| (p, KeychainKind::Internal)),
+ ];
+
+ for (policy, keychain) in policies.into_iter().flatten() {
+ let policy = serde_json::from_str::<BTreeMap<String, Vec<usize>>>(&policy)?;
+ tx_builder.policy_path(policy, keychain);
+ }
+
+ let psbt = tx_builder.finish()?;
+
+ let psbt_base64 = BASE64_STANDARD.encode(psbt.serialize());
+
+ if wallet_opts.verbose {
+ Ok(serde_json::to_string_pretty(
+ &json!({"psbt": psbt_base64, "details": psbt}),
+ )?)
+ } else {
+ Ok(serde_json::to_string_pretty(
+ &json!({"psbt": psbt_base64 }),
+ )?)
+ }
+ }
+ #[cfg(feature = "dns_payment")]
+ CreateDnsTx {
+ recipients,
dns_recipients,
- #[cfg(feature = "dns_payment")]
dns_resolver,
send_all,
enable_rbf,
add_string,
} => {
let mut tx_builder = wallet.build_tx();
+ let mut recipients = recipients;
- #[cfg(feature = "dns_payment")]
for recipient in dns_recipients {
log::info!("Resolving DNS instructions for recipient {}", recipient.0);
let amount = Amount::from_sat(recipient.1);