Merge pull request #65 from Electric-Coin-Company/shield-funds

Add commands to shield funds
This commit is contained in:
Jack Grigg 2024-12-15 04:11:32 +13:00 committed by GitHub
commit 39b668a314
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 349 additions and 38 deletions

View File

@ -11,6 +11,7 @@ pub(crate) mod pczt;
pub(crate) mod propose; pub(crate) mod propose;
pub(crate) mod reset; pub(crate) mod reset;
pub(crate) mod send; pub(crate) mod send;
pub(crate) mod shield;
pub(crate) mod sync; pub(crate) mod sync;
pub(crate) mod upgrade; pub(crate) mod upgrade;

View File

@ -4,8 +4,9 @@ use gumdrop::Options;
use iso_currency::Currency; use iso_currency::Currency;
use rust_decimal::{prelude::FromPrimitive, Decimal}; use rust_decimal::{prelude::FromPrimitive, Decimal};
use tracing::{info, warn}; use tracing::{info, warn};
use uuid::Uuid;
use zcash_client_backend::{data_api::WalletRead, tor}; use zcash_client_backend::{data_api::WalletRead, tor};
use zcash_client_sqlite::WalletDb; use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_protocol::value::{Zatoshis, COIN}; use zcash_protocol::value::{Zatoshis, COIN};
use crate::{ use crate::{
@ -16,6 +17,13 @@ use crate::{
// Options accepted for the `balance` command // Options accepted for the `balance` command
#[derive(Debug, Options)] #[derive(Debug, Options)]
pub(crate) struct Command { pub(crate) struct Command {
#[options(
free,
required,
help = "the UUID of the account for which to get a balance"
)]
account_id: Uuid,
#[options(help = "Convert ZEC values into the given currency")] #[options(help = "Convert ZEC values into the given currency")]
convert: Option<Currency>, convert: Option<Currency>,
} }
@ -26,10 +34,7 @@ impl Command {
let (_, db_data) = get_db_paths(wallet_dir.as_ref()); let (_, db_data) = get_db_paths(wallet_dir.as_ref());
let db_data = WalletDb::for_path(db_data, params)?; let db_data = WalletDb::for_path(db_data, params)?;
let account_id = *db_data let account_id = AccountUuid::from_uuid(self.account_id);
.get_account_ids()?
.first()
.ok_or(anyhow!("Wallet has no accounts"))?;
let address = db_data let address = db_data
.get_current_address(account_id)? .get_current_address(account_id)?

View File

@ -1,18 +1,26 @@
use anyhow::anyhow; use anyhow::anyhow;
use gumdrop::Options; use gumdrop::Options;
use uuid::Uuid;
use zcash_client_backend::{ use zcash_client_backend::{
data_api::{InputSource, WalletRead}, data_api::{InputSource, WalletRead},
ShieldedProtocol, ShieldedProtocol,
}; };
use zcash_client_sqlite::WalletDb; use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_protocol::value::{Zatoshis, MAX_MONEY}; use zcash_protocol::value::{Zatoshis, MAX_MONEY};
use crate::{config::get_wallet_network, data::get_db_paths, error, ui::format_zec}; use crate::{config::get_wallet_network, data::get_db_paths, error, ui::format_zec};
// Options accepted for the `balance` command // Options accepted for the `list-unspent` command
#[derive(Debug, Options)] #[derive(Debug, Options)]
pub(crate) struct Command {} pub(crate) struct Command {
#[options(
free,
required,
help = "the UUID of the account for which to list unspent funds"
)]
account_id: Uuid,
}
impl Command { impl Command {
pub(crate) fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> { pub(crate) fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> {
@ -20,10 +28,7 @@ impl Command {
let (_, db_data) = get_db_paths(wallet_dir); let (_, db_data) = get_db_paths(wallet_dir);
let db_data = WalletDb::for_path(db_data, params)?; let db_data = WalletDb::for_path(db_data, params)?;
let account = *db_data let account_id = AccountUuid::from_uuid(self.account_id);
.get_account_ids()?
.first()
.ok_or(anyhow!("Wallet has no accounts"))?;
// Use the height of the maximum scanned block as the anchor height, to emulate a // Use the height of the maximum scanned block as the anchor height, to emulate a
// zero-conf transaction in order to select every note in the wallet. // zero-conf transaction in order to select every note in the wallet.
@ -34,7 +39,7 @@ impl Command {
.block_height(); .block_height();
let notes = db_data.select_spendable_notes( let notes = db_data.select_spendable_notes(
account, account_id,
Zatoshis::const_from_u64(MAX_MONEY), Zatoshis::const_from_u64(MAX_MONEY),
&[ShieldedProtocol::Sapling, ShieldedProtocol::Orchard], &[ShieldedProtocol::Sapling, ShieldedProtocol::Orchard],
anchor_height, anchor_height,

View File

@ -5,6 +5,7 @@ pub(crate) mod create;
pub(crate) mod inspect; pub(crate) mod inspect;
pub(crate) mod prove; pub(crate) mod prove;
pub(crate) mod send; pub(crate) mod send;
pub(crate) mod shield;
pub(crate) mod sign; pub(crate) mod sign;
#[cfg(feature = "pczt-qr")] #[cfg(feature = "pczt-qr")]
@ -14,6 +15,8 @@ pub(crate) mod qr;
pub(crate) enum Command { pub(crate) enum Command {
#[options(help = "create a PCZT")] #[options(help = "create a PCZT")]
Create(create::Command), Create(create::Command),
#[options(help = "create a shielding PCZT")]
Shield(shield::Command),
#[options(help = "inspect a PCZT")] #[options(help = "inspect a PCZT")]
Inspect(inspect::Command), Inspect(inspect::Command),
#[options(help = "create proofs for a PCZT")] #[options(help = "create proofs for a PCZT")]

View File

@ -5,19 +5,17 @@ use anyhow::anyhow;
use gumdrop::Options; use gumdrop::Options;
use tokio::io::{stdout, AsyncWriteExt}; use tokio::io::{stdout, AsyncWriteExt};
use uuid::Uuid;
use zcash_address::ZcashAddress; use zcash_address::ZcashAddress;
use zcash_client_backend::{ use zcash_client_backend::{
data_api::{ data_api::wallet::{
wallet::{ create_pczt_from_proposal, input_selection::GreedyInputSelector, propose_transfer,
create_pczt_from_proposal, input_selection::GreedyInputSelector, propose_transfer,
},
WalletRead,
}, },
fees::{standard::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule}, fees::{standard::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
wallet::OvkPolicy, wallet::OvkPolicy,
ShieldedProtocol, ShieldedProtocol,
}; };
use zcash_client_sqlite::WalletDb; use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_protocol::{ use zcash_protocol::{
memo::{Memo, MemoBytes}, memo::{Memo, MemoBytes},
value::Zatoshis, value::Zatoshis,
@ -29,6 +27,9 @@ use crate::{config::WalletConfig, data::get_db_paths, error, MIN_CONFIRMATIONS};
// Options accepted for the `pczt create` command // Options accepted for the `pczt create` command
#[derive(Debug, Options)] #[derive(Debug, Options)]
pub(crate) struct Command { pub(crate) struct Command {
#[options(free, required, help = "the UUID of the account to send funds from")]
account_id: Uuid,
#[options( #[options(
required, required,
help = "the recipient's Unified, Sapling or transparent address" help = "the recipient's Unified, Sapling or transparent address"
@ -61,10 +62,7 @@ impl Command {
let (_, db_data) = get_db_paths(wallet_dir.as_ref()); let (_, db_data) = get_db_paths(wallet_dir.as_ref());
let mut db_data = WalletDb::for_path(db_data, params)?; let mut db_data = WalletDb::for_path(db_data, params)?;
let account_id = *db_data let account_id = AccountUuid::from_uuid(self.account_id);
.get_account_ids()?
.first()
.ok_or(anyhow!("Wallet has no accounts"))?;
// Create the PCZT. // Create the PCZT.
let change_strategy = MultiOutputChangeStrategy::new( let change_strategy = MultiOutputChangeStrategy::new(

100
src/commands/pczt/shield.rs Normal file
View File

@ -0,0 +1,100 @@
use std::num::NonZeroUsize;
use anyhow::anyhow;
use gumdrop::Options;
use tokio::io::{stdout, AsyncWriteExt};
use uuid::Uuid;
use zcash_client_backend::{
data_api::{
wallet::{
create_pczt_from_proposal, input_selection::GreedyInputSelector, propose_shielding,
},
WalletRead,
},
fees::{standard::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
wallet::OvkPolicy,
ShieldedProtocol,
};
use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_protocol::value::Zatoshis;
use crate::{config::WalletConfig, data::get_db_paths, error};
// Options accepted for the `pczt shield` command
#[derive(Debug, Options)]
pub(crate) struct Command {
#[options(free, required, help = "the UUID of the account to shield funds in")]
account_id: Uuid,
#[options(
help = "note management: the number of notes to maintain in the wallet",
default = "4"
)]
target_note_count: usize,
#[options(
help = "note management: the minimum allowed value for split change amounts",
default = "10000000"
)]
min_split_output_value: u64,
}
impl Command {
pub(crate) async fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> {
let config = WalletConfig::read(wallet_dir.as_ref())?;
let params = config.network();
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
let mut db_data = WalletDb::for_path(db_data, params)?;
let account_id = AccountUuid::from_uuid(self.account_id);
// Create the PCZT.
let change_strategy = MultiOutputChangeStrategy::new(
StandardFeeRule::Zip317,
None,
ShieldedProtocol::Orchard,
DustOutputPolicy::default(),
SplitPolicy::with_min_output_value(
NonZeroUsize::new(self.target_note_count)
.ok_or(anyhow!("target note count must be nonzero"))?,
Zatoshis::from_u64(self.min_split_output_value)?,
),
);
let input_selector = GreedyInputSelector::new();
// For this dev tool, shield all funds immediately.
let max_height = match db_data.chain_height()? {
Some(max_height) => max_height,
// If we haven't scanned anything, there's nothing to do.
None => return Ok(()),
};
let transparent_balances = db_data.get_transparent_balances(account_id, max_height)?;
let from_addrs = transparent_balances.into_keys().collect::<Vec<_>>();
let proposal = propose_shielding(
&mut db_data,
&params,
&input_selector,
&change_strategy,
Zatoshis::ZERO,
&from_addrs,
account_id,
0,
)
.map_err(error::Error::Shield)?;
let pczt = create_pczt_from_proposal(
&mut db_data,
&params,
account_id,
OvkPolicy::Sender,
&proposal,
)
.map_err(error::Error::Shield)?;
stdout().write_all(&pczt.serialize()).await?;
Ok(())
}
}

View File

@ -3,16 +3,14 @@ use std::{num::NonZeroUsize, str::FromStr};
use anyhow::anyhow; use anyhow::anyhow;
use gumdrop::Options; use gumdrop::Options;
use uuid::Uuid;
use zcash_address::ZcashAddress; use zcash_address::ZcashAddress;
use zcash_client_backend::{ use zcash_client_backend::{
data_api::{ data_api::wallet::{input_selection::GreedyInputSelector, propose_transfer},
wallet::{input_selection::GreedyInputSelector, propose_transfer},
WalletRead,
},
fees::{zip317::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule}, fees::{zip317::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
ShieldedProtocol, ShieldedProtocol,
}; };
use zcash_client_sqlite::WalletDb; use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_protocol::value::Zatoshis; use zcash_protocol::value::Zatoshis;
use zip321::{Payment, TransactionRequest}; use zip321::{Payment, TransactionRequest};
@ -20,6 +18,9 @@ use crate::{config::get_wallet_network, data::get_db_paths, error, MIN_CONFIRMAT
// Options accepted for the `propose` command // Options accepted for the `propose` command
#[derive(Debug, Options)] #[derive(Debug, Options)]
pub(crate) struct Command { pub(crate) struct Command {
#[options(free, required, help = "the UUID of the account to send funds from")]
account_id: Uuid,
#[options( #[options(
required, required,
help = "the recipient's Unified, Sapling or transparent address" help = "the recipient's Unified, Sapling or transparent address"
@ -48,10 +49,7 @@ impl Command {
let (_, db_data) = get_db_paths(wallet_dir.as_ref()); let (_, db_data) = get_db_paths(wallet_dir.as_ref());
let mut db_data = WalletDb::for_path(db_data, params)?; let mut db_data = WalletDb::for_path(db_data, params)?;
let account = *db_data let account_id = AccountUuid::from_uuid(self.account_id);
.get_account_ids()?
.first()
.ok_or_else(|| anyhow!("Wallet has no accounts."))?;
let change_strategy = MultiOutputChangeStrategy::new( let change_strategy = MultiOutputChangeStrategy::new(
StandardFeeRule::Zip317, StandardFeeRule::Zip317,
@ -75,7 +73,7 @@ impl Command {
let proposal = propose_transfer( let proposal = propose_transfer(
&mut db_data, &mut db_data,
&params, &params,
account, account_id,
&input_selector, &input_selector,
&change_strategy, &change_strategy,
request, request,

View File

@ -5,6 +5,7 @@ use anyhow::anyhow;
use gumdrop::Options; use gumdrop::Options;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
use uuid::Uuid;
use zcash_address::ZcashAddress; use zcash_address::ZcashAddress;
use zcash_client_backend::{ use zcash_client_backend::{
data_api::{ data_api::{
@ -19,7 +20,7 @@ use zcash_client_backend::{
wallet::OvkPolicy, wallet::OvkPolicy,
ShieldedProtocol, ShieldedProtocol,
}; };
use zcash_client_sqlite::WalletDb; use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_proofs::prover::LocalTxProver; use zcash_proofs::prover::LocalTxProver;
use zcash_protocol::value::Zatoshis; use zcash_protocol::value::Zatoshis;
use zip321::{Payment, TransactionRequest}; use zip321::{Payment, TransactionRequest};
@ -35,6 +36,9 @@ use crate::{
// Options accepted for the `send` command // Options accepted for the `send` command
#[derive(Debug, Options)] #[derive(Debug, Options)]
pub(crate) struct Command { pub(crate) struct Command {
#[options(free, required, help = "the UUID of the account to send funds from")]
account_id: Uuid,
#[options( #[options(
required, required,
help = "age identity file to decrypt the mnemonic phrase with" help = "age identity file to decrypt the mnemonic phrase with"
@ -80,10 +84,7 @@ impl Command {
let (_, db_data) = get_db_paths(wallet_dir.as_ref()); let (_, db_data) = get_db_paths(wallet_dir.as_ref());
let mut db_data = WalletDb::for_path(db_data, params)?; let mut db_data = WalletDb::for_path(db_data, params)?;
let account_id = *db_data let account_id = AccountUuid::from_uuid(self.account_id);
.get_account_ids()?
.first()
.ok_or(anyhow!("Wallet has no accounts"))?;
let account = db_data let account = db_data
.get_account(account_id)? .get_account(account_id)?
.ok_or(anyhow!("Account missing: {:?}", account_id))?; .ok_or(anyhow!("Account missing: {:?}", account_id))?;

183
src/commands/shield.rs Normal file
View File

@ -0,0 +1,183 @@
use std::num::NonZeroUsize;
use anyhow::anyhow;
use gumdrop::Options;
use secrecy::ExposeSecret;
use uuid::Uuid;
use zcash_client_backend::{
data_api::{
wallet::{
create_proposed_transactions, input_selection::GreedyInputSelector, propose_shielding,
},
Account, WalletRead,
},
fees::{standard::MultiOutputChangeStrategy, DustOutputPolicy, SplitPolicy, StandardFeeRule},
keys::UnifiedSpendingKey,
proto::service,
wallet::OvkPolicy,
ShieldedProtocol,
};
use zcash_client_sqlite::{AccountUuid, WalletDb};
use zcash_proofs::prover::LocalTxProver;
use zcash_protocol::value::Zatoshis;
use crate::{
config::WalletConfig,
data::get_db_paths,
error,
remote::{tor_client, Servers},
};
// Options accepted for the `shield` command
#[derive(Debug, Options)]
pub(crate) struct Command {
#[options(free, required, help = "the UUID of the account to shield funds in")]
account_id: Uuid,
#[options(
required,
help = "age identity file to decrypt the mnemonic phrase with"
)]
identity: String,
#[options(
help = "the server to shield via (default is \"ecc\")",
default = "ecc",
parse(try_from_str = "Servers::parse")
)]
server: Servers,
#[options(help = "disable connections via TOR")]
disable_tor: bool,
#[options(
help = "note management: the number of notes to maintain in the wallet",
default = "4"
)]
target_note_count: usize,
#[options(
help = "note management: the minimum allowed value for split change amounts",
default = "10000000"
)]
min_split_output_value: u64,
}
impl Command {
pub(crate) async fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> {
let mut config = WalletConfig::read(wallet_dir.as_ref())?;
let params = config.network();
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
let mut db_data = WalletDb::for_path(db_data, params)?;
let account_id = AccountUuid::from_uuid(self.account_id);
let account = db_data
.get_account(account_id)?
.ok_or(anyhow!("Account missing: {:?}", account_id))?;
let derivation = account.source().key_derivation().ok_or(anyhow!(
"Cannot spend from view-only accounts; did you mean to use `pczt shield` instead?"
))?;
// Decrypt the mnemonic to access the seed.
let identities = age::IdentityFile::from_file(self.identity)?.into_identities()?;
config.decrypt(identities.iter().map(|i| i.as_ref() as _))?;
let usk = UnifiedSpendingKey::from_seed(
&params,
config
.seed()
.ok_or(anyhow!("Seed must be present to enable sending"))?
.expose_secret(),
derivation.account_index(),
)
.map_err(error::Error::from)?;
let server = self.server.pick(params)?;
let mut client = if self.disable_tor {
server.connect_direct().await?
} else {
server.connect(|| tor_client(wallet_dir.as_ref())).await?
};
// Create the transaction.
println!("Creating transaction...");
let prover =
LocalTxProver::with_default_location().ok_or(error::Error::MissingParameters)?;
let change_strategy = MultiOutputChangeStrategy::new(
StandardFeeRule::Zip317,
None,
ShieldedProtocol::Orchard,
DustOutputPolicy::default(),
SplitPolicy::with_min_output_value(
NonZeroUsize::new(self.target_note_count)
.ok_or(anyhow!("target note count must be nonzero"))?,
Zatoshis::from_u64(self.min_split_output_value)?,
),
);
let input_selector = GreedyInputSelector::new();
// For this dev tool, shield all funds immediately.
let max_height = match db_data.chain_height()? {
Some(max_height) => max_height,
// If we haven't scanned anything, there's nothing to do.
None => return Ok(()),
};
let transparent_balances = db_data.get_transparent_balances(account_id, max_height)?;
let from_addrs = transparent_balances.into_keys().collect::<Vec<_>>();
let proposal = propose_shielding(
&mut db_data,
&params,
&input_selector,
&change_strategy,
Zatoshis::ZERO,
&from_addrs,
account_id,
0,
)
.map_err(error::Error::Shield)?;
let txids = create_proposed_transactions(
&mut db_data,
&params,
&prover,
&prover,
&usk,
OvkPolicy::Sender,
&proposal,
)
.map_err(error::Error::Shield)?;
if txids.len() > 1 {
return Err(anyhow!(
"Multi-transaction proposals are not yet supported."
));
}
let txid = *txids.first();
// Send the transaction.
println!("Sending transaction...");
let (txid, raw_tx) = db_data
.get_transaction(txid)?
.map(|tx| {
let mut raw_tx = service::RawTransaction::default();
tx.write(&mut raw_tx.data).unwrap();
(tx.txid(), raw_tx)
})
.ok_or(anyhow!("Transaction not found for id {:?}", txid))?;
let response = client.send_transaction(raw_tx).await?.into_inner();
if response.error_code != 0 {
Err(error::Error::SendFailed {
code: response.error_code,
reason: response.error_message,
}
.into())
} else {
println!("{}", txid);
Ok(())
}
}
}

View File

@ -1,3 +1,4 @@
use std::convert::Infallible;
use std::fmt; use std::fmt;
use zcash_client_backend::{ use zcash_client_backend::{
@ -22,6 +23,15 @@ pub(crate) type WalletErrorT = WalletError<
ReceivedNoteId, ReceivedNoteId,
>; >;
pub(crate) type ShieldErrorT = WalletError<
SqliteClientError,
commitment_tree::Error,
GreedyInputSelectorError,
zip317::FeeError,
zip317::FeeError,
Infallible,
>;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
Cache(FsBlockDbError), Cache(FsBlockDbError),
@ -32,6 +42,7 @@ pub enum Error {
InvalidTreeState, InvalidTreeState,
MissingParameters, MissingParameters,
SendFailed { code: i32, reason: String }, SendFailed { code: i32, reason: String },
Shield(ShieldErrorT),
Wallet(WalletErrorT), Wallet(WalletErrorT),
Zip321(Zip321Error), Zip321(Zip321Error),
} }
@ -47,6 +58,7 @@ impl fmt::Display for Error {
Error::InvalidTreeState => write!(f, "Invalid TreeState received from server"), Error::InvalidTreeState => write!(f, "Invalid TreeState received from server"),
Error::MissingParameters => write!(f, "Missing proving parameters"), Error::MissingParameters => write!(f, "Missing proving parameters"),
Error::SendFailed { code, reason } => write!(f, "Send failed: ({}) {}", code, reason), Error::SendFailed { code, reason } => write!(f, "Send failed: ({}) {}", code, reason),
Error::Shield(e) => e.fmt(f),
Error::Wallet(e) => e.fmt(f), Error::Wallet(e) => e.fmt(f),
Error::Zip321(e) => write!(f, "{:?}", e), Error::Zip321(e) => write!(f, "{:?}", e),
} }

View File

@ -73,6 +73,9 @@ enum Command {
#[options(help = "list the unspent notes in the wallet")] #[options(help = "list the unspent notes in the wallet")]
ListUnspent(commands::list_unspent::Command), ListUnspent(commands::list_unspent::Command),
#[options(help = "shield transparent funds received by the wallet")]
Shield(commands::shield::Command),
#[options(help = "propose a transfer of funds to the given address and display the proposal")] #[options(help = "propose a transfer of funds to the given address and display the proposal")]
Propose(commands::propose::Command), Propose(commands::propose::Command),
@ -165,10 +168,12 @@ fn main() -> Result<(), anyhow::Error> {
Some(Command::ListAddresses(command)) => command.run(opts.wallet_dir), Some(Command::ListAddresses(command)) => command.run(opts.wallet_dir),
Some(Command::ListTx(command)) => command.run(opts.wallet_dir), Some(Command::ListTx(command)) => command.run(opts.wallet_dir),
Some(Command::ListUnspent(command)) => command.run(opts.wallet_dir), Some(Command::ListUnspent(command)) => command.run(opts.wallet_dir),
Some(Command::Shield(command)) => command.run(opts.wallet_dir).await,
Some(Command::Propose(command)) => command.run(opts.wallet_dir).await, Some(Command::Propose(command)) => command.run(opts.wallet_dir).await,
Some(Command::Send(command)) => command.run(opts.wallet_dir).await, Some(Command::Send(command)) => command.run(opts.wallet_dir).await,
Some(Command::Pczt(command)) => match command { Some(Command::Pczt(command)) => match command {
commands::pczt::Command::Create(command) => command.run(opts.wallet_dir).await, commands::pczt::Command::Create(command) => command.run(opts.wallet_dir).await,
commands::pczt::Command::Shield(command) => command.run(opts.wallet_dir).await,
commands::pczt::Command::Inspect(command) => command.run().await, commands::pczt::Command::Inspect(command) => command.run().await,
commands::pczt::Command::Prove(command) => command.run(opts.wallet_dir).await, commands::pczt::Command::Prove(command) => command.run(opts.wallet_dir).await,
commands::pczt::Command::Sign(command) => command.run(opts.wallet_dir).await, commands::pczt::Command::Sign(command) => command.run(opts.wallet_dir).await,