/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2026 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::collections::HashMap; use darkfi::{tx::Transaction, util::parse::encode_base10, zk::halo2::Field}; use darkfi_money_contract::{client::OwnCoin, model::TokenId}; use darkfi_sdk::{ crypto::{ keypair::{Address, Network, PublicKey, SecretKey, StandardAddress}, BaseBlind, ContractId, FuncId, DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID, }, pasta::pallas, }; use darkfi_serial::{deserialize, serialize}; use prettytable::{format, row, Table}; use crate::money::BALANCE_BASE10_DECIMALS; pub fn prettytable_addrs( network: Network, addresses: &[(u64, PublicKey, SecretKey, u64)], ) -> Table { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_titles(row!["Key ID", "Address", "Public Key", "Secret Key", "Is Default"]); for (key_id, public_key, secret_key, is_default) in addresses { let is_default = match is_default { 1 => "*", _ => "", }; let address: Address = StandardAddress::from_public(network, *public_key).into(); table.add_row(row![key_id, address, public_key, secret_key, is_default]); } table } pub fn prettytable_balance( balmap: &HashMap, alimap: &HashMap, ) -> Table { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_titles(row!["Token ID", "Aliases", "Balance"]); for (token_id, balance) in balmap.iter() { let alias = match alimap.get(token_id) { Some(v) => v, None => "-", }; table.add_row(row![token_id, alias, encode_base10(*balance, BALANCE_BASE10_DECIMALS)]); } table } pub fn prettytable_coins( coins: &[(OwnCoin, u32, bool, Option, String)], alimap: &HashMap, ) -> Table { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_titles(row![ "Coin", "Token ID", "Aliases", "Value", "Spend Hook", "User Data", "Creation Height", "Spent", "Spent Height", "Spent TX", ]); for coin in coins { let alias = match alimap.get(&coin.0.note.token_id.to_string()) { Some(v) => v, None => "-", }; let spend_hook = if coin.0.note.spend_hook != FuncId::none() { format!("{}", coin.0.note.spend_hook) } else { String::from("-") }; let user_data = if coin.0.note.user_data != pallas::Base::ZERO { bs58::encode(serialize(&coin.0.note.user_data)).into_string().to_string() } else { String::from("-") }; let spent_height = match coin.3 { Some(spent_height) => spent_height.to_string(), None => String::from("-"), }; table.add_row(row![ bs58::encode(&serialize(&coin.0.coin.inner())).into_string().to_string(), coin.0.note.token_id, alias, format!( "{} ({})", coin.0.note.value, encode_base10(coin.0.note.value, BALANCE_BASE10_DECIMALS) ), spend_hook, user_data, coin.1, coin.2, spent_height, coin.4, ]); } table } pub fn prettytable_tokenlist( tokens: &[(TokenId, SecretKey, BaseBlind, bool, Option)], alimap: &HashMap, ) -> Table { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_titles(row![ "Token ID", "Aliases", "Mint Authority", "Token Blind", "Frozen", "Freeze Height", ]); for (token_id, authority, blind, frozen, freeze_height) in tokens { let alias = match alimap.get(&token_id.to_string()) { Some(v) => v, None => "-", }; let freeze_height = match freeze_height { Some(freeze_height) => freeze_height.to_string(), None => String::from("-"), }; table.add_row(row![token_id, alias, authority, blind, frozen, freeze_height]); } table } pub fn prettytable_contract_history(deploy_history: &[(String, String, u32)]) -> Table { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_titles(row!["Transaction Hash", "Type", "Block Height"]); for (tx_hash, tx_type, block_height) in deploy_history { table.add_row(row![tx_hash, tx_type, block_height]); } table } pub fn prettytable_contract_auth(auths: &[(ContractId, SecretKey, bool, Option)]) -> Table { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_titles(row!["Contract ID", "Secret Key", "Locked", "Lock Height"]); for (contract_id, secret_key, is_locked, lock_height) in auths { let lock_height = match lock_height { Some(lock_height) => lock_height.to_string(), None => String::from("-"), }; table.add_row(row![contract_id, secret_key, is_locked, lock_height]); } table } pub fn prettytable_aliases(alimap: &HashMap) -> Table { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_titles(row!["Alias", "Token ID"]); for (alias, token_id) in alimap.iter() { table.add_row(row![alias, token_id]); } table } pub fn prettytable_scanned_blocks(scanned_blocks: &[(u32, String, String)]) -> Table { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.set_titles(row!["Height", "Hash", "Signing Key"]); for (height, hash, signing_key) in scanned_blocks { table.add_row(row![height, hash, signing_key]); } table } pub fn pretty_tx(tx: &Transaction) -> String { let hash = tx.hash().to_string(); let mut fees: Vec = vec![]; let mut fees_total: u64 = 0; let mut fees_overflow = false; let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); table.add_row(row!["", "Contract", "Function"]); for (i, call) in tx.calls.iter().enumerate() { if call.data.is_money_fee() { if let Ok(fee) = deserialize(&call.data.data[1..9]) { fees.push(format!("{} DRK", encode_base10(fee, BALANCE_BASE10_DECIMALS))); fees_total = fees_total.checked_add(fee).unwrap_or_else(|| { fees_overflow = true; u64::MAX }); } else { fees.push("invalid".to_string()); } } let contract_name = match call.data.contract_id { id if id == *MONEY_CONTRACT_ID => "Money", id if id == *DAO_CONTRACT_ID => "DAO", id if id == *DEPLOYOOOR_CONTRACT_ID => "Deployooor", _ => "Custom", }; let calldata = &call.data.data; table.add_row(row![ i.to_string(), format!("{} [{}]", call.data.contract_id.to_string(), contract_name), // Function code if !calldata.is_empty() { calldata[0].to_string() } else { "-".to_string() }, ]); } let fee = match fees.len() { 0 => "-".to_string(), 1 => fees[0].clone(), _ => format!( "{} [TOTAL: {}]", fees.join(", "), if fees_overflow { "OVERFLOW".to_string() } else { format!("{} DRK", encode_base10(fees_total, BALANCE_BASE10_DECIMALS)) } ), }; format!("Hash: {hash}\nFee: {fee}\n\n{table}") }