Skip to content

Commit d279097

Browse files
committed
fix: separate CreateTx from CreateDnsTx
1 parent 5c496b1 commit d279097

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

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

0 commit comments

Comments
 (0)