mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
drk/interactive: wallet command handling added
This commit is contained in:
@@ -16,7 +16,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{io::ErrorKind, str::FromStr};
|
||||
use std::{
|
||||
io::{stdin, ErrorKind},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use futures::{select, FutureExt};
|
||||
use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
|
||||
@@ -24,18 +27,23 @@ use linenoise_rs::{
|
||||
linenoise_history_add, linenoise_history_load, linenoise_history_save,
|
||||
linenoise_set_completion_callback, linenoise_set_hints_callback, LinenoiseState,
|
||||
};
|
||||
use prettytable::{format, row, Table};
|
||||
use smol::channel::{unbounded, Receiver, Sender};
|
||||
use url::Url;
|
||||
|
||||
use darkfi::{
|
||||
cli_desc,
|
||||
system::{msleep, ExecutorPtr, StoppableTask, StoppableTaskPtr},
|
||||
util::path::expand_path,
|
||||
util::{parse::encode_base10, path::expand_path},
|
||||
zk::halo2::Field,
|
||||
Error,
|
||||
};
|
||||
use darkfi_sdk::{crypto::FuncId, pasta::pallas};
|
||||
use darkfi_serial::{deserialize_async, serialize_async};
|
||||
|
||||
use crate::{
|
||||
cli_util::{generate_completions, kaching},
|
||||
money::BALANCE_BASE10_DECIMALS,
|
||||
rpc::subscribe_blocks,
|
||||
DrkPtr,
|
||||
};
|
||||
@@ -54,6 +62,7 @@ fn help() {
|
||||
println!("\tkaching: Fun");
|
||||
println!("\tping: Send a ping request to the darkfid RPC endpoint");
|
||||
println!("\tcompletions: Generate a SHELL completion script and print to stdout");
|
||||
println!("\twallet: Wallet operations");
|
||||
println!(
|
||||
"\tsubscribe: Perform a scan and then subscribe to darkfid to listen for incoming blocks"
|
||||
);
|
||||
@@ -86,6 +95,21 @@ fn completion(buf: &str, lc: &mut Vec<String>) {
|
||||
return
|
||||
}
|
||||
|
||||
if buf.starts_with("w") {
|
||||
lc.push("wallet".to_string());
|
||||
lc.push("wallet --initialize".to_string());
|
||||
lc.push("wallet --keygen".to_string());
|
||||
lc.push("wallet --balance".to_string());
|
||||
lc.push("wallet --address".to_string());
|
||||
lc.push("wallet --addresses".to_string());
|
||||
lc.push("wallet --default_address".to_string());
|
||||
lc.push("wallet --secrets".to_string());
|
||||
lc.push("wallet --import_secrets".to_string());
|
||||
lc.push("wallet --tree".to_string());
|
||||
lc.push("wallet --coins".to_string());
|
||||
return
|
||||
}
|
||||
|
||||
if buf.starts_with("su") {
|
||||
lc.push("subscribe".to_string());
|
||||
return
|
||||
@@ -108,6 +132,7 @@ fn completion(buf: &str, lc: &mut Vec<String>) {
|
||||
|
||||
if buf.starts_with("sc") {
|
||||
lc.push("scan".to_string());
|
||||
lc.push("scan --reset".to_string());
|
||||
return
|
||||
}
|
||||
|
||||
@@ -116,6 +141,7 @@ fn completion(buf: &str, lc: &mut Vec<String>) {
|
||||
lc.push("subscribe".to_string());
|
||||
lc.push("snooze".to_string());
|
||||
lc.push("scan".to_string());
|
||||
lc.push("scan --reset".to_string());
|
||||
return
|
||||
}
|
||||
|
||||
@@ -129,7 +155,11 @@ fn completion(buf: &str, lc: &mut Vec<String>) {
|
||||
fn hints(buf: &str) -> Option<(String, i32, bool)> {
|
||||
match buf {
|
||||
"completions " => Some(("{shell}".to_string(), 35, false)), // 35 = magenta
|
||||
"scan " => Some(("--reset {height}".to_string(), 35, false)), // 35 = magenta
|
||||
"wallet " => Some(("--{initialize|keygen|balance|address|addresses|default_address|secrets|import_secrets|tree|coins}".to_string(), 35, false)),
|
||||
"wallet -" => Some(("-{initialize|keygen|balance|address|addresses|default_address|secrets|import_secrets|tree|coins}".to_string(), 35, false)),
|
||||
"wallet --" => Some(("{initialize|keygen|balance|address|addresses|default_address|secrets|import_secrets|tree|coins}".to_string(), 35, false)),
|
||||
"wallet --default_address " => Some(("{address_id}".to_string(), 35, false)),
|
||||
"scan --reset " => Some(("{height}".to_string(), 35, false)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -196,6 +226,7 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: &
|
||||
"kaching" => kaching().await,
|
||||
"ping" => handle_ping(drk).await,
|
||||
"completions" => handle_completions(&parts),
|
||||
"wallet" => handle_wallet(drk, &parts).await,
|
||||
"subscribe" => {
|
||||
handle_subscribe(
|
||||
drk,
|
||||
@@ -333,6 +364,7 @@ async fn handle_ping(drk: &DrkPtr) {
|
||||
|
||||
/// Auxiliary function to define the completions command handling.
|
||||
fn handle_completions(parts: &[&str]) {
|
||||
// Check correct command structure
|
||||
if parts.len() != 2 {
|
||||
println!("Malformed `completions` command");
|
||||
println!("Usage: completions {{shell}}");
|
||||
@@ -344,6 +376,283 @@ fn handle_completions(parts: &[&str]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Auxiliary function to define the wallet command handling.
|
||||
async fn handle_wallet(drk: &DrkPtr, parts: &[&str]) {
|
||||
// Check correct command structure
|
||||
if parts.len() != 2 && parts.len() != 3 {
|
||||
println!("Malformed `wallet` command");
|
||||
println!("Usage: wallet --{{initialize|keygen|balance|address|addresses|default_address|secrets|import_secrets|tree|coins}}");
|
||||
return
|
||||
}
|
||||
|
||||
// Handle command flag
|
||||
if parts[1] == "--initialize" {
|
||||
let lock = drk.read().await;
|
||||
if let Err(e) = lock.initialize_wallet().await {
|
||||
println!("Error initializing wallet: {e:?}");
|
||||
return
|
||||
}
|
||||
if let Err(e) = lock.initialize_money().await {
|
||||
println!("Failed to initialize Money: {e:?}");
|
||||
return
|
||||
}
|
||||
if let Err(e) = lock.initialize_dao().await {
|
||||
println!("Failed to initialize DAO: {e:?}");
|
||||
return
|
||||
}
|
||||
if let Err(e) = lock.initialize_deployooor() {
|
||||
println!("Failed to initialize Deployooor: {e:?}");
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if parts[1] == "--keygen" {
|
||||
if let Err(e) = drk.read().await.money_keygen().await {
|
||||
println!("Failed to generate keypair: {e:?}");
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if parts[1] == "--balance" {
|
||||
let lock = drk.read().await;
|
||||
let balmap = match lock.money_balance().await {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
println!("Failed to fetch balances map: {e:?}");
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
let aliases_map = match lock.get_aliases_mapped_by_token().await {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
println!("Failed to fetch aliases map: {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!["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)
|
||||
]);
|
||||
}
|
||||
|
||||
if table.is_empty() {
|
||||
println!("No unspent balances found");
|
||||
} else {
|
||||
println!("{table}");
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if parts[1] == "--address" {
|
||||
match drk.read().await.default_address().await {
|
||||
Ok(address) => println!("{address}"),
|
||||
Err(e) => println!("Failed to fetch default address: {e:?}"),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if parts[1] == "--addresses" {
|
||||
let addresses = match drk.read().await.addresses().await {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
println!("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]);
|
||||
}
|
||||
|
||||
if table.is_empty() {
|
||||
println!("No addresses found");
|
||||
} else {
|
||||
println!("{table}");
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if parts[1] == "--default_address" {
|
||||
if parts.len() != 3 {
|
||||
println!("Malformed `wallet` command");
|
||||
println!("Usage: wallet --default_address {{address_id}}");
|
||||
}
|
||||
|
||||
let idx = match usize::from_str(parts[2]) {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
println!("Invalid address id: {e:?}");
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = drk.read().await.set_default_address(idx) {
|
||||
println!("Failed to set default address: {e:?}");
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if parts[1] == "--secrets" {
|
||||
match drk.read().await.get_money_secrets().await {
|
||||
Ok(secrets) => {
|
||||
for secret in secrets {
|
||||
println!("{secret}");
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Failed to fetch secrets: {e:?}"),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if parts[1] == "--import_secrets" {
|
||||
let mut secrets = vec![];
|
||||
// TODO: read from a file here not stdin
|
||||
let lines = stdin().lines();
|
||||
for (i, line) in lines.enumerate() {
|
||||
if let Ok(line) = line {
|
||||
let Ok(bytes) = bs58::decode(&line.trim()).into_vec() else {
|
||||
println!("Warning: Failed to decode secret on line {i}");
|
||||
continue
|
||||
};
|
||||
let Ok(secret) = deserialize_async(&bytes).await else {
|
||||
println!("Warning: Failed to deserialize secret on line {i}");
|
||||
continue
|
||||
};
|
||||
secrets.push(secret);
|
||||
}
|
||||
}
|
||||
|
||||
match drk.read().await.import_money_secrets(secrets).await {
|
||||
Ok(pubkeys) => {
|
||||
for key in pubkeys {
|
||||
println!("{key}");
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Failed to import secrets: {e:?}"),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if parts[1] == "--tree" {
|
||||
// TODO: write to a file here not stdout
|
||||
match drk.read().await.get_money_tree().await {
|
||||
Ok(tree) => println!("{tree:#?}"),
|
||||
Err(e) => println!("Failed to fetch tree: {e:?}"),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if parts[1] == "--coins" {
|
||||
let lock = drk.read().await;
|
||||
let coins = match lock.get_coins(true).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
println!("Failed to fetch coins: {e:?}");
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
if coins.is_empty() {
|
||||
return
|
||||
}
|
||||
|
||||
let aliases_map = match lock.get_aliases_mapped_by_token().await {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
println!("Failed to fetch aliases map: {e:?}");
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
]);
|
||||
}
|
||||
|
||||
println!("{table}");
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
println!("Malformed `wallet` command");
|
||||
println!("Usage: wallet --{{initialize|keygen|balance|address|addresses|default_address|secrets|import_secrets|tree|coins}}");
|
||||
}
|
||||
|
||||
/// Auxiliary function to define the subscribe command handling.
|
||||
async fn handle_subscribe(
|
||||
drk: &DrkPtr,
|
||||
|
||||
@@ -886,12 +886,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
|
||||
if coins {
|
||||
let coins = drk.get_coins(true).await?;
|
||||
|
||||
let aliases_map = drk.get_aliases_mapped_by_token().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![
|
||||
|
||||
Reference in New Issue
Block a user