Skip to content

Commit 943b4b6

Browse files
sdmg15tvpeter
authored andcommitted
fix: separate CreateTx from CreateDnsTx
1 parent 550918c commit 943b4b6

2 files changed

Lines changed: 142 additions & 10 deletions

File tree

src/commands.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,14 +380,57 @@ pub enum OfflineWalletSubCommand {
380380
/// Adds a recipient to the transaction.
381381
// Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704.
382382
// Address and amount parsing is done at run time in handler function.
383+
#[arg(env = "ADDRESS:SAT", long = "to", required = true, value_parser = parse_recipient)]
384+
recipients: Vec<(ScriptBuf, u64)>,
385+
/// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0.
386+
#[arg(long = "send_all", short = 'a')]
387+
send_all: bool,
388+
/// Enables Replace-By-Fee (BIP125).
389+
#[arg(long = "enable_rbf", short = 'r', default_value_t = true)]
390+
enable_rbf: bool,
391+
/// 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.
392+
#[arg(long = "offline_signer")]
393+
offline_signer: bool,
394+
/// Selects which utxos *must* be spent.
395+
#[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
396+
utxos: Option<Vec<OutPoint>>,
397+
/// Marks a utxo as unspendable.
398+
#[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
399+
unspendable: Option<Vec<OutPoint>>,
400+
/// Fee rate to use in sat/vbyte.
401+
#[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")]
402+
fee_rate: Option<f32>,
403+
/// Selects which policy should be used to satisfy the external descriptor.
404+
#[arg(env = "EXT_POLICY", long = "external_policy")]
405+
external_policy: Option<String>,
406+
/// Selects which policy should be used to satisfy the internal descriptor.
407+
#[arg(env = "INT_POLICY", long = "internal_policy")]
408+
internal_policy: Option<String>,
409+
/// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes)
410+
#[arg(
411+
env = "ADD_STRING",
412+
long = "add_string",
413+
short = 's',
414+
conflicts_with = "add_data"
415+
)]
416+
add_string: Option<String>,
417+
/// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes)
418+
#[arg(
419+
env = "ADD_DATA",
420+
long = "add_data",
421+
short = 'o',
422+
conflicts_with = "add_string"
423+
)]
424+
add_data: Option<String>, //base 64 econding
425+
},
426+
#[cfg(feature = "dns_payment")]
427+
/// Creates a new unsigned transaction from DNS payment instructions.
428+
CreateDnsTx {
429+
/// Adds a recipient to the transaction.
383430
#[arg(env = "ADDRESS:SAT", long = "to", value_parser = parse_recipient)]
384431
recipients: Vec<(ScriptBuf, u64)>,
385-
#[cfg(feature = "dns_payment")]
386-
/// Adds DNS recipients to the transaction
387432
#[arg(long = "to_dns", value_parser = parse_dns_recipient)]
388433
dns_recipients: Vec<(String, u64)>,
389-
#[cfg(feature = "dns_payment")]
390-
/// Custom resolver DNS IP to be used for resolution.
391434
#[arg(long = "dns_resolver", default_value = "8.8.8.8")]
392435
dns_resolver: String,
393436
/// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0.

src/handlers.rs

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -529,13 +529,102 @@ pub async fn handle_offline_wallet_subcommand(
529529
}
530530
}
531531
CreateTx {
532-
#[cfg(feature = "dns_payment")]
533-
mut recipients,
534-
#[cfg(not(feature = "dns_payment"))]
535532
recipients,
536-
#[cfg(feature = "dns_payment")]
533+
send_all,
534+
enable_rbf,
535+
offline_signer,
536+
utxos,
537+
unspendable,
538+
fee_rate,
539+
external_policy,
540+
internal_policy,
541+
add_data,
542+
add_string,
543+
} => {
544+
let mut tx_builder = wallet.build_tx();
545+
546+
if send_all {
547+
if recipients.len() == 1 {
548+
tx_builder.drain_wallet().drain_to(recipients[0].0.clone());
549+
} else {
550+
return Err(Error::Generic(
551+
"Wallet can only be drained to a single output".to_string(),
552+
));
553+
}
554+
} else {
555+
let recipients: Vec<_> = recipients
556+
.into_iter()
557+
.map(|(script, amount)| (script, Amount::from_sat(amount)))
558+
.collect();
559+
tx_builder.set_recipients(recipients);
560+
}
561+
562+
if !enable_rbf {
563+
tx_builder.set_exact_sequence(Sequence::MAX);
564+
}
565+
566+
if offline_signer {
567+
tx_builder.include_output_redeem_witness_script();
568+
}
569+
570+
if let Some(fee_rate) = fee_rate
571+
&& let Some(fee_rate) = FeeRate::from_sat_per_vb(fee_rate as u64)
572+
{
573+
tx_builder.fee_rate(fee_rate);
574+
}
575+
576+
if let Some(utxos) = utxos {
577+
tx_builder
578+
.add_utxos(&utxos[..])
579+
.map_err(|_| bdk_wallet::error::CreateTxError::UnknownUtxo)?;
580+
}
581+
582+
if let Some(unspendable) = unspendable {
583+
tx_builder.unspendable(unspendable);
584+
}
585+
586+
if let Some(base64_data) = add_data {
587+
let op_return_data = BASE64_STANDARD
588+
.decode(base64_data)
589+
.map_err(|e| Error::Generic(e.to_string()))?;
590+
tx_builder.add_data(
591+
&PushBytesBuf::try_from(op_return_data)
592+
.map_err(|e| Error::Generic(e.to_string()))?,
593+
);
594+
} else if let Some(string_data) = add_string {
595+
let data = PushBytesBuf::try_from(string_data.as_bytes().to_vec())
596+
.map_err(|e| Error::Generic(e.to_string()))?;
597+
tx_builder.add_data(&data);
598+
}
599+
600+
let policies = vec![
601+
external_policy.map(|p| (p, KeychainKind::External)),
602+
internal_policy.map(|p| (p, KeychainKind::Internal)),
603+
];
604+
605+
for (policy, keychain) in policies.into_iter().flatten() {
606+
let policy = serde_json::from_str::<BTreeMap<String, Vec<usize>>>(&policy)?;
607+
tx_builder.policy_path(policy, keychain);
608+
}
609+
610+
let psbt = tx_builder.finish()?;
611+
612+
let psbt_base64 = BASE64_STANDARD.encode(psbt.serialize());
613+
614+
if wallet_opts.verbose {
615+
Ok(serde_json::to_string_pretty(
616+
&json!({"psbt": psbt_base64, "details": psbt}),
617+
)?)
618+
} else {
619+
Ok(serde_json::to_string_pretty(
620+
&json!({"psbt": psbt_base64 }),
621+
)?)
622+
}
623+
}
624+
#[cfg(feature = "dns_payment")]
625+
CreateDnsTx {
626+
recipients,
537627
dns_recipients,
538-
#[cfg(feature = "dns_payment")]
539628
dns_resolver,
540629
send_all,
541630
enable_rbf,
@@ -549,8 +638,8 @@ pub async fn handle_offline_wallet_subcommand(
549638
add_string,
550639
} => {
551640
let mut tx_builder = wallet.build_tx();
641+
let mut recipients = recipients;
552642

553-
#[cfg(feature = "dns_payment")]
554643
for recipient in dns_recipients {
555644
log::info!("Resolving DNS instructions for recipient {}", recipient.0);
556645
let amount = Amount::from_sat(recipient.1);

0 commit comments

Comments
 (0)