drk/
common.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use 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            // Function code
258            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}