]> Untitled Git - bdk-cli/commitdiff
fix: separate CreateTx from CreateDnsTx
authorSonkeng Maldini <sdmg15@pm.me>
Mon, 6 Jul 2026 20:06:10 +0000 (21:06 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Wed, 8 Jul 2026 08:17:40 +0000 (09:17 +0100)
src/commands.rs
src/handlers.rs

index 8af3b316fc692d0030d6acccf56fbe4009d0ee66..849bb38af3e48a23f3cb5ba9f4ffece12521c11c 100644 (file)
@@ -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<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.
index 8d6a70c2a3551568646365baa8c3fdbd7233239a..dc36d547418718665011c6a9d426db9057860c3e 100644 (file)
@@ -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::<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,
@@ -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);