mod dns_payment_instructions;
use crate::error::BDKCliError as Error;
use crate::handlers::dns::dns_payment_instructions::{
parse_dns_instructions, process_instructions, resolve_dns_recipient,
};
use crate::handlers::{AppContext, AsyncAppCommand, Init, OfflineOperations};
use crate::utils::types::{PsbtResult, StatusResult};
use crate::utils::{parse_dns_recipient, parse_outpoint, parse_recipient};
use bdk_wallet::KeychainKind;
use bdk_wallet::bitcoin::base64::Engine;
use bdk_wallet::bitcoin::base64::prelude::BASE64_STANDARD;
use bdk_wallet::bitcoin::script::PushBytesBuf;
use bdk_wallet::bitcoin::{Amount, FeeRate, OutPoint, ScriptBuf, Sequence};
use clap::Parser;
use std::collections::BTreeMap;
#[derive(Parser, Debug, Clone, PartialEq)]
pub struct ResolveDnsRecipientCommand {
pub hrn: String,
#[arg(long, default_value = "8.8.8.8")]
pub resolver: String,
}
impl AsyncAppCommand<AppContext<Init>> for ResolveDnsRecipientCommand {
type Output = StatusResult;
async fn execute(&self, ctx: &mut AppContext<Init>) -> Result<Self::Output, Error> {
let resolved = resolve_dns_recipient(&self.hrn, ctx.network, &self.resolver)
.await
.map_err(|e| Error::Generic(format!("{:?}", e)))?;
Ok(StatusResult {
message: resolved.display()?,
})
}
}
#[derive(Parser, Debug, Clone, PartialEq)]
pub struct CreateDnsTxCommand {
#[arg(env = "ADDRESS:SAT", long = "to", value_parser = parse_recipient)]
pub recipients: Vec<(ScriptBuf, u64)>,
#[arg(long = "to_dns", value_parser = parse_dns_recipient)]
pub dns_recipients: Vec<(String, u64)>,
#[arg(long = "dns_resolver", default_value = "8.8.8.8")]
pub dns_resolver: String,
#[arg(long = "send_all", short = 'a')]
pub send_all: bool,
#[arg(long = "enable_rbf", short = 'r', default_value_t = true)]
pub enable_rbf: bool,
#[arg(long = "offline_signer")]
pub offline_signer: bool,
#[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
pub utxos: Option<Vec<OutPoint>>,
#[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
pub unspendable: Option<Vec<OutPoint>>,
#[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")]
pub fee_rate: Option<f32>,
#[arg(env = "EXT_POLICY", long = "external_policy")]
pub external_policy: Option<String>,
#[arg(env = "INT_POLICY", long = "internal_policy")]
pub internal_policy: Option<String>,
#[arg(
env = "ADD_STRING",
long = "add_string",
short = 's',
conflicts_with = "add_data"
)]
pub add_string: Option<String>,
#[arg(
env = "ADD_DATA",
long = "add_data",
short = 'o',
conflicts_with = "add_string"
)]
pub add_data: Option<String>,
}
impl AsyncAppCommand<AppContext<OfflineOperations<'_>>> for CreateDnsTxCommand {
type Output = PsbtResult;
async fn execute(
&self,
ctx: &mut AppContext<OfflineOperations<'_>>,
) -> Result<Self::Output, Error> {
let network = ctx.network;
let mut recipients: Vec<(ScriptBuf, u64)> = self.recipients.clone();
for (hrn, amount_sat) in &self.dns_recipients {
log::info!("Resolving DNS instructions for recipient {hrn}");
let amount = Amount::from_sat(*amount_sat);
let (resolver, instructions) = parse_dns_instructions(hrn, network, &self.dns_resolver)
.await
.map_err(|e| Error::Generic(format!("Parsing error occured {e:#?}")))?;
let payment = process_instructions(amount, &instructions, resolver).await?;
recipients.push((payment.0.into(), payment.1.to_sat()));
}
if recipients.is_empty() {
return Err(Error::Generic(
"Either --to or --to_dns parameters must be specified".to_string(),
));
}
let mut tx_builder = ctx.state.wallet.build_tx();
if self.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 = recipients
.into_iter()
.map(|(script, amount)| (script, Amount::from_sat(amount)))
.collect();
tx_builder.set_recipients(recipients);
}
if !self.enable_rbf {
tx_builder.set_exact_sequence(Sequence::MAX);
}
if self.offline_signer {
tx_builder.add_global_xpubs();
}
if let Some(fee_rate) = self.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) = &self.utxos {
tx_builder
.add_utxos(&utxos[..])
.map_err(|_| bdk_wallet::error::CreateTxError::UnknownUtxo)?;
}
if let Some(unspendable) = &self.unspendable {
tx_builder.unspendable(unspendable.to_vec());
}
if let Some(base64_data) = &self.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) = &self.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![
self.external_policy
.as_ref()
.map(|p| (p, KeychainKind::External)),
self.internal_policy
.as_ref()
.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()?;
Ok(PsbtResult::new(&psbt, Some(false)))
}
}