From: Sonkeng Maldini Date: Mon, 6 Jul 2026 20:06:10 +0000 (+0100) Subject: fix: separate CreateTx from CreateDnsTx X-Git-Url: http://internal-gitweb-vhost/blockdata/script/encode/-script/display/struct.CommandString.html?a=commitdiff_plain;h=943b4b648f89c1958cbf5bcd085b28e80e1077a1;p=bdk-cli fix: separate CreateTx from CreateDnsTx --- diff --git a/src/commands.rs b/src/commands.rs index 8af3b31..849bb38 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -380,14 +380,57 @@ pub enum OfflineWalletSubCommand { /// 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>, + /// Marks a utxo as unspendable. + #[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)] + unspendable: Option>, + /// Fee rate to use in sat/vbyte. + #[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")] + fee_rate: Option, + /// Selects which policy should be used to satisfy the external descriptor. + #[arg(env = "EXT_POLICY", long = "external_policy")] + external_policy: Option, + /// Selects which policy should be used to satisfy the internal descriptor. + #[arg(env = "INT_POLICY", long = "internal_policy")] + internal_policy: Option, + /// 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, + /// 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, //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. diff --git a/src/handlers.rs b/src/handlers.rs index 8d6a70c..dc36d54 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -529,13 +529,102 @@ pub async fn handle_offline_wallet_subcommand( } } 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::>>(&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, @@ -549,8 +638,8 @@ pub async fn handle_offline_wallet_subcommand( 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);