1use std::collections::HashMap;
20
21use darkfi::{tx::Transaction, util::parse::encode_base10, zk::halo2::Field};
22use darkfi_money_contract::{client::OwnCoin, model::TokenId};
23use darkfi_sdk::{
24 crypto::{
25 keypair::{Address, Network, PublicKey, SecretKey, StandardAddress},
26 BaseBlind, ContractId, FuncId, DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID,
27 },
28 pasta::pallas,
29};
30use darkfi_serial::{deserialize, serialize};
31use prettytable::{format, row, Table};
32
33use crate::money::BALANCE_BASE10_DECIMALS;
34
35pub fn prettytable_addrs(
36 network: Network,
37 addresses: &[(u64, PublicKey, SecretKey, u64)],
38) -> Table {
39 let mut table = Table::new();
40 table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
41 table.set_titles(row!["Key ID", "Address", "Public Key", "Secret Key", "Is Default"]);
42 for (key_id, public_key, secret_key, is_default) in addresses {
43 let is_default = match is_default {
44 1 => "*",
45 _ => "",
46 };
47
48 let address: Address = StandardAddress::from_public(network, *public_key).into();
49 table.add_row(row![key_id, address, public_key, secret_key, is_default]);
50 }
51
52 table
53}
54
55pub fn prettytable_balance(
56 balmap: &HashMap<String, u64>,
57 alimap: &HashMap<String, String>,
58) -> Table {
59 let mut table = Table::new();
60 table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
61 table.set_titles(row!["Token ID", "Aliases", "Balance"]);
62
63 for (token_id, balance) in balmap.iter() {
64 let alias = match alimap.get(token_id) {
65 Some(v) => v,
66 None => "-",
67 };
68
69 table.add_row(row![token_id, alias, encode_base10(*balance, BALANCE_BASE10_DECIMALS)]);
70 }
71
72 table
73}
74
75pub fn prettytable_coins(
76 coins: &[(OwnCoin, u32, bool, Option<u32>, String)],
77 alimap: &HashMap<String, String>,
78) -> Table {
79 let mut table = Table::new();
80 table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
81 table.set_titles(row![
82 "Coin",
83 "Token ID",
84 "Aliases",
85 "Value",
86 "Spend Hook",
87 "User Data",
88 "Creation Height",
89 "Spent",
90 "Spent Height",
91 "Spent TX",
92 ]);
93
94 for coin in coins {
95 let alias = match alimap.get(&coin.0.note.token_id.to_string()) {
96 Some(v) => v,
97 None => "-",
98 };
99
100 let spend_hook = if coin.0.note.spend_hook != FuncId::none() {
101 format!("{}", coin.0.note.spend_hook)
102 } else {
103 String::from("-")
104 };
105
106 let user_data = if coin.0.note.user_data != pallas::Base::ZERO {
107 bs58::encode(serialize(&coin.0.note.user_data)).into_string().to_string()
108 } else {
109 String::from("-")
110 };
111
112 let spent_height = match coin.3 {
113 Some(spent_height) => spent_height.to_string(),
114 None => String::from("-"),
115 };
116
117 table.add_row(row![
118 bs58::encode(&serialize(&coin.0.coin.inner())).into_string().to_string(),
119 coin.0.note.token_id,
120 alias,
121 format!(
122 "{} ({})",
123 coin.0.note.value,
124 encode_base10(coin.0.note.value, BALANCE_BASE10_DECIMALS)
125 ),
126 spend_hook,
127 user_data,
128 coin.1,
129 coin.2,
130 spent_height,
131 coin.4,
132 ]);
133 }
134
135 table
136}
137
138pub fn prettytable_tokenlist(
139 tokens: &[(TokenId, SecretKey, BaseBlind, bool, Option<u32>)],
140 alimap: &HashMap<String, String>,
141) -> Table {
142 let mut table = Table::new();
143 table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
144 table.set_titles(row![
145 "Token ID",
146 "Aliases",
147 "Mint Authority",
148 "Token Blind",
149 "Frozen",
150 "Freeze Height",
151 ]);
152
153 for (token_id, authority, blind, frozen, freeze_height) in tokens {
154 let alias = match alimap.get(&token_id.to_string()) {
155 Some(v) => v,
156 None => "-",
157 };
158
159 let freeze_height = match freeze_height {
160 Some(freeze_height) => freeze_height.to_string(),
161 None => String::from("-"),
162 };
163
164 table.add_row(row![token_id, alias, authority, blind, frozen, freeze_height]);
165 }
166
167 table
168}
169
170pub fn prettytable_contract_history(deploy_history: &[(String, String, u32)]) -> Table {
171 let mut table = Table::new();
172 table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
173 table.set_titles(row!["Transaction Hash", "Type", "Block Height"]);
174
175 for (tx_hash, tx_type, block_height) in deploy_history {
176 table.add_row(row![tx_hash, tx_type, block_height]);
177 }
178
179 table
180}
181
182pub fn prettytable_contract_auth(auths: &[(ContractId, SecretKey, bool, Option<u32>)]) -> Table {
183 let mut table = Table::new();
184 table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
185 table.set_titles(row!["Contract ID", "Secret Key", "Locked", "Lock Height"]);
186
187 for (contract_id, secret_key, is_locked, lock_height) in auths {
188 let lock_height = match lock_height {
189 Some(lock_height) => lock_height.to_string(),
190 None => String::from("-"),
191 };
192
193 table.add_row(row![contract_id, secret_key, is_locked, lock_height]);
194 }
195
196 table
197}
198
199pub fn prettytable_aliases(alimap: &HashMap<String, TokenId>) -> Table {
200 let mut table = Table::new();
201 table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
202 table.set_titles(row!["Alias", "Token ID"]);
203
204 for (alias, token_id) in alimap.iter() {
205 table.add_row(row![alias, token_id]);
206 }
207
208 table
209}
210
211pub fn prettytable_scanned_blocks(scanned_blocks: &[(u32, String, String)]) -> Table {
212 let mut table = Table::new();
213 table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
214 table.set_titles(row!["Height", "Hash", "Signing Key"]);
215 for (height, hash, signing_key) in scanned_blocks {
216 table.add_row(row![height, hash, signing_key]);
217 }
218
219 table
220}
221
222pub fn pretty_tx(tx: &Transaction) -> String {
223 let hash = tx.hash().to_string();
224
225 let mut fees: Vec<String> = vec![];
226 let mut fees_total: u64 = 0;
227 let mut fees_overflow = false;
228
229 let mut table = Table::new();
230 table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
231 table.add_row(row!["", "Contract", "Function"]);
232
233 for (i, call) in tx.calls.iter().enumerate() {
234 if call.data.is_money_fee() {
235 if let Ok(fee) = deserialize(&call.data.data[1..9]) {
236 fees.push(format!("{} DRK", encode_base10(fee, BALANCE_BASE10_DECIMALS)));
237 fees_total = fees_total.checked_add(fee).unwrap_or_else(|| {
238 fees_overflow = true;
239 u64::MAX
240 });
241 } else {
242 fees.push("invalid".to_string());
243 }
244 }
245
246 let contract_name = match call.data.contract_id {
247 id if id == *MONEY_CONTRACT_ID => "Money",
248 id if id == *DAO_CONTRACT_ID => "DAO",
249 id if id == *DEPLOYOOOR_CONTRACT_ID => "Deployooor",
250 _ => "Custom",
251 };
252
253 let calldata = &call.data.data;
254 table.add_row(row![
255 i.to_string(),
256 format!("{} [{}]", call.data.contract_id.to_string(), contract_name),
257 if !calldata.is_empty() { calldata[0].to_string() } else { "-".to_string() },
259 ]);
260 }
261
262 let fee = match fees.len() {
263 0 => "-".to_string(),
264 1 => fees[0].clone(),
265 _ => format!(
266 "{} [TOTAL: {}]",
267 fees.join(", "),
268 if fees_overflow {
269 "OVERFLOW".to_string()
270 } else {
271 format!("{} DRK", encode_base10(fees_total, BALANCE_BASE10_DECIMALS))
272 }
273 ),
274 };
275
276 format!("Hash: {hash}\nFee: {fee}\n\n{table}")
277}