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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use crate::imports::*;
use kaspa_consensus_core::tx::TransactionId;
use kaspa_wallet_core::error::Error as WalletError;
use kaspa_wallet_core::storage::Binding;
#[derive(Default, Handler)]
#[help("Display transaction history")]
pub struct History;

impl History {
    async fn main(self: Arc<Self>, ctx: &Arc<dyn Context>, mut argv: Vec<String>, _cmd: &str) -> Result<()> {
        let ctx = ctx.clone().downcast_arc::<KaspaCli>()?;

        if argv.is_empty() {
            self.display_help(ctx, argv).await?;
            return Ok(());
        }

        let account = ctx.account().await?;
        let network_id = ctx.wallet().network_id()?;
        let binding = Binding::from(&account);
        let current_daa_score = ctx.wallet().current_daa_score();

        let (last, include_utxo) = match argv.remove(0).as_str() {
            "lookup" => {
                let transaction_id = if argv.is_empty() {
                    tprintln!(ctx, "usage: history lookup <transaction id>");
                    return Ok(());
                } else {
                    argv.remove(0)
                };

                let txid = TransactionId::from_hex(transaction_id.as_str())?;
                let store = ctx.wallet().store().as_transaction_record_store()?;
                match store.load_single(&binding, &network_id, &txid).await {
                    Ok(tx) => {
                        let lines = tx
                            .format_transaction_with_args(&ctx.wallet(), None, current_daa_score, true, true, Some(account.clone()))
                            .await;
                        lines.iter().for_each(|line| tprintln!(ctx, "{line}"));
                    }
                    Err(_) => {
                        tprintln!(ctx, "transaction not found");
                    }
                }

                return Ok(());
            }
            "list" => {
                let last = if argv.is_empty() { None } else { argv[0].parse::<usize>().ok() };
                (last, false)
            }
            "details" => {
                let last = if argv.is_empty() { None } else { argv[0].parse::<usize>().ok() };
                (last, true)
            }
            v => {
                tprintln!(ctx, "unknown command: '{v}'");
                self.display_help(ctx, argv).await?;
                return Ok(());
            }
        };

        let store = ctx.wallet().store().as_transaction_record_store()?;
        let mut ids = match store.transaction_id_iter(&binding, &network_id).await {
            Ok(ids) => ids,
            Err(err) => {
                if matches!(err, WalletError::NoRecordsFound) {
                    tprintln!(ctx);
                    tprintln!(ctx, "No transactions found for this account.");
                    tprintln!(ctx);
                } else {
                    terrorln!(ctx, "{err}");
                }
                return Ok(());
            }
        };
        let length = ids.size_hint().0;
        let skip = if let Some(last) = last {
            if last > length {
                0
            } else {
                length - last
            }
        } else {
            0
        };
        let mut index = 0;
        let page = 25;

        tprintln!(ctx);

        while let Some(id) = ids.try_next().await? {
            if index >= skip {
                if index > 0 && index % page == 0 {
                    tprintln!(ctx);
                    let prompt = format!(
                        "Displaying transactions {} to {} of {} (press any key to continue, 'Q' to abort)",
                        index.separated_string(),
                        (index + page).separated_string(),
                        length.separated_string()
                    );
                    let query = ctx.term().kbhit(Some(&prompt)).await?;
                    tprintln!(ctx);
                    if query.to_lowercase() == "q" {
                        return Ok(());
                    }
                }

                match store.load_single(&binding, &network_id, &id).await {
                    Ok(tx) => {
                        let lines = tx
                            .format_transaction_with_args(
                                &ctx.wallet(),
                                None,
                                current_daa_score,
                                include_utxo,
                                true,
                                Some(account.clone()),
                            )
                            .await;
                        lines.iter().for_each(|line| tprintln!(ctx, "{line}"));
                    }
                    Err(err) => {
                        terrorln!(ctx, "Unable to read transaction data: {err}");
                    }
                }
            }
            index += 1;
        }

        tprintln!(ctx);
        tprintln!(ctx, "{} transactions", length.separated_string());
        tprintln!(ctx);

        Ok(())
    }

    async fn display_help(self: Arc<Self>, ctx: Arc<KaspaCli>, _argv: Vec<String>) -> Result<()> {
        ctx.term().help(
            &[
                ("list [<last N transactions>]", "List transactions"),
                ("details [<last N transactions>]", "List transactions with UTXO details"),
                ("lookup <transaction id>", "Lookup transaction in the history"),
            ],
            None,
        )?;

        Ok(())
    }
}