kaspa_cli_lib/extensions/
transaction.rs

1use crate::imports::*;
2use kaspa_consensus_core::tx::{TransactionInput, TransactionOutpoint};
3use kaspa_wallet_core::storage::Binding;
4use kaspa_wallet_core::storage::{TransactionData, TransactionKind, TransactionRecord};
5use kaspa_wallet_core::wallet::WalletGuard;
6use workflow_log::style;
7
8pub trait TransactionTypeExtension {
9    fn style(&self, s: &str) -> String;
10    fn style_with_sign(&self, s: &str, history: bool) -> String;
11}
12
13impl TransactionTypeExtension for TransactionKind {
14    fn style(&self, s: &str) -> String {
15        match self {
16            TransactionKind::Incoming => style(s).green().to_string(),
17            TransactionKind::Outgoing => style(s).red().to_string(),
18            TransactionKind::External => style(s).red().to_string(),
19            TransactionKind::Batch => style(s).dim().to_string(),
20            TransactionKind::Reorg => style(s).dim().to_string(),
21            TransactionKind::Stasis => style(s).dim().to_string(),
22            TransactionKind::TransferIncoming => style(s).green().to_string(),
23            TransactionKind::TransferOutgoing => style(s).red().to_string(),
24            TransactionKind::Change => style(s).dim().to_string(),
25        }
26    }
27
28    fn style_with_sign(&self, s: &str, history: bool) -> String {
29        match self {
30            TransactionKind::Incoming => style("+".to_string() + s).green().to_string(),
31            TransactionKind::TransferIncoming => style("+".to_string() + s).green().to_string(),
32            TransactionKind::Outgoing => style("-".to_string() + s).red().to_string(),
33            TransactionKind::TransferOutgoing => style("-".to_string() + s).red().to_string(),
34            TransactionKind::External => style("-".to_string() + s).red().to_string(),
35            TransactionKind::Batch => style("".to_string() + s).dim().to_string(),
36            TransactionKind::Reorg => {
37                if history {
38                    style("".to_string() + s).dim()
39                } else {
40                    style("-".to_string() + s).red()
41                }
42            }
43            .to_string(),
44            TransactionKind::Stasis => style("".to_string() + s).dim().to_string(),
45            _ => style(s).dim().to_string(),
46        }
47    }
48}
49
50#[async_trait]
51pub trait TransactionExtension {
52    async fn format_transaction(&self, wallet: &Arc<Wallet>, include_utxos: bool, guard: &WalletGuard) -> Vec<String>;
53    async fn format_transaction_with_state(
54        &self,
55        wallet: &Arc<Wallet>,
56        state: Option<&str>,
57        include_utxos: bool,
58        guard: &WalletGuard,
59    ) -> Vec<String>;
60    async fn format_transaction_with_args(
61        &self,
62        wallet: &Arc<Wallet>,
63        state: Option<&str>,
64        current_daa_score: Option<u64>,
65        include_utxos: bool,
66        history: bool,
67        account: Option<Arc<dyn Account>>,
68        guard: &WalletGuard,
69    ) -> Vec<String>;
70}
71
72#[async_trait]
73impl TransactionExtension for TransactionRecord {
74    async fn format_transaction(&self, wallet: &Arc<Wallet>, include_utxos: bool, guard: &WalletGuard) -> Vec<String> {
75        self.format_transaction_with_args(wallet, None, None, include_utxos, false, None, guard).await
76    }
77
78    async fn format_transaction_with_state(
79        &self,
80        wallet: &Arc<Wallet>,
81        state: Option<&str>,
82        include_utxos: bool,
83        guard: &WalletGuard,
84    ) -> Vec<String> {
85        self.format_transaction_with_args(wallet, state, None, include_utxos, false, None, guard).await
86    }
87
88    async fn format_transaction_with_args(
89        &self,
90        wallet: &Arc<Wallet>,
91        state: Option<&str>,
92        current_daa_score: Option<u64>,
93        include_utxos: bool,
94        history: bool,
95        account: Option<Arc<dyn Account>>,
96        guard: &WalletGuard,
97    ) -> Vec<String> {
98        let TransactionRecord { id, binding, block_daa_score, transaction_data, .. } = self;
99
100        let name = match binding {
101            Binding::Custom(id) => style(id.short()).cyan(),
102            Binding::Account(account_id) => {
103                let account = if let Some(account) = account {
104                    Some(account)
105                } else {
106                    wallet.get_account_by_id(account_id, guard).await.ok().flatten()
107                };
108
109                if let Some(account) = account {
110                    style(account.name_with_id()).cyan()
111                } else {
112                    style(account_id.short() + " ??").magenta()
113                }
114            }
115        };
116
117        let transaction_type = transaction_data.kind();
118        let kind = transaction_type.style(&transaction_type.to_string());
119
120        let maturity = current_daa_score.map(|score| self.maturity(score).to_string()).unwrap_or_default();
121
122        let block_daa_score = block_daa_score.separated_string();
123        let state = state.unwrap_or(&maturity);
124        let mut lines = vec![format!("{name} {id} @{block_daa_score} DAA - {kind} {state}")];
125
126        let suffix = kaspa_suffix(&self.network_id.network_type);
127
128        match transaction_data {
129            TransactionData::Reorg { utxo_entries, aggregate_input_value }
130            | TransactionData::Stasis { utxo_entries, aggregate_input_value }
131            | TransactionData::Incoming { utxo_entries, aggregate_input_value }
132            | TransactionData::External { utxo_entries, aggregate_input_value }
133            | TransactionData::Change { utxo_entries, aggregate_input_value, .. } => {
134                let aggregate_input_value =
135                    transaction_type.style_with_sign(sompi_to_kaspa_string(*aggregate_input_value).as_str(), history);
136                lines.push(format!("{:>4}UTXOs: {}  Total: {}", "", utxo_entries.len(), aggregate_input_value));
137                if include_utxos {
138                    for utxo_entry in utxo_entries {
139                        let address =
140                            style(utxo_entry.address.as_ref().map(|addr| addr.to_string()).unwrap_or_else(|| "n/a".to_string()))
141                                .blue();
142                        let index = utxo_entry.index;
143                        let is_coinbase = if utxo_entry.is_coinbase {
144                            style(format!("coinbase utxo [{index}]")).dim()
145                        } else {
146                            style(format!("standard utxo [{index}]")).dim()
147                        };
148                        let amount = transaction_type.style_with_sign(sompi_to_kaspa_string(utxo_entry.amount).as_str(), history);
149
150                        lines.push(format!("{:>4}{address}", ""));
151                        lines.push(format!("{:>4}{amount} {suffix} {is_coinbase}", ""));
152                    }
153                }
154            }
155            TransactionData::Outgoing { fees, aggregate_input_value, transaction, payment_value, change_value, .. }
156            | TransactionData::Batch { fees, aggregate_input_value, transaction, payment_value, change_value, .. }
157            | TransactionData::TransferIncoming { fees, aggregate_input_value, transaction, payment_value, change_value, .. }
158            | TransactionData::TransferOutgoing { fees, aggregate_input_value, transaction, payment_value, change_value, .. } => {
159                if let Some(payment_value) = payment_value {
160                    lines.push(format!(
161                        "{:>4}Payment: {}  Used: {}  Fees: {}  Change: {}  UTXOs: [{}↠{}]",
162                        "",
163                        style(sompi_to_kaspa_string(*payment_value)).red(),
164                        style(sompi_to_kaspa_string(*aggregate_input_value)).blue(),
165                        style(sompi_to_kaspa_string(*fees)).red(),
166                        style(sompi_to_kaspa_string(*change_value)).green(),
167                        transaction.inputs.len(),
168                        transaction.outputs.len(),
169                    ));
170                } else {
171                    lines.push(format!(
172                        "{:>4}Sweep: {}  Fees: {}  Change: {}  UTXOs: [{}↠{}]",
173                        "",
174                        style(sompi_to_kaspa_string(*aggregate_input_value)).blue(),
175                        style(sompi_to_kaspa_string(*fees)).red(),
176                        style(sompi_to_kaspa_string(*change_value)).green(),
177                        transaction.inputs.len(),
178                        transaction.outputs.len(),
179                    ));
180                }
181
182                if include_utxos {
183                    for input in transaction.inputs.iter() {
184                        let TransactionInput { previous_outpoint, signature_script: _, sequence, sig_op_count } = input;
185                        let TransactionOutpoint { transaction_id, index } = previous_outpoint;
186
187                        lines.push(format!("{:>4}{sequence:>2}: {transaction_id}:{index} SigOps: {sig_op_count}", ""));
188                    }
189                }
190            }
191        }
192
193        lines
194    }
195}