diff --git a/src/commands/pczt/combine.rs b/src/commands/pczt/combine.rs index 6b1c1ee..7548b38 100644 --- a/src/commands/pczt/combine.rs +++ b/src/commands/pczt/combine.rs @@ -14,6 +14,10 @@ pub(crate) struct Command { /// A list of PCZT files to combine #[arg(short, long)] input: Vec, + + /// Path to a file to which to write the combined PCZT. If not provided, writes to stdout. + #[arg(short, long)] + output: Option, } impl Command { @@ -34,7 +38,16 @@ impl Command { .combine() .map_err(|e| anyhow!("Failed to combine PCZTs: {:?}", e))?; - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + File::create(output_path) + .await? + .write_all(&pczt.serialize()) + .await?; + } else { + let mut stdout = stdout(); + stdout.write_all(&pczt.serialize()).await?; + stdout.flush().await?; + } Ok(()) } diff --git a/src/commands/pczt/create.rs b/src/commands/pczt/create.rs index c51be89..579856d 100644 --- a/src/commands/pczt/create.rs +++ b/src/commands/pczt/create.rs @@ -1,10 +1,13 @@ #![allow(deprecated)] -use std::{num::NonZeroUsize, str::FromStr}; +use std::{num::NonZeroUsize, path::PathBuf, str::FromStr}; use anyhow::anyhow; use clap::Args; use rand::rngs::OsRng; -use tokio::io::{stdout, AsyncWriteExt}; +use tokio::{ + fs::File, + io::{stdout, AsyncWriteExt}, +}; use uuid::Uuid; use zcash_address::ZcashAddress; @@ -56,6 +59,10 @@ pub(crate) struct Command { #[arg(long)] #[arg(default_value_t = 10000000)] min_split_output_value: u64, + + /// Path to a file to which to write the PCZT. If not provided, writes to stdout. + #[arg(long)] + output: Option, } impl Command { @@ -115,7 +122,16 @@ impl Command { ) .map_err(error::Error::from)?; - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + File::create(output_path) + .await? + .write_all(&pczt.serialize()) + .await?; + } else { + let mut stdout = stdout(); + stdout.write_all(&pczt.serialize()).await?; + stdout.flush().await?; + } Ok(()) } diff --git a/src/commands/pczt/create_manual.rs b/src/commands/pczt/create_manual.rs index ce55e99..e8df70f 100644 --- a/src/commands/pczt/create_manual.rs +++ b/src/commands/pczt/create_manual.rs @@ -1,11 +1,14 @@ #![allow(deprecated)] -use std::str::FromStr; +use std::{path::PathBuf, str::FromStr}; use anyhow::anyhow; use clap::Args; use pczt::roles::{creator::Creator, io_finalizer::IoFinalizer, updater::Updater}; use rand::rngs::OsRng; -use tokio::io::{stdout, AsyncWriteExt}; +use tokio::{ + fs::File, + io::{stdout, AsyncWriteExt}, +}; use transparent::builder::TransparentInputInfo; use zcash_address::ZcashAddress; @@ -66,6 +69,10 @@ pub(crate) struct Command { #[command(flatten)] connection: ConnectionArgs, + + /// Path to a file to which to write the PCZT. If not provided, writes to stdout. + #[arg(long)] + output: Option, } impl Command { @@ -254,7 +261,16 @@ impl Command { )? .finish(); - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + File::create(output_path) + .await? + .write_all(&pczt.serialize()) + .await?; + } else { + let mut stdout = stdout(); + stdout.write_all(&pczt.serialize()).await?; + stdout.flush().await?; + } Ok(()) } diff --git a/src/commands/pczt/create_max.rs b/src/commands/pczt/create_max.rs index ea850d6..d7009ea 100644 --- a/src/commands/pczt/create_max.rs +++ b/src/commands/pczt/create_max.rs @@ -1,8 +1,11 @@ -use std::str::FromStr; +use std::{path::PathBuf, str::FromStr}; use clap::Args; use rand::rngs::OsRng; -use tokio::io::{stdout, AsyncWriteExt}; +use tokio::{ + fs::File, + io::{stdout, AsyncWriteExt}, +}; use uuid::Uuid; use zcash_address::ZcashAddress; @@ -44,6 +47,10 @@ pub(crate) struct Command { /// wallet or if the wallet is not yet synced. #[arg(long)] only_spendable: bool, + + /// Path to a file to which to write the PCZT. If not provided, writes to stdout. + #[arg(long)] + output: Option, } impl Command { @@ -91,7 +98,16 @@ impl Command { ) .map_err(error::Error::from)?; - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + File::create(output_path) + .await? + .write_all(&pczt.serialize()) + .await?; + } else { + let mut stdout = stdout(); + stdout.write_all(&pczt.serialize()).await?; + stdout.flush().await?; + } Ok(()) } diff --git a/src/commands/pczt/inspect.rs b/src/commands/pczt/inspect.rs index eb6db38..832042a 100644 --- a/src/commands/pczt/inspect.rs +++ b/src/commands/pczt/inspect.rs @@ -1,10 +1,13 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, path::PathBuf}; use anyhow::anyhow; use clap::Args; use pczt::{roles::verifier::Verifier, Pczt}; use secrecy::ExposeSecret; -use tokio::io::{stdin, AsyncReadExt}; +use tokio::{ + fs::File, + io::{stdin, AsyncReadExt}, +}; use ::transparent::sighash::SighashType; use transparent::address::TransparentAddress; @@ -27,6 +30,9 @@ pub(crate) struct Command { /// age identity file to decrypt the mnemonic phrase with (if a wallet is provided) #[arg(short, long)] identity: Option, + + /// Path to a file from which to read the PCZT. If not provided, reads from stdin. + input: Option, } impl Command { @@ -39,7 +45,11 @@ impl Command { }?; let mut buf = vec![]; - stdin().read_to_end(&mut buf).await?; + if let Some(input_path) = &self.input { + File::open(input_path).await?.read_to_end(&mut buf).await?; + } else { + stdin().read_to_end(&mut buf).await?; + } let pczt = Pczt::parse(&buf).map_err(|e| anyhow!("Failed to read PCZT: {:?}", e))?; diff --git a/src/commands/pczt/pay_manual.rs b/src/commands/pczt/pay_manual.rs index 10baca4..fb0507b 100644 --- a/src/commands/pczt/pay_manual.rs +++ b/src/commands/pczt/pay_manual.rs @@ -1,10 +1,13 @@ -use std::{collections::BTreeMap, convert::Infallible}; +use std::{collections::BTreeMap, convert::Infallible, path::PathBuf}; use anyhow::anyhow; use clap::Args; use pczt::roles::{creator::Creator, io_finalizer::IoFinalizer, updater::Updater}; use rand::rngs::OsRng; -use tokio::io::{stdout, AsyncWriteExt}; +use tokio::{ + fs::File, + io::{stdout, AsyncWriteExt}, +}; use transparent::{builder::TransparentInputInfo, bundle::TxOut}; use zcash_client_backend::{ @@ -65,6 +68,10 @@ pub(crate) struct Command { #[command(flatten)] connection: ConnectionArgs, + + /// Path to a file to which to write the PCZT. If not provided, writes to stdout. + #[arg(long)] + output: Option, } impl Command { @@ -351,7 +358,16 @@ impl Command { } let pczt = updater.finish(); - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + File::create(output_path) + .await? + .write_all(&pczt.serialize()) + .await?; + } else { + let mut stdout = stdout(); + stdout.write_all(&pczt.serialize()).await?; + stdout.flush().await?; + } Ok(()) } diff --git a/src/commands/pczt/prove.rs b/src/commands/pczt/prove.rs index 9ce9ecd..24ca213 100644 --- a/src/commands/pczt/prove.rs +++ b/src/commands/pczt/prove.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use anyhow::anyhow; use clap::Args; use pczt::{ @@ -6,7 +8,10 @@ use pczt::{ }; use sapling::ProofGenerationKey; use secrecy::{ExposeSecret, SecretVec}; -use tokio::io::{stdin, stdout, AsyncReadExt, AsyncWriteExt}; +use tokio::{ + fs::File, + io::{stdin, stdout, AsyncReadExt, AsyncWriteExt}, +}; use zcash_keys::keys::UnifiedSpendingKey; use zcash_proofs::prover::LocalTxProver; use zcash_protocol::consensus::{NetworkConstants, Parameters}; @@ -25,12 +30,23 @@ pub(crate) struct Command { /// age identity file to decrypt the mnemonic phrase with for deriving the Sapling proof generation key #[arg(short, long)] identity: Option, + + /// Path to a file from which to read the PCZT. If not provided, reads from stdin. + input: Option, + + /// Path to a file to which to write the PCZT. If not provided, writes to stdout. + #[arg(long)] + output: Option, } impl Command { pub(crate) async fn run(self, wallet_dir: Option) -> Result<(), anyhow::Error> { let mut buf = vec![]; - stdin().read_to_end(&mut buf).await?; + if let Some(input_path) = &self.input { + File::open(input_path).await?.read_to_end(&mut buf).await?; + } else { + stdin().read_to_end(&mut buf).await?; + } let pczt = Pczt::parse(&buf).map_err(|e| anyhow!("Failed to read PCZT: {:?}", e))?; @@ -179,7 +195,16 @@ impl Command { .map_err(|e| anyhow!("Failed to create Sapling proofs: {:?}", e))? .finish(); - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + File::create(output_path) + .await? + .write_all(&pczt.serialize()) + .await?; + } else { + let mut stdout = stdout(); + stdout.write_all(&pczt.serialize()).await?; + stdout.flush().await?; + } Ok(()) } diff --git a/src/commands/pczt/qr.rs b/src/commands/pczt/qr.rs index b3deb64..ac1c17a 100644 --- a/src/commands/pczt/qr.rs +++ b/src/commands/pczt/qr.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{path::PathBuf, time::Duration}; use anyhow::anyhow; use clap::Args; @@ -12,7 +12,10 @@ use nokhwa::{ }; use pczt::Pczt; use qrcode::{render::unicode, QrCode}; -use tokio::io::{stdin, stdout, AsyncReadExt, AsyncWriteExt, Stdout}; +use tokio::{ + fs::File, + io::{stdin, stdout, AsyncReadExt, AsyncWriteExt, Stdout}, +}; use crate::ShutdownListener; @@ -37,6 +40,9 @@ pub(crate) struct Send { #[cfg(feature = "tui")] #[arg(long)] pub(crate) tui: bool, + + /// Path to a file from which to read the PCZT. If not provided, reads from stdin. + input: Option, } impl Send { @@ -46,7 +52,11 @@ impl Send { #[cfg(feature = "tui")] tui: Tui, ) -> Result<(), anyhow::Error> { let mut buf = vec![]; - stdin().read_to_end(&mut buf).await?; + if let Some(input_path) = &self.input { + File::open(input_path).await?.read_to_end(&mut buf).await?; + } else { + stdin().read_to_end(&mut buf).await?; + } let pczt = Pczt::parse(&buf).map_err(|e| anyhow!("Failed to read PCZT: {:?}", e))?; @@ -129,6 +139,10 @@ pub(crate) struct Receive { #[arg(long)] #[arg(default_value_t = 500)] interval: u64, + + /// Path to a file to which to write the PCZT. If not provided, writes to stdout. + #[arg(long)] + output: Option, } impl Receive { @@ -219,7 +233,16 @@ impl Receive { ) .map_err(|e| anyhow!("Failed to read PCZT from QR codes: {:?}", e))?; - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + File::create(output_path) + .await? + .write_all(&pczt.serialize()) + .await?; + } else { + let mut stdout = stdout(); + stdout.write_all(&pczt.serialize()).await?; + stdout.flush().await?; + } Ok(()) } diff --git a/src/commands/pczt/redact.rs b/src/commands/pczt/redact.rs index 8e534b2..1bfb2bf 100644 --- a/src/commands/pczt/redact.rs +++ b/src/commands/pczt/redact.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use anyhow::anyhow; use clap::Args; use pczt::{ @@ -9,7 +11,10 @@ use pczt::{ }, Pczt, }; -use tokio::io::{stdin, stdout, AsyncReadExt, AsyncWriteExt}; +use tokio::{ + fs::File, + io::{stdin, stdout, AsyncReadExt, AsyncWriteExt}, +}; // Options accepted for the `pczt redact` command #[derive(Debug, Args)] @@ -17,12 +22,23 @@ pub(crate) struct Command { /// A list of PCZT keys to redact, in foo.bar.baz notation #[arg(short, long)] key: Vec, + + /// Path to a file from which to read the PCZT. If not provided, reads from stdin. + input: Option, + + /// Path to a file to which to write the PCZT. If not provided, writes to stdout. + #[arg(long)] + output: Option, } impl Command { pub(crate) async fn run(self) -> Result<(), anyhow::Error> { let mut buf = vec![]; - stdin().read_to_end(&mut buf).await?; + if let Some(input_path) = &self.input { + File::open(input_path).await?.read_to_end(&mut buf).await?; + } else { + stdin().read_to_end(&mut buf).await?; + } let pczt = Pczt::parse(&buf).map_err(|e| anyhow!("Failed to read PCZT: {:?}", e))?; @@ -34,7 +50,16 @@ impl Command { })? .finish(); - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + File::create(output_path) + .await? + .write_all(&pczt.serialize()) + .await?; + } else { + let mut stdout = stdout(); + stdout.write_all(&pczt.serialize()).await?; + stdout.flush().await?; + } Ok(()) } diff --git a/src/commands/pczt/send.rs b/src/commands/pczt/send.rs index 67d5284..6888698 100644 --- a/src/commands/pczt/send.rs +++ b/src/commands/pczt/send.rs @@ -1,8 +1,13 @@ +use std::path::PathBuf; + use anyhow::anyhow; use clap::Args; use pczt::Pczt; use rand::rngs::OsRng; -use tokio::io::{stdin, AsyncReadExt}; +use tokio::{ + fs::File, + io::{stdin, AsyncReadExt}, +}; use zcash_client_backend::{ data_api::{wallet::extract_and_store_transaction_from_pczt, WalletRead}, proto::service, @@ -17,6 +22,9 @@ use crate::{config::WalletConfig, data::get_db_paths, error, remote::ConnectionA pub(crate) struct Command { #[command(flatten)] connection: ConnectionArgs, + + /// Path to a file from which to read the PCZT. If not provided, reads from stdin. + input: Option, } impl Command { @@ -30,7 +38,11 @@ impl Command { let mut client = self.connection.connect(params, wallet_dir.as_ref()).await?; let mut buf = vec![]; - stdin().read_to_end(&mut buf).await?; + if let Some(input_path) = &self.input { + File::open(input_path).await?.read_to_end(&mut buf).await?; + } else { + stdin().read_to_end(&mut buf).await?; + } let pczt = Pczt::parse(&buf).map_err(|e| anyhow!("Failed to read PCZT: {:?}", e))?; diff --git a/src/commands/pczt/send_without_storing.rs b/src/commands/pczt/send_without_storing.rs index 41035ac..2ae36bc 100644 --- a/src/commands/pczt/send_without_storing.rs +++ b/src/commands/pczt/send_without_storing.rs @@ -1,10 +1,15 @@ +use std::path::PathBuf; + use anyhow::anyhow; use clap::Args; use pczt::{ roles::{spend_finalizer::SpendFinalizer, tx_extractor::TransactionExtractor}, Pczt, }; -use tokio::io::{stdin, AsyncReadExt}; +use tokio::{ + fs::File, + io::{stdin, AsyncReadExt}, +}; use zcash_client_backend::proto::service; use zcash_proofs::prover::LocalTxProver; @@ -15,6 +20,9 @@ use crate::{config::WalletConfig, error, remote::ConnectionArgs}; pub(crate) struct Command { #[command(flatten)] connection: ConnectionArgs, + + /// Path to a file from which to read the PCZT. If not provided, reads from stdin. + input: Option, } impl Command { @@ -25,7 +33,11 @@ impl Command { let mut client = self.connection.connect(params, wallet_dir.as_ref()).await?; let mut buf = vec![]; - stdin().read_to_end(&mut buf).await?; + if let Some(input_path) = &self.input { + File::open(input_path).await?.read_to_end(&mut buf).await?; + } else { + stdin().read_to_end(&mut buf).await?; + } let pczt = Pczt::parse(&buf).map_err(|e| anyhow!("Failed to read PCZT: {:?}", e))?; diff --git a/src/commands/pczt/shield.rs b/src/commands/pczt/shield.rs index d24510a..3ce841a 100644 --- a/src/commands/pczt/shield.rs +++ b/src/commands/pczt/shield.rs @@ -1,10 +1,14 @@ use std::collections::HashSet; use std::num::NonZeroUsize; +use std::path::PathBuf; use anyhow::anyhow; use clap::Args; use rand::rngs::OsRng; -use tokio::io::{stdout, AsyncWriteExt}; +use tokio::{ + fs::File, + io::{stdout, AsyncWriteExt}, +}; use transparent::address::TransparentAddress; use uuid::Uuid; use zcash_client_backend::{ @@ -43,6 +47,10 @@ pub(crate) struct Command { #[arg(long)] #[arg(default_value_t = 10000000)] min_split_output_value: u64, + + /// Path to a file to which to write the PCZT. If not provided, writes to stdout. + #[arg(long)] + output: Option, } impl Command { @@ -109,7 +117,16 @@ impl Command { ) .map_err(error::Error::Shield)?; - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + File::create(output_path) + .await? + .write_all(&pczt.serialize()) + .await?; + } else { + let mut stdout = stdout(); + stdout.write_all(&pczt.serialize()).await?; + stdout.flush().await?; + } Ok(()) } diff --git a/src/commands/pczt/sign.rs b/src/commands/pczt/sign.rs index 43a5125..fb8ce72 100644 --- a/src/commands/pczt/sign.rs +++ b/src/commands/pczt/sign.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, convert::Infallible}; +use std::{collections::BTreeMap, convert::Infallible, path::PathBuf}; use anyhow::anyhow; use clap::Args; @@ -7,7 +7,10 @@ use pczt::{ Pczt, }; use secrecy::ExposeSecret; -use tokio::io::{stdin, stdout, AsyncReadExt, AsyncWriteExt}; +use tokio::{ + fs::File, + io::{stdin, stdout, AsyncReadExt, AsyncWriteExt}, +}; use ::transparent::{ keys::{NonHardenedChildIndex, TransparentKeyScope}, @@ -25,6 +28,13 @@ pub(crate) struct Command { /// age identity file to decrypt the mnemonic phrase with #[arg(short, long)] identity: String, + + /// Path to a file from which to read the PCZT. If not provided, reads from stdin. + input: Option, + + /// Path to a file to which to write the PCZT. If not provided, writes to stdout. + #[arg(long)] + output: Option, } impl Command { @@ -33,7 +43,11 @@ impl Command { let params = config.network(); let mut buf = vec![]; - stdin().read_to_end(&mut buf).await?; + if let Some(input_path) = &self.input { + File::open(input_path).await?.read_to_end(&mut buf).await?; + } else { + stdin().read_to_end(&mut buf).await?; + } let pczt = Pczt::parse(&buf).map_err(|e| anyhow!("Failed to read PCZT: {:?}", e))?; @@ -202,7 +216,14 @@ impl Command { let pczt = signer.finish(); - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + let mut file = File::create(output_path).await?; + file.write_all(&pczt.serialize()).await?; + file.flush().await?; + } else { + stdout().write_all(&pczt.serialize()).await?; + stdout().flush().await?; + } Ok(()) } diff --git a/src/commands/pczt/update_with_derivation.rs b/src/commands/pczt/update_with_derivation.rs index fc0aa22..fd399be 100644 --- a/src/commands/pczt/update_with_derivation.rs +++ b/src/commands/pczt/update_with_derivation.rs @@ -1,10 +1,15 @@ +use std::path::PathBuf; + use anyhow::anyhow; use bip32::Prefix; use clap::Args; use pczt::{roles::updater::Updater, Pczt}; use sapling::zip32::DiversifiableFullViewingKey; use secrecy::ExposeSecret; -use tokio::io::{stdin, stdout, AsyncReadExt, AsyncWriteExt}; +use tokio::{ + fs::File, + io::{stdin, stdout, AsyncReadExt, AsyncWriteExt}, +}; use transparent::{address::TransparentAddress, pczt::Bip32Derivation, zip48}; use zcash_keys::keys::UnifiedSpendingKey; use zcash_protocol::{ @@ -29,6 +34,13 @@ pub(crate) struct Command { /// The ZIP 32 or BIP 44 path to derive. path: String, + + /// Path to a file from which to read the PCZT. If not provided, reads from stdin. + input: Option, + + /// Path to a file to which to write the PCZT. If not provided, writes to stdout. + #[arg(long)] + output: Option, } impl Command { @@ -39,7 +51,11 @@ impl Command { let params = config.network(); let mut buf = vec![]; - stdin().read_to_end(&mut buf).await?; + if let Some(input_path) = &self.input { + File::open(input_path).await?.read_to_end(&mut buf).await?; + } else { + stdin().read_to_end(&mut buf).await?; + } let pczt = Pczt::parse(&buf).map_err(|e| anyhow!("Failed to read PCZT: {:?}", e))?; @@ -154,7 +170,16 @@ impl Command { let pczt = updater.finish(); - stdout().write_all(&pczt.serialize()).await?; + if let Some(output_path) = &self.output { + File::create(output_path) + .await? + .write_all(&pczt.serialize()) + .await?; + } else { + let mut stdout = stdout(); + stdout.write_all(&pczt.serialize()).await?; + stdout.flush().await?; + } Ok(()) }