1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use anyhow::Result;

use address::AddressCmd;
use balance::BalanceCmd;
use staked::StakedCmd;
use transaction_hashes::TransactionHashesCmd;
use tx::TxCmd;
use wallet_id::WalletIdCmd;

use crate::App;

use self::auction::AuctionCmd;

mod address;
mod auction;
mod balance;
mod staked;
mod wallet_id;

pub mod transaction_hashes;
mod tx;

#[derive(Debug, clap::Subcommand)]
pub enum ViewCmd {
    /// View your auction information
    Auction(AuctionCmd),
    /// View your wallet id
    WalletId(WalletIdCmd),
    /// View one of your addresses, either by numerical index, or a random ephemeral one.
    Address(AddressCmd),
    /// View your account balances.
    Balance(BalanceCmd),
    /// View your staked delegation tokens.
    Staked(StakedCmd),
    /// Deletes all scanned data and local state, while leaving keys untouched.
    Reset(Reset),
    /// Synchronizes the client, privately scanning the chain state.
    ///
    /// `pcli` syncs automatically prior to any action requiring chain state,
    /// but this command can be used to "pre-sync" before interactive use.
    Sync,
    /// Get transaction hashes and block heights of spendable notes.
    #[clap(visible_alias = "list-tx-hashes")]
    ListTransactionHashes(TransactionHashesCmd),
    /// Displays a transaction's details by hash.
    Tx(TxCmd),
}

impl ViewCmd {
    pub fn offline(&self) -> bool {
        match self {
            ViewCmd::Auction(auction_cmd) => auction_cmd.offline(),
            ViewCmd::WalletId(wallet_id_cmd) => wallet_id_cmd.offline(),
            ViewCmd::Address(address_cmd) => address_cmd.offline(),
            ViewCmd::Balance(balance_cmd) => balance_cmd.offline(),
            ViewCmd::Staked(staked_cmd) => staked_cmd.offline(),
            ViewCmd::Reset(_) => true,
            ViewCmd::Sync => false,
            ViewCmd::ListTransactionHashes(transactions_cmd) => transactions_cmd.offline(),
            ViewCmd::Tx(tx_cmd) => tx_cmd.offline(),
        }
    }

    pub async fn exec(&self, app: &mut App) -> Result<()> {
        // TODO: refactor view methods to take a single App
        let full_viewing_key = app.config.full_viewing_key.clone();

        match self {
            ViewCmd::Auction(auction_cmd) => {
                auction_cmd.exec(app.view(), &full_viewing_key).await?
            }
            ViewCmd::WalletId(wallet_id_cmd) => {
                wallet_id_cmd.exec(&full_viewing_key)?;
            }
            ViewCmd::Tx(tx_cmd) => {
                tx_cmd.exec(app).await?;
            }
            ViewCmd::ListTransactionHashes(transactions_cmd) => {
                let view_client = app.view();
                transactions_cmd
                    .exec(&full_viewing_key, view_client)
                    .await?;
            }
            ViewCmd::Sync => {
                // We set needs_sync() -> true, so by this point, we have
                // already synchronized the wallet above, so we can just return.
            }
            ViewCmd::Reset(_reset) => {
                // The wallet has already been reset by a short-circuiting path.
            }
            ViewCmd::Address(address_cmd) => {
                address_cmd.exec(&full_viewing_key)?;
            }
            ViewCmd::Balance(balance_cmd) => {
                let view_client = app.view();
                balance_cmd.exec(view_client).await?;
            }
            ViewCmd::Staked(staked_cmd) => {
                let channel = app.pd_channel().await?;
                let view_client = app.view();
                staked_cmd
                    .exec(&full_viewing_key, view_client, channel)
                    .await?;
            }
        }

        Ok(())
    }
}

#[derive(Debug, clap::Parser)]
pub struct Reset;

impl Reset {
    pub fn exec(&self, data_path: impl AsRef<camino::Utf8Path>) -> Result<()> {
        tracing::info!("resetting client state");
        let view_path = data_path.as_ref().join(crate::VIEW_FILE_NAME);
        if view_path.is_file() {
            std::fs::remove_file(&view_path)?;
            println!("Deleted view data at {view_path}");
        } else if view_path.exists() {
            anyhow::bail!(
                "Expected view data at {} but found something that is not a file; refusing to delete it",
                view_path
            );
        } else {
            anyhow::bail!(
                "No view data exists at {}, so it cannot be deleted",
                view_path
            );
        }

        Ok(())
    }
}