Sfoglia il codice sorgente

drk: Address support

x 7 mesi fa
parent
commit
34cafd044b
5 ha cambiato i file con 401 aggiunte e 309 eliminazioni
  1. 209 0
      bin/drk/src/common.rs
  2. 67 157
      bin/drk/src/interactive.rs
  3. 8 1
      bin/drk/src/lib.rs
  4. 97 151
      bin/drk/src/main.rs
  5. 20 0
      src/sdk/src/crypto/keypair.rs

+ 209 - 0
bin/drk/src/common.rs

@@ -0,0 +1,209 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 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 <https://www.gnu.org/licenses/>.
+ */
+
+use std::collections::HashMap;
+
+use darkfi::{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,
+    },
+    pasta::pallas,
+};
+use darkfi_serial::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", "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, secret_key, is_default]);
+    }
+
+    table
+}
+
+pub fn prettytable_balance(
+    balmap: &HashMap<String, u64>,
+    alimap: &HashMap<String, String>,
+) -> 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<u32>, String)],
+    alimap: &HashMap<String, String>,
+) -> 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<u32>)],
+    alimap: &HashMap<String, String>,
+) -> 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<u32>)]) -> 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<String, TokenId>) -> 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
+}

+ 67 - 157
bin/drk/src/interactive.rs

