Skip to content

Commit 0eda8cd

Browse files
committed
feat: add option --to_dns to allow specifying dns recipients when creating a transaction
1 parent a0f3e9d commit 0eda8cd

4 files changed

Lines changed: 60 additions & 5 deletions

File tree

src/commands.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,9 @@ pub enum OfflineWalletSubCommand {
291291
// Address and amount parsing is done at run time in handler function.
292292
#[arg(env = "ADDRESS:SAT", long = "to", required = true, value_parser = parse_recipient)]
293293
recipients: Vec<(ScriptBuf, u64)>,
294+
/// Adds DNS recipients to the transaction
295+
#[arg(long = "to_dns")]
296+
dns_recipients: Option<String>,
294297
/// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0.
295298
#[arg(long = "send_all", short = 'a')]
296299
send_all: bool,

src/dns_payment_instructions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn process_fixed_instructions(
4747
};
4848

4949
// We need this conversion since Amount from instructions is different from Amount from bitcoin
50-
let onchain_amount = Amount::from_sat(onchain_amount.sats_rounding_up());
50+
let onchain_amount = Amount::from_sat(onchain_amount.milli_sats());
5151

5252
Ok(Payment {
5353
address: addr.clone(),

src/handlers.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::utils::*;
1919
#[cfg(feature = "redb")]
2020
use bdk_redb::Store as RedbStore;
2121
use bdk_wallet::bip39::{Language, Mnemonic};
22+
use bdk_wallet::bitcoin::ScriptBuf;
2223
use bdk_wallet::bitcoin::base64::Engine;
2324
use bdk_wallet::bitcoin::base64::prelude::BASE64_STANDARD;
2425
use bdk_wallet::bitcoin::{
@@ -330,7 +331,7 @@ pub async fn handle_offline_wallet_subcommand(
330331
}
331332

332333
ResolveDnsRecipient { hrn, amount } => {
333-
let resolved = resolve_dns_recipient(&hrn, Amount::from_sat(amount), Network::Bitcoin)
334+
let resolved = resolve_dns_recipient(&hrn, Amount::from_sat(amount), wallet.network())
334335
.await
335336
.map_err(|e| Error::Generic(format!("{:?}", e)))?;
336337

@@ -344,6 +345,7 @@ pub async fn handle_offline_wallet_subcommand(
344345

345346
CreateTx {
346347
recipients,
348+
dns_recipients,
347349
send_all,
348350
enable_rbf,
349351
offline_signer,
@@ -360,10 +362,34 @@ pub async fn handle_offline_wallet_subcommand(
360362
if send_all {
361363
tx_builder.drain_wallet().drain_to(recipients[0].0.clone());
362364
} else {
363-
let recipients = recipients
365+
let mut recipients: Vec<(ScriptBuf, Amount)> = recipients
364366
.into_iter()
365367
.map(|(script, amount)| (script, Amount::from_sat(amount)))
366368
.collect();
369+
370+
if let Some(recip) = dns_recipients {
371+
let parsed_recip = parse_dns_recipients(&recip)
372+
.await
373+
.map_err(|pe| Error::Generic(format!("Resolution failed: {pe}")))?;
374+
375+
// Validates if the amount the user wants to send is in the range of what the payment instructions returned
376+
parsed_recip.iter().try_for_each(|(_, r)| {
377+
let amount = r.amount.to_sat();
378+
if r.min_amount.map_or(false, |min| amount < min.to_sat()) {
379+
return Err(Error::Generic("Amount lesser than min".to_string()));
380+
}
381+
if r.max_amount.map_or(false, |max| amount > max.to_sat()) {
382+
return Err(Error::Generic("Amount greater than max".to_string()));
383+
}
384+
Ok(())
385+
})?;
386+
387+
let mut vec_recip = parsed_recip
388+
.iter()
389+
.map(|recip| (recip.1.address.script_pubkey(), recip.1.amount))
390+
.collect::<Vec<_>>();
391+
recipients.append(&mut vec_recip);
392+
}
367393
tx_builder.set_recipients(recipients);
368394
}
369395

src/utils.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
//! Utility Tools
1010
//!
1111
//! This module includes all the utility tools used by the App.
12-
use crate::error::BDKCliError as Error;
12+
use crate::{
13+
dns_payment_instructions::{Payment, resolve_dns_recipient},
14+
error::BDKCliError as Error,
15+
};
1316
use std::{
1417
fmt::Display,
1518
path::{Path, PathBuf},
@@ -25,7 +28,10 @@ use bdk_kyoto::{
2528
};
2629
use bdk_wallet::{
2730
KeychainKind,
28-
bitcoin::bip32::{DerivationPath, Xpub},
31+
bitcoin::{
32+
Amount,
33+
bip32::{DerivationPath, Xpub},
34+
},
2935
keys::DescriptorPublicKey,
3036
miniscript::{
3137
Descriptor, Miniscript, Terminal,
@@ -69,6 +75,26 @@ pub(crate) fn parse_recipient(s: &str) -> Result<(ScriptBuf, u64), String> {
6975
Ok((addr.script_pubkey(), val))
7076
}
7177

78+
/// Parse dns recipients in the form "test@me.com:10000,test2@from.com:40000" from cli input
79+
pub(crate) async fn parse_dns_recipients(s: &str) -> Result<Vec<(Amount, Payment)>, String> {
80+
let parts: Vec<_> = s.split(',').collect();
81+
let mut res = vec![];
82+
83+
for addr_amount in parts {
84+
let split: Vec<_> = addr_amount.split(':').collect();
85+
if split.len() != 2 {
86+
return Err("Invalid format".to_string());
87+
}
88+
let sending_amount = Amount::from_sat(u64::from_str(split[1]).map_err(|e| e.to_string())?);
89+
90+
let resolved = resolve_dns_recipient(split[0], sending_amount, Network::Bitcoin)
91+
.await
92+
.map_err(|p| format!("Parse Error occured: {:?}", p).to_string())?;
93+
res.push((sending_amount, resolved));
94+
}
95+
Ok(res)
96+
}
97+
7298
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
7399
/// Parse the proxy (Socket:Port) argument from the cli input.
74100
pub(crate) fn parse_proxy_auth(s: &str) -> Result<(String, String), Error> {

0 commit comments

Comments
 (0)