kaspa_cli_lib/extensions/
transaction.rs1use 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}