@@ -48,8 +48,9 @@ use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction};
 use darkfi_money_contract::model::{Coin, CoinAttributes, TokenId};
 use darkfi_sdk::{
     crypto::{
-        note::AeadEncryptedNote, BaseBlind, ContractId, FuncId, FuncRef, Keypair, PublicKey,
-        SecretKey, DAO_CONTRACT_ID,
+        keypair::{Address, StandardAddress},
+        note::AeadEncryptedNote,
+        BaseBlind, ContractId, FuncId, FuncRef, Keypair, SecretKey, DAO_CONTRACT_ID,
     },
     pasta::{group::ff::PrimeField, pallas},
     tx::TransactionHash,
@@ -61,6 +62,7 @@ use crate::{
         append_or_print, generate_completions, kaching, parse_token_pair, parse_tx_from_input,
         parse_value_pair, print_output,
     },
+    common::*,
     dao::{DaoParams, ProposalRecord},
     money::BALANCE_BASE10_DECIMALS,
     rpc::subscribe_blocks,
@@ -831,8 +833,9 @@ async fn handle_wallet_keygen(drk: &DrkPtr, output: &mut Vec<String>) {
 
 /// Auxiliary function to define the wallet balance subcommand handling.
 async fn handle_wallet_balance(drk: &DrkPtr, output: &mut Vec<String>) {
-    let lock = drk.read().await;
-    let balmap = match lock.money_balance().await {
+    let drk = drk.read().await;
+
+    let balmap = match drk.money_balance().await {
         Ok(m) => m,
         Err(e) => {
             output.push(format!("Failed to fetch balances map: {e}"));
@@ -840,7 +843,7 @@ async fn handle_wallet_balance(drk: &DrkPtr, output: &mut Vec<String>) {
         }
     };
 
-    let aliases_map = match lock.get_aliases_mapped_by_token().await {
+    let alimap = match drk.get_aliases_mapped_by_token().await {
         Ok(m) => m,
         Err(e) => {
             output.push(format!("Failed to fetch aliases map: {e}"));
@@ -848,18 +851,7 @@ async fn handle_wallet_balance(drk: &DrkPtr, output: &mut Vec<String>) {
         }
     };
 
-    // Create a prettytable with the new data:
-    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 aliases = match aliases_map.get(token_id) {
-            Some(a) => a,
-            None => "-",
-        };
-
-        table.add_row(row![token_id, aliases, encode_base10(*balance, BALANCE_BASE10_DECIMALS)]);
-    }
+    let table = prettytable_balance(&balmap, &alimap);
 
     if table.is_empty() {
         output.push(String::from("No unspent balances found"));
@@ -870,33 +862,33 @@ async fn handle_wallet_balance(drk: &DrkPtr, output: &mut Vec<String>) {
 
 /// Auxiliary function to define the wallet address subcommand handling.
 async fn handle_wallet_address(drk: &DrkPtr, output: &mut Vec<String>) {
-    match drk.read().await.default_address().await {
-        Ok(address) => output.push(format!("{address}")),
-        Err(e) => output.push(format!("Failed to fetch default address: {e}")),
-    }
+    let drk = drk.read().await;
+
+    let public_key = match drk.default_address().await {
+        Ok(v) => v,
+        Err(e) => {
+            output.push(format!("Failed to fetch default address: {e}"));
+            return
+        }
+    };
+
+    let addr: Address = StandardAddress::from_public(drk.network, public_key).into();
+    output.push(format!("{addr}"));
 }
 
 /// Auxiliary function to define the wallet addresses subcommand handling.
 async fn handle_wallet_addresses(drk: &DrkPtr, output: &mut Vec<String>) {
-    let addresses = match drk.read().await.addresses().await {
-        Ok(a) => a,
+    let drk = drk.read().await;
+    let network = drk.network;
+    let addresses = match drk.addresses().await {
+        Ok(v) => v,
         Err(e) => {
             output.push(format!("Failed to fetch addresses: {e}"));
             return
         }
     };
 
-    // Create a prettytable with the new data:
-    let mut table = Table::new();
-    table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
-    table.set_titles(row!["Key ID", "Public Key", "Secret Key", "Is Default"]);
-    for (key_id, public_key, secret_key, is_default) in addresses {
-        let is_default = match is_default {
-            1 => "*",
-            _ => "",
-        };
-        table.add_row(row![key_id, public_key, secret_key, is_default]);
-    }
+    let table = prettytable_addrs(network, &addresses);
 
     if table.is_empty() {
         output.push(String::from("No addresses found"));
@@ -1011,61 +1003,7 @@ async fn handle_wallet_coins(drk: &DrkPtr, output: &mut Vec<String>) {
         }
     };
 
-    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 aliases = match aliases_map.get(&coin.0.note.token_id.to_string()) {
-            Some(a) => a,
-            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_async(&coin.0.note.user_data).await).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_async(&coin.0.coin.inner()).await).into_string().to_string(),
-            coin.0.note.token_id,
-            aliases,
-            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,
-        ]);
-    }
-
+    let table = prettytable_coins(&coins, &aliases_map);
     output.push(format!("{table}"));
 }
 
@@ -1228,13 +1166,19 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>)
     };
     index += 1;
 
-    let rcpt = match PublicKey::from_str(parts[index]) {
+    let rcpt = match Address::from_str(parts[index]) {
         Ok(r) => r,
         Err(e) => {
             output.push(format!("Invalid recipient: {e}"));
             return
         }
     };
+
+    if rcpt.network() != lock.network {
+        output.push("Mismatched recipient address prefix".to_string());
+        return
+    }
+
     index += 1;
 
     let spend_hook = if index < parts.len() {
@@ -1280,7 +1224,10 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String>)
         None
     };
 
-    match lock.transfer(&amount, token_id, rcpt, spend_hook, user_data, half_split).await {
+    match lock
+        .transfer(&amount, token_id, *rcpt.public_key(), spend_hook, user_data, half_split)
+        .await
+    {
         Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
         Err(e) => output.push(format!("Failed to create payment transaction: {e}")),
     }
@@ -1691,7 +1638,7 @@ async fn handle_dao_balance(drk: &DrkPtr, parts: &[&str], output: &mut Vec<Strin
         }
     };
 
-    let aliases_map = match lock.get_aliases_mapped_by_token().await {
+    let alimap = match lock.get_aliases_mapped_by_token().await {
         Ok(m) => m,
         Err(e) => {
             output.push(format!("Failed to fetch aliases map: {e}"));
@@ -1699,17 +1646,7 @@ async fn handle_dao_balance(drk: &DrkPtr, parts: &[&str], output: &mut Vec<Strin
         }
     };
 
-    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 aliases = match aliases_map.get(token_id) {
-            Some(a) => a,
-            None => "-",
-        };
-
-        table.add_row(row![token_id, aliases, encode_base10(*balance, BALANCE_BASE10_DECIMALS)]);
-    }
+    let table = prettytable_balance(&balmap, &alimap);
 
     if table.is_empty() {
         output.push(String::from("No unspent balances found"))
@@ -1765,7 +1702,7 @@ async fn handle_dao_propose_transfer(drk: &DrkPtr, parts: &[&str], output: &mut
         }
     };
 
-    let rcpt = match PublicKey::from_str(parts[6]) {
+    let rcpt = match Address::from_str(parts[6]) {
         Ok(r) => r,
         Err(e) => {
             output.push(format!("Invalid recipient: {e}"));
@@ -1773,6 +1710,11 @@ async fn handle_dao_propose_transfer(drk: &DrkPtr, parts: &[&str], output: &mut
         }
     };
 
+    if rcpt.network() != lock.network {
+        output.push("Recipient address prefix mismatch".to_string());
+        return
+    }
+
     let mut index = 7;
     let spend_hook = if index < parts.len() {
         match FuncId::from_str(parts[index]) {
@@ -1820,7 +1762,15 @@ async fn handle_dao_propose_transfer(drk: &DrkPtr, parts: &[&str], output: &mut
     match drk
         .read()
         .await
-        .dao_propose_transfer(parts[2], duration, &amount, token_id, rcpt, spend_hook, user_data)
+        .dao_propose_transfer(
+            parts[2],
+            duration,
+            &amount,
+            token_id,
+            *rcpt.public_key(),
+            spend_hook,
+            user_data,
+        )
         .await
     {
         Ok(proposal) => output.push(format!("Generated proposal: {}", proposal.bulla())),
@@ -2859,13 +2809,7 @@ async fn handle_alias_show(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String
         }
     };
 
-    // Create a prettytable with the new data:
-    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 map.iter() {
-        table.add_row(row![alias, token_id]);
-    }
+    let table = prettytable_aliases(&map);
 
     if table.is_empty() {
         output.push(String::from("No aliases found"));
@@ -2989,30 +2933,7 @@ async fn handle_token_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String
         }
     };
 
-    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 aliases = match aliases_map.get(&token_id.to_string()) {
-            Some(a) => a,
-            None => "-",
-        };
-
-        let freeze_height = match freeze_height {
-            Some(freeze_height) => freeze_height.to_string(),
-            None => String::from("-"),
-        };
-
-        table.add_row(row![token_id, aliases, authority, blind, frozen, freeze_height]);
-    }
+    let table = prettytable_tokenlist(&tokens, &aliases_map);
 
     if table.is_empty() {
         output.push(String::from("No tokens found"));
@@ -3038,7 +2959,7 @@ async fn handle_token_mint(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String
         return
     }
 
-    let rcpt = match PublicKey::from_str(parts[4]) {
+    let rcpt = match Address::from_str(parts[4]) {
         Ok(r) => r,
         Err(e) => {
             output.push(format!("Invalid recipient: {e}"));
@@ -3047,6 +2968,12 @@ async fn handle_token_mint(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String
     };
 
     let lock = drk.read().await;
+
+    if rcpt.network() != lock.network {
+        output.push("Recipient address prefix mismatch".to_string());
+        return
+    }
+
     let token_id = match lock.get_token(String::from(parts[2])).await {
         Ok(t) => t,
         Err(e) => {
@@ -3100,7 +3027,7 @@ async fn handle_token_mint(drk: &DrkPtr, parts: &[&str], output: &mut Vec<String
         None
     };
 
-    match lock.mint_token(&amount, rcpt, token_id, spend_hook, user_data).await {
+    match lock.mint_token(&amount, *rcpt.public_key(), token_id, spend_hook, user_data).await {
         Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
         Err(e) => output.push(format!("Failed to create token mint transaction: {e}")),
     }
@@ -3195,14 +3122,7 @@ async fn handle_contract_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<Str
             }
         };
 
-        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 history {
-            table.add_row(row![tx_hash, tx_type, block_height]);
-        }
-
+        let table = prettytable_contract_history(&history);
         if table.is_empty() {
             output.push(String::from("No history records found"));
         } else {
@@ -3219,17 +3139,7 @@ async fn handle_contract_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<Str
         }
     };
 
-    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]);
-    }
+    let table = prettytable_contract_auth(&auths);
 
     if table.is_empty() {
         output.push(String::from("No deploy authorities found"));

+ 8 - 1
bin/drk/src/lib.rs

@@ -22,11 +22,15 @@ use smol::lock::RwLock;
 use url::Url;
 
 use darkfi::{system::ExecutorPtr, util::path::expand_path, Error, Result};
+use darkfi_sdk::crypto::keypair::Network;
 
 /// Error codes
 pub mod error;
 use error::{WalletDbError, WalletDbResult};
 
+/// Common shared functions
+pub mod common;
+
 /// darkfid JSON-RPC related methods
 pub mod rpc;
 use rpc::DarkfidRpcClient;
@@ -74,6 +78,8 @@ pub type DrkPtr = Arc<RwLock<Drk>>;
 
 /// CLI-util structure
 pub struct Drk {
+    /// Blockchain network
+    pub network: Network,
     /// Blockchain cache database operations handler
     pub cache: Cache,
     /// Wallet database operations handler
@@ -86,6 +92,7 @@ pub struct Drk {
 
 impl Drk {
     pub async fn new(
+        network: Network,
         cache_path: String,
         wallet_path: String,
         wallet_pass: String,
@@ -118,7 +125,7 @@ impl Drk {
             None
         };
 
-        Ok(Self { cache, wallet, rpc_client, fun })
+        Ok(Self { network, cache, wallet, rpc_client, fun })
     }
 
     pub fn into_ptr(self) -> DrkPtr {

+ 97 - 151
bin/drk/src/main.rs

@@ -46,8 +46,9 @@ use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction};
 use darkfi_money_contract::model::{Coin, CoinAttributes, TokenId};
 use darkfi_sdk::{
     crypto::{
-        note::AeadEncryptedNote, BaseBlind, ContractId, FuncId, FuncRef, Keypair, PublicKey,
-        SecretKey, DAO_CONTRACT_ID,
+        keypair::{Address, Network},
+        note::AeadEncryptedNote,
+        BaseBlind, ContractId, FuncId, FuncRef, Keypair, SecretKey, DAO_CONTRACT_ID,
     },
     pasta::{group::ff::PrimeField, pallas},
     tx::TransactionHash,
@@ -59,6 +60,7 @@ use drk::{
         generate_completions, kaching, parse_token_pair, parse_tx_from_stdin, parse_value_pair,
         print_output,
     },
+    common::*,
     dao::{DaoParams, ProposalRecord},
     interactive::interactive,
     money::BALANCE_BASE10_DECIMALS,
@@ -598,7 +600,13 @@ struct BlockchainNetwork {
 async fn parse_blockchain_config(
     config: Option<String>,
     network: &str,
-) -> Result<BlockchainNetwork> {
+) -> Result<(BlockchainNetwork, Network)> {
+    let used_net = match network {
+        "mainnet" | "localnet" => Network::Mainnet,
+        "testnet" => Network::Testnet,
+        _ => return Err(Error::ParseFailed("Invalid blockchain network")),
+    };
+
     // Grab config path
     let config_path = get_config_path(config, CONFIG_FILE)?;
 
@@ -633,11 +641,12 @@ async fn parse_blockchain_config(
             }
         };
 
-    Ok(network_config)
+    Ok((network_config, used_net))
 }
 
 /// Auxiliary function to create a `Drk` wallet for provided configuration.
 async fn new_wallet(
+    network: Network,
     cache_path: String,
     wallet_path: String,
     wallet_pass: String,
@@ -651,7 +660,7 @@ async fn new_wallet(
         exit(2);
     }
 
-    match Drk::new(cache_path, wallet_path, wallet_pass, endpoint, ex, fun).await {
+    match Drk::new(network, cache_path, wallet_path, wallet_pass, endpoint, ex, fun).await {
         Ok(wallet) => wallet,
         Err(e) => {
             eprintln!("Error initializing wallet: {e}");
@@ -663,7 +672,7 @@ async fn new_wallet(
 async_daemonize!(realmain);
 async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
     // Grab blockchain network configuration
-    let blockchain_config = match args.network.as_str() {
+    let (blockchain_config, network) = match args.network.as_str() {
         "localnet" => parse_blockchain_config(args.config, "localnet").await?,
         "testnet" => parse_blockchain_config(args.config, "testnet").await?,
         "mainnet" => parse_blockchain_config(args.config, "mainnet").await?,
@@ -686,6 +695,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             set_terminal_writer(args.verbose, non_blocking)?;
 
             let drk = new_wallet(
+                network,
                 blockchain_config.cache_path,
                 blockchain_config.wallet_path,
                 blockchain_config.wallet_pass,
@@ -695,6 +705,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             )
             .await
             .into_ptr();
+
             interactive(
                 &drk,
                 &blockchain_config.endpoint,
@@ -704,6 +715,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 &ex,
             )
             .await;
+
             drk.read().await.stop_rpc_client().await?;
             Ok(())
         }
@@ -719,6 +731,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
         Subcmd::Ping => {
             let drk = new_wallet(
+                network,
                 blockchain_config.cache_path,
                 blockchain_config.wallet_path,
                 blockchain_config.wallet_pass,
@@ -743,6 +756,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
         Subcmd::Wallet { command } => {
             let drk = new_wallet(
+                network,
                 blockchain_config.cache_path,
                 blockchain_config.wallet_path,
                 blockchain_config.wallet_pass,
@@ -824,18 +838,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
                 WalletSubcmd::Addresses => {
                     let addresses = drk.addresses().await?;
-
-                    // Create a prettytable with the new data:
-                    let mut table = Table::new();
-                    table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
-                    table.set_titles(row!["Key ID", "Public Key", "Secret Key", "Is Default"]);
-                    for (key_id, public_key, secret_key, is_default) in addresses {
-                        let is_default = match is_default {
-                            1 => "*",
-                            _ => "",
-                        };
-                        table.add_row(row![key_id, public_key, secret_key, is_default]);
-                    }
+                    let table = prettytable_addrs(drk.network, &addresses);
 
                     if table.is_empty() {
                         println!("No addresses found");
@@ -895,72 +898,11 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
                 WalletSubcmd::Coins => {
                     let coins = drk.get_coins(true).await?;
-
                     if coins.is_empty() {
                         return Ok(())
                     }
-
                     let aliases_map = drk.get_aliases_mapped_by_token().await?;
-
-                    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 aliases = match aliases_map.get(&coin.0.note.token_id.to_string()) {
-                            Some(a) => a,
-                            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_async(&coin.0.note.user_data).await)
-                                .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_async(&coin.0.coin.inner()).await)
-                                .into_string()
-                                .to_string(),
-                            coin.0.note.token_id,
-                            aliases,
-                            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,
-                        ]);
-                    }
-
+                    let table = prettytable_coins(&coins, &aliases_map);
                     println!("{table}");
                 }
 
@@ -1016,6 +958,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             let tx = parse_tx_from_stdin().await?;
 
             let drk = new_wallet(
+                network,
                 blockchain_config.cache_path,
                 blockchain_config.wallet_path,
                 blockchain_config.wallet_pass,
@@ -1055,6 +998,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             let coin = Coin::from(elem);
             let drk = new_wallet(
+                network,
                 blockchain_config.cache_path,
                 blockchain_config.wallet_path,
                 blockchain_config.wallet_pass,
@@ -1073,6 +1017,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
         Subcmd::Transfer { amount, token, recipient, spend_hook, user_data, half_split } => {
             let drk = new_wallet(
+                network,
                 blockchain_config.cache_path,
                 blockchain_config.wallet_path,
                 blockchain_config.wallet_pass,
@@ -1087,7 +1032,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 exit(2);
             }
 
-            let rcpt = match PublicKey::from_str(&recipient) {
+            let rcpt = match Address::from_str(&recipient) {
                 Ok(r) => r,
                 Err(e) => {
                     eprintln!("Invalid recipient: {e}");
@@ -1095,6 +1040,11 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 }
             };
 
+            if rcpt.network() != drk.network {
+                eprintln!("Recipient address prefix mismatch");
+                exit(2);
+            }
+
             let token_id = match drk.get_token(token).await {
                 Ok(t) => t,
                 Err(e) => {
@@ -1136,7 +1086,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             };
 
             let tx = match drk
-                .transfer(&amount, token_id, rcpt, spend_hook, user_data, half_split)
+                .transfer(&amount, token_id, *rcpt.public_key(), spend_hook, user_data, half_split)
                 .await
             {
                 Ok(t) => t,
@@ -1154,6 +1104,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
         Subcmd::Otc { command } => match command {
             OtcSubcmd::Init { value_pair, token_pair } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1188,6 +1139,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 let partial: PartialSwapData = deserialize_async(&bytes).await?;
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1217,6 +1169,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 };
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1240,6 +1193,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 let mut tx = parse_tx_from_stdin().await?;
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1293,6 +1247,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1359,6 +1314,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 let params = DaoParams::from_toml_str(&buf)?;
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1380,6 +1336,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             DaoSubcmd::List { name } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1401,6 +1358,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             DaoSubcmd::Balance { name } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1425,22 +1383,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     }
                 };
 
-                // Create a prettytable with the new data:
-                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 aliases = match aliases_map.get(token_id) {
-                        Some(a) => a,
-                        None => "-",
-                    };
-
-                    table.add_row(row![
-                        token_id,
-                        aliases,
-                        encode_base10(*balance, BALANCE_BASE10_DECIMALS)
-                    ]);
-                }
+                let table = prettytable_balance(&balmap, &aliases_map);
 
                 if table.is_empty() {
                     println!("No unspent balances found");
@@ -1453,6 +1396,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             DaoSubcmd::Mint { name } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1483,6 +1427,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 user_data,
             } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1497,7 +1442,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     exit(2);
                 }
 
-                let rcpt = match PublicKey::from_str(&recipient) {
+                let rcpt = match Address::from_str(&recipient) {
                     Ok(r) => r,
                     Err(e) => {
                         eprintln!("Invalid recipient: {e}");
@@ -1505,6 +1450,11 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     }
                 };
 
+                if rcpt.network() != drk.network {
+                    eprintln!("Recipient address prefix mismatch");
+                    exit(2);
+                }
+
                 let token_id = match drk.get_token(token).await {
                     Ok(t) => t,
                     Err(e) => {
@@ -1547,7 +1497,13 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
                 let proposal = match drk
                     .dao_propose_transfer(
-                        &name, duration, &amount, token_id, rcpt, spend_hook, user_data,
+                        &name,
+                        duration,
+                        &amount,
+                        token_id,
+                        *rcpt.public_key(),
+                        spend_hook,
+                        user_data,
                     )
                     .await
                 {
@@ -1565,6 +1521,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             DaoSubcmd::ProposeGeneric { name, duration, user_data } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1610,6 +1567,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             DaoSubcmd::Proposals { name } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1637,6 +1595,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 };
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1845,6 +1804,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 let encrypted_proposal: AeadEncryptedNote = deserialize_async(&bytes).await?;
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1912,6 +1872,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 };
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1942,6 +1903,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 };
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -1999,6 +1961,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             DaoSubcmd::MiningConfig { name } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2023,6 +1986,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             let mut tx = parse_tx_from_stdin().await?;
 
             let drk = new_wallet(
+                network,
                 blockchain_config.cache_path,
                 blockchain_config.wallet_path,
                 blockchain_config.wallet_pass,
@@ -2053,6 +2017,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             let tx = parse_tx_from_stdin().await?;
 
             let drk = new_wallet(
+                network,
                 blockchain_config.cache_path,
                 blockchain_config.wallet_path,
                 blockchain_config.wallet_pass,
@@ -2091,6 +2056,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
         Subcmd::Scan { reset } => {
             let drk = new_wallet(
+                network,
                 blockchain_config.cache_path,
                 blockchain_config.wallet_path,
                 blockchain_config.wallet_pass,
@@ -2124,6 +2090,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 let tx_hash = TransactionHash(*blake3::Hash::from_hex(&tx_hash)?.as_bytes());
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2164,6 +2131,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 let tx = parse_tx_from_stdin().await?;
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2189,6 +2157,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             ExplorerSubcmd::TxsHistory { tx_hash, encode } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2248,6 +2217,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             ExplorerSubcmd::ClearReverted => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2270,6 +2240,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             ExplorerSubcmd::ScannedBlocks { height } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2336,6 +2307,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 };
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2368,6 +2340,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 };
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2378,13 +2351,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 .await;
                 let map = drk.get_aliases(alias, token_id).await?;
 
-                // Create a prettytable with the new data:
-                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 map.iter() {
-                    table.add_row(row![alias, token_id]);
-                }
+                let table = prettytable_aliases(&map);
 
                 if table.is_empty() {
                     println!("No aliases found");
@@ -2397,6 +2364,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             AliasSubcmd::Remove { alias } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2436,6 +2404,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 };
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2452,6 +2421,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             TokenSubcmd::GenerateMint => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2470,6 +2440,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             TokenSubcmd::List => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2487,30 +2458,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     }
                 };
 
-                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 aliases = match aliases_map.get(&token_id.to_string()) {
-                        Some(a) => a,
-                        None => "-",
-                    };
-
-                    let freeze_height = match freeze_height {
-                        Some(freeze_height) => freeze_height.to_string(),
-                        None => String::from("-"),
-                    };
-
-                    table.add_row(row![token_id, aliases, authority, blind, frozen, freeze_height]);
-                }
+                let table = prettytable_tokenlist(&tokens, &aliases_map);
 
                 if table.is_empty() {
                     println!("No tokens found");
@@ -2523,6 +2471,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             TokenSubcmd::Mint { token, amount, recipient, spend_hook, user_data } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2537,7 +2486,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     exit(2);
                 }
 
-                let rcpt = match PublicKey::from_str(&recipient) {
+                let rcpt = match Address::from_str(&recipient) {
                     Ok(r) => r,
                     Err(e) => {
                         eprintln!("Invalid recipient: {e}");
@@ -2545,6 +2494,11 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     }
                 };
 
+                if rcpt.network() != drk.network {
+                    eprintln!("Recipient address prefix mismatch");
+                    exit(2);
+                }
+
                 let token_id = match drk.get_token(token).await {
                     Ok(t) => t,
                     Err(e) => {
@@ -2585,7 +2539,9 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                     None => None,
                 };
 
-                let tx = match drk.mint_token(&amount, rcpt, token_id, spend_hook, user_data).await
+                let tx = match drk
+                    .mint_token(&amount, *rcpt.public_key(), token_id, spend_hook, user_data)
+                    .await
                 {
                     Ok(tx) => tx,
                     Err(e) => {
@@ -2601,6 +2557,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             TokenSubcmd::Freeze { token } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2634,6 +2591,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
         Subcmd::Contract { command } => match command {
             ContractSubcmd::GenerateDeploy => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2656,6 +2614,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             ContractSubcmd::List { contract_id } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2676,13 +2635,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
                     let history = drk.get_deploy_auth_history(&contract_id).await?;
 
-                    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 history {
-                        table.add_row(row![tx_hash, tx_type, block_height]);
-                    }
+                    let table = prettytable_contract_history(&history);
 
                     if table.is_empty() {
                         println!("No history records found");
@@ -2695,17 +2648,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
                 let auths = drk.list_deploy_auth().await?;
 
-                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]);
-                }
+                let table = prettytable_contract_auth(&auths);
 
                 if table.is_empty() {
                     println!("No deploy authorities found");
@@ -2718,6 +2661,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
 
             ContractSubcmd::ExportData { tx_hash } => {
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2752,6 +2696,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 };
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,
@@ -2785,6 +2730,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 };
 
                 let drk = new_wallet(
+                    network,
                     blockchain_config.cache_path,
                     blockchain_config.wallet_path,
                     blockchain_config.wallet_pass,

+ 20 - 0
src/sdk/src/crypto/keypair.rs

@@ -217,6 +217,12 @@ pub enum Network {
     Testnet,
 }
 
+impl Network {
+    pub fn is_testnet(self) -> bool {
+        self == Network::Testnet
+    }
+}
+
 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
 pub enum AddressPrefix {
     MainnetStandard = 0x39,
@@ -258,6 +264,14 @@ impl StandardAddress {
             Network::Testnet => AddressPrefix::TestnetStandard,
         }
     }
+
+    pub fn public_key(&self) -> &PublicKey {
+        &self.spending_key
+    }
+
+    pub fn from_public(network: Network, public_key: PublicKey) -> Self {
+        Self { network, spending_key: public_key }
+    }
 }
 
 impl From<StandardAddress> for Address {
@@ -284,6 +298,12 @@ impl Address {
             Self::Standard(addr) => addr.network,
         }
     }
+
+    pub fn public_key(&self) -> &PublicKey {
+        match self {
+            Self::Standard(addr) => addr.public_key(),
+        }
+    }
 }
 
 impl FromStr for Address